mirror of
https://github.com/ansible/awx.git
synced 2026-01-19 05:31:22 -03:30
Merge pull request #6109 from ansible/inventory-rework
New Inventory Pages
This commit is contained in:
commit
2c0a24f408
@ -1450,7 +1450,6 @@ input[type="checkbox"].checkbox-no-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#inventories_table i[class*="icon-job-"],
|
||||
#home_groups_table i[class*="icon-job-"] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@ -130,6 +130,10 @@
|
||||
.noselect;
|
||||
}
|
||||
|
||||
.Form-tab--notitle {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.Form-tab:hover {
|
||||
color: @btn-txt;
|
||||
background-color: @btn-bg-hov;
|
||||
|
||||
@ -162,10 +162,16 @@ table, tbody {
|
||||
// float: right;
|
||||
}
|
||||
|
||||
.List-actionHolder--leftAlign {
|
||||
width: 50%;
|
||||
margin-left: 50%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.List-actions {
|
||||
display: flex;
|
||||
margin-bottom: -32px;
|
||||
margin-top: 18px;
|
||||
margin-bottom: -34px;
|
||||
}
|
||||
|
||||
.List-auxAction {
|
||||
@ -197,7 +203,7 @@ table, tbody {
|
||||
.List-buttonDefault {
|
||||
background-color: @btn-bg;
|
||||
color: @btn-txt;
|
||||
border-color: @btn-bord;
|
||||
border-color: @b7grey;
|
||||
}
|
||||
|
||||
.List-buttonDefault:hover,
|
||||
@ -206,6 +212,11 @@ table, tbody {
|
||||
color: @btn-txt;
|
||||
}
|
||||
|
||||
.List-buttonDefault[disabled] {
|
||||
color: @d7grey;
|
||||
border-color: @d7grey;
|
||||
}
|
||||
|
||||
.List-searchDropdown {
|
||||
border-top-left-radius: 5px!important;
|
||||
border-bottom-left-radius: 5px!important;
|
||||
|
||||
@ -93,6 +93,10 @@ body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.Panel-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn{
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@ -75,9 +75,6 @@ export default [
|
||||
if(key === "AD_HOC_COMMANDS"){
|
||||
$scope[key] = data[key].toString();
|
||||
}
|
||||
else if(key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH"){
|
||||
$scope[key] = JSON.stringify(data[key]);
|
||||
}
|
||||
else {
|
||||
$scope[key] = ConfigurationUtils.arrayToList(data[key], key);
|
||||
}
|
||||
@ -356,38 +353,27 @@ export default [
|
||||
clearApiErrors();
|
||||
_.each(keys, function(key) {
|
||||
if($scope.configDataResolve[key].type === 'choice' || multiselectDropdowns.indexOf(key) !== -1) {
|
||||
|
||||
// Handle AD_HOC_COMMANDS
|
||||
if(multiselectDropdowns.indexOf(key) !== -1) {
|
||||
let newModules = $("#configuration_jobs_template_AD_HOC_COMMANDS > option")
|
||||
.filter("[data-select2-tag=true]")
|
||||
.map((i, val) => ({value: $(val).text()}));
|
||||
newModules.each(function(i, val) {
|
||||
$scope[key].push(val);
|
||||
});
|
||||
|
||||
payload[key] = ConfigurationUtils.listToArray(_.map($scope[key], 'value').join(','));
|
||||
}
|
||||
|
||||
//Parse dropdowns and dropdowns labeled as lists
|
||||
else if($scope[key] === null) {
|
||||
if($scope[key] === null) {
|
||||
payload[key] = null;
|
||||
} else if($scope[key][0] && $scope[key][0].value !== undefined) {
|
||||
payload[key] = _.map($scope[key], 'value').join(',');
|
||||
if(multiselectDropdowns.indexOf(key) !== -1) {
|
||||
// Handle AD_HOC_COMMANDS
|
||||
payload[key] = ConfigurationUtils.listToArray(_.map($scope[key], 'value').join(','));
|
||||
} else {
|
||||
payload[key] = _.map($scope[key], 'value').join(',');
|
||||
}
|
||||
} else {
|
||||
payload[key] = $scope[key].value;
|
||||
if(multiselectDropdowns.indexOf(key) !== -1) {
|
||||
// Default AD_HOC_COMMANDS to an empty list
|
||||
payload[key] = $scope[key].value || [];
|
||||
} else {
|
||||
payload[key] = $scope[key].value;
|
||||
}
|
||||
}
|
||||
} else if($scope.configDataResolve[key].type === 'list' && $scope[key] !== null) {
|
||||
|
||||
if(key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH"){
|
||||
payload[key] = $scope[key] === "{}" ? [] : ToJSON($scope.parseType,
|
||||
$scope[key]);
|
||||
}
|
||||
else {
|
||||
// Parse lists
|
||||
payload[key] = ConfigurationUtils.listToArray($scope[key], key);
|
||||
}
|
||||
|
||||
// Parse lists
|
||||
payload[key] = ConfigurationUtils.listToArray($scope[key], key);
|
||||
}
|
||||
else if($scope.configDataResolve[key].type === 'nested object') {
|
||||
if($scope[key] === '') {
|
||||
|
||||
@ -49,9 +49,9 @@ function InventoriesAdd($scope, $location,
|
||||
$scope.parseType = 'yaml';
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
variable: 'variables',
|
||||
variable: 'inventory_variables',
|
||||
parse_variable: 'parseType',
|
||||
field_id: 'inventory_variables'
|
||||
field_id: 'inventory_inventory_variables'
|
||||
});
|
||||
}
|
||||
|
||||
@ -59,9 +59,7 @@ function InventoriesAdd($scope, $location,
|
||||
$scope.formSave = function() {
|
||||
Wait('start');
|
||||
try {
|
||||
var fld, json_data, data;
|
||||
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
var fld, data;
|
||||
|
||||
data = {};
|
||||
for (fld in form.fields) {
|
||||
@ -77,7 +75,7 @@ function InventoriesAdd($scope, $location,
|
||||
.success(function(data) {
|
||||
var inventory_id = data.id;
|
||||
Wait('stop');
|
||||
$location.path('/inventories/' + inventory_id + '/manage');
|
||||
$state.go('inventories.edit', {inventory_id: inventory_id}, {reload: true});
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors($scope, data, status, form, {
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
import controller from './inventory-add.controller';
|
||||
|
||||
export default
|
||||
angular.module('inventoryAdd', [])
|
||||
angular.module('InventoryAdd', [])
|
||||
.controller('InventoryAddController', controller);
|
||||
|
||||
@ -169,7 +169,7 @@ function adhocController($q, $scope, $stateParams,
|
||||
ParseTypeChange({ scope: $scope, field_id: 'adhoc_extra_vars' , variable: "extra_vars"});
|
||||
|
||||
$scope.formCancel = function(){
|
||||
$state.go('inventoryManage');
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
// remove all data input into the form and reset the form back to defaults
|
||||
1
awx/ui/client/src/inventories/adhoc/adhoc.partial.html
Normal file
1
awx/ui/client/src/inventories/adhoc/adhoc.partial.html
Normal file
@ -0,0 +1 @@
|
||||
<div ng-cloak id="htmlTemplate"></div>
|
||||
@ -4,8 +4,8 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../../shared/template-url/template-url.factory';
|
||||
import { N_ } from '../../../i18n';
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
import { N_ } from '../../i18n';
|
||||
|
||||
export default {
|
||||
url: '/adhoc',
|
||||
@ -15,10 +15,10 @@ export default {
|
||||
squash: true
|
||||
}
|
||||
},
|
||||
name: 'inventoryManage.adhoc',
|
||||
name: 'inventories.edit.adhoc',
|
||||
views: {
|
||||
'form@inventoryManage': {
|
||||
templateUrl: templateUrl('inventories/manage/adhoc/adhoc'),
|
||||
'adhocForm@inventories': {
|
||||
templateUrl: templateUrl('inventories/adhoc/adhoc'),
|
||||
controller: 'adhocController'
|
||||
}
|
||||
},
|
||||
@ -0,0 +1,27 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
function AnsibleFacts($scope, Facts, ParseTypeChange, ParseVariableString) {
|
||||
|
||||
function init() {
|
||||
$scope.facts = ParseVariableString(Facts.data);
|
||||
|
||||
$scope.parseType = 'yaml';
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
variable: 'facts',
|
||||
parse_variable: 'parseType',
|
||||
field_id: 'host_facts',
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
export default ['$scope', 'Facts', 'ParseTypeChange', 'ParseVariableString', AnsibleFacts
|
||||
];
|
||||
@ -0,0 +1,10 @@
|
||||
<form class="Form ng-pristine ng-valid ng-valid-required" name="host_form" id="host_form" autocomplete="off" novalidate="">
|
||||
<div class="form-group Form-formGroup Form-formGroup--fullWidth">
|
||||
<label class="Form-inputLabelContainer " for="facts">
|
||||
<span class="Form-inputLabel">FACTS</span>
|
||||
</label>
|
||||
<div>
|
||||
<textarea rows="6" ng-model="facts" name="facts" class="form-control Form-textArea Form-formGroup--fullWidth" id="host_facts"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
11
awx/ui/client/src/inventories/ansible_facts/main.js
Normal file
11
awx/ui/client/src/inventories/ansible_facts/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './ansible_facts.controller';
|
||||
|
||||
export default
|
||||
angular.module('AnsibleFacts', [])
|
||||
.controller('AnsibleFactsController', controller);
|
||||
@ -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;
|
||||
}
|
||||
];
|
||||
@ -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'
|
||||
}
|
||||
}
|
||||
};}];
|
||||
13
awx/ui/client/src/inventories/completed_jobs/main.js
Normal file
13
awx/ui/client/src/inventories/completed_jobs/main.js
Normal 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);
|
||||
@ -0,0 +1,72 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$scope', '$state', '$stateParams', 'GroupManageService', 'CopyMoveGroupList', 'group', 'Dataset',
|
||||
function($scope, $state, $stateParams, GroupManageService, CopyMoveGroupList, group, Dataset){
|
||||
var list = CopyMoveGroupList;
|
||||
|
||||
$scope.item = group;
|
||||
$scope.submitMode = $stateParams.groups === undefined ? 'move' : 'copy';
|
||||
$scope.toggle_row = function(id){
|
||||
// toggle off anything else currently selected
|
||||
_.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;});
|
||||
// yoink the currently selected thing
|
||||
$scope.selected = _.find($scope.groups, (item) => {return item.id === id;});
|
||||
};
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^');
|
||||
};
|
||||
$scope.formSave = function(){
|
||||
switch($scope.submitMode) {
|
||||
case 'copy':
|
||||
GroupManageService.associateGroup(group, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
|
||||
break;
|
||||
case 'move':
|
||||
switch($scope.targetRootGroup){
|
||||
case true:
|
||||
// disassociating group will bubble it to the root group level
|
||||
GroupManageService.disassociateGroup(group.id, _.last($stateParams.group)).then(() => $state.go('^', null, {reload: true}));
|
||||
break;
|
||||
default:
|
||||
// at the root group level, no dissassociation is needed
|
||||
if (!$stateParams.group){
|
||||
GroupManageService.associateGroup(group, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
|
||||
}
|
||||
else{
|
||||
// unsure if orphaned resources get garbage collected, safe bet is to associate before disassociate
|
||||
GroupManageService.associateGroup(group, $scope.selected.id).then(() => {
|
||||
GroupManageService.disassociateGroup(group.id, _.last($stateParams.group))
|
||||
.then(() => $state.go('^', null, {reload: true}));
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
$scope.toggleTargetRootGroup = function(){
|
||||
$scope.selected = !$scope.selected;
|
||||
// cannot perform copy operations to root group level
|
||||
$scope.submitMode = 'move';
|
||||
// toggle off anything currently selected in the list, for clarity
|
||||
_.forEach($scope.groups, (item) => {item.checked = null;});
|
||||
// disable list selections
|
||||
$('#copyMove-list :input').each((idx, el) => {
|
||||
$(el).prop('disabled', (idx, value) => !value);
|
||||
});
|
||||
};
|
||||
|
||||
function init(){
|
||||
$scope.atRootLevel = $stateParams.group ? false : true;
|
||||
|
||||
// search init
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
|
||||
init();
|
||||
}];
|
||||
@ -20,5 +20,5 @@ export default {
|
||||
label: 'Target Group Name'
|
||||
}
|
||||
},
|
||||
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/groups'
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/groups'
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$scope', '$state', '$stateParams', 'HostManageService', 'CopyMoveGroupList', 'host', 'Dataset',
|
||||
function($scope, $state, $stateParams, HostManageService, CopyMoveGroupList, host, Dataset){
|
||||
var list = CopyMoveGroupList;
|
||||
|
||||
$scope.item = host;
|
||||
$scope.submitMode = 'copy';
|
||||
$scope.toggle_row = function(id){
|
||||
// toggle off anything else currently selected
|
||||
_.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;});
|
||||
// yoink the currently selected thing
|
||||
$scope.selected = _.find($scope.groups, (item) => {return item.id === id;});
|
||||
};
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^');
|
||||
};
|
||||
$scope.formSave = function(){
|
||||
switch($scope.submitMode) {
|
||||
case 'copy':
|
||||
HostManageService.associateGroup(host, $scope.selected.id).then(() => $state.go('^'));
|
||||
break;
|
||||
case 'move':
|
||||
// at the root group level, no dissassociation is needed
|
||||
if (!$stateParams.group){
|
||||
HostManageService.associateGroup(host, $scope.selected.id).then(() => $state.go('^', null, {reload: true}));
|
||||
}
|
||||
else{
|
||||
HostManageService.associateGroup(host, $scope.selected.id).then(() => {
|
||||
HostManageService.disassociateGroup(host, _.last($stateParams.group))
|
||||
.then(() => $state.go('^', null, {reload: true}));
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
var init = function(){
|
||||
// search init
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
|
||||
};
|
||||
init();
|
||||
}];
|
||||
98
awx/ui/client/src/inventories/copy-move/copy-move.route.js
Normal file
98
awx/ui/client/src/inventories/copy-move/copy-move.route.js
Normal file
@ -0,0 +1,98 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
import { N_ } from '../../i18n';
|
||||
|
||||
import CopyMoveGroupsController from './copy-move-groups.controller';
|
||||
import CopyMoveHostsController from './copy-move-hosts.controller';
|
||||
|
||||
var copyMoveGroupRoute = {
|
||||
name: 'inventories.edit.groups.copyMoveGroup',
|
||||
url: '/copy-move-group/{group_id:int}',
|
||||
searchPrefix: 'copy',
|
||||
data: {
|
||||
group_id: 'group_id',
|
||||
},
|
||||
params: {
|
||||
copy_search: {
|
||||
value: {
|
||||
not__id__in: null
|
||||
},
|
||||
dynamic: true,
|
||||
squash: ''
|
||||
}
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
label: N_("COPY OR MOVE") + " {{item.name}}"
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath', 'group',
|
||||
function(list, qs, $stateParams, GetBasePath, group) {
|
||||
$stateParams.copy_search.not__id__in = ($stateParams.group && $stateParams.group.length > 0 ? group.id + ',' + _.last($stateParams.group) : group.id.toString());
|
||||
let path = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/';
|
||||
return qs.search(path, $stateParams.copy_search);
|
||||
}
|
||||
],
|
||||
group: ['GroupManageService', '$stateParams', function(GroupManageService, $stateParams){
|
||||
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
|
||||
}]
|
||||
},
|
||||
views: {
|
||||
'copyMove@inventories' : {
|
||||
controller: CopyMoveGroupsController,
|
||||
templateUrl: templateUrl('inventories/copy-move/copy-move'),
|
||||
},
|
||||
'copyMoveList@inventories.edit.groups.copyMoveGroup': {
|
||||
templateProvider: function(CopyMoveGroupList, generateList) {
|
||||
let html = generateList.build({
|
||||
list: CopyMoveGroupList,
|
||||
mode: 'lookup',
|
||||
input_type: 'radio'
|
||||
});
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var copyMoveHostRoute = {
|
||||
name: 'inventories.edit.hosts.copyMoveHost',
|
||||
url: '/copy-move-host/{host_id}',
|
||||
searchPrefix: 'copy',
|
||||
ncyBreadcrumb: {
|
||||
label: N_("COPY OR MOVE") + " {{item.name}}"
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['CopyMoveGroupList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/';
|
||||
return qs.search(path, $stateParams.copy_search);
|
||||
}
|
||||
],
|
||||
host: ['HostManageService', '$stateParams', function(HostManageService, $stateParams){
|
||||
return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]);
|
||||
}]
|
||||
},
|
||||
views: {
|
||||
'copyMove@inventories': {
|
||||
templateUrl: templateUrl('inventories/copy-move/copy-move'),
|
||||
controller: CopyMoveHostsController,
|
||||
},
|
||||
'copyMoveList@inventories.edit.hosts.copyMoveHost': {
|
||||
templateProvider: function(CopyMoveGroupList, generateList, $stateParams, GetBasePath) {
|
||||
let list = CopyMoveGroupList;
|
||||
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/groups/';
|
||||
let html = generateList.build({
|
||||
list: CopyMoveGroupList,
|
||||
mode: 'lookup',
|
||||
input_type: 'radio'
|
||||
});
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {copyMoveGroupRoute, copyMoveHostRoute};
|
||||
@ -20,7 +20,7 @@ function InventoriesEdit($scope, $location,
|
||||
form = InventoryForm,
|
||||
inventory_id = $stateParams.inventory_id,
|
||||
master = {},
|
||||
fld, json_data, data;
|
||||
fld, data;
|
||||
|
||||
ClearScope();
|
||||
init();
|
||||
@ -45,9 +45,9 @@ function InventoriesEdit($scope, $location,
|
||||
.success(function(data) {
|
||||
var fld;
|
||||
for (fld in form.fields) {
|
||||
if (fld === 'variables') {
|
||||
$scope.variables = ParseVariableString(data.variables);
|
||||
master.variables = $scope.variables;
|
||||
if (fld === 'inventory_variables') {
|
||||
$scope.inventory_variables = ParseVariableString(data.variables);
|
||||
master.inventory_variables = $scope.variables;
|
||||
} else if (fld === 'inventory_name') {
|
||||
$scope[fld] = data.name;
|
||||
master[fld] = $scope[fld];
|
||||
@ -71,9 +71,9 @@ function InventoriesEdit($scope, $location,
|
||||
$scope.parseType = 'yaml';
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
variable: 'variables',
|
||||
variable: 'inventory_variables',
|
||||
parse_variable: 'parseType',
|
||||
field_id: 'inventory_variables'
|
||||
field_id: 'inventory_inventory_variables'
|
||||
});
|
||||
|
||||
OrgAdminLookup.checkForAdminAccess({organization: data.organization})
|
||||
@ -83,8 +83,6 @@ function InventoriesEdit($scope, $location,
|
||||
|
||||
$scope.inventory_obj = data;
|
||||
$scope.name = data.name;
|
||||
|
||||
$scope.$emit('inventoryLoaded');
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
@ -96,9 +94,6 @@ function InventoriesEdit($scope, $location,
|
||||
$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) {
|
||||
@ -122,10 +117,6 @@ function InventoriesEdit($scope, $location,
|
||||
});
|
||||
};
|
||||
|
||||
$scope.manageInventory = function() {
|
||||
$location.path($location.path() + '/manage');
|
||||
};
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('inventories');
|
||||
};
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
import controller from './inventory-edit.controller';
|
||||
|
||||
export default
|
||||
angular.module('inventoryEdit', [])
|
||||
angular.module('InventoryEdit', [])
|
||||
.controller('InventoryEditController', controller);
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import GroupAddController from './groups-add.controller';
|
||||
|
||||
export default ['$stateExtender', 'templateUrl', '$injector',
|
||||
function($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 = {
|
||||
name: `${formStateDefinition.name}.${list.iterator}s.add`,
|
||||
url: `/add`,
|
||||
ncyBreadcrumb: {
|
||||
parent: `${formStateDefinition.name}`,
|
||||
label: `${breadcrumbLabel}`
|
||||
},
|
||||
views: {
|
||||
'groupForm@inventories': {
|
||||
templateProvider: function(GenerateForm, GroupForm) {
|
||||
let form = GroupForm;
|
||||
return GenerateForm.buildHTML(form, {
|
||||
mode: 'add',
|
||||
related: false
|
||||
});
|
||||
},
|
||||
controller: GroupAddController
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
'FormDefinition': [params.form, function(definition) {
|
||||
return definition;
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
state = $stateExtender.buildDefinition(stateConfig);
|
||||
return state;
|
||||
};
|
||||
return val;
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,62 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$state', '$stateParams', '$scope', 'GroupForm',
|
||||
'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupManageService',
|
||||
'GetChoices', 'GetBasePath', 'CreateSelect2',
|
||||
'rbacUiControlService', 'ToJSON',
|
||||
function($state, $stateParams, $scope, GroupForm, ParseTypeChange,
|
||||
GenerateForm, inventoryData, GroupManageService, GetChoices,
|
||||
GetBasePath, CreateSelect2, rbacUiControlService,
|
||||
ToJSON) {
|
||||
|
||||
let form = GroupForm;
|
||||
init();
|
||||
|
||||
function init() {
|
||||
// apply form definition's default field values
|
||||
GenerateForm.applyDefaults(form, $scope);
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.envParseType = 'yaml';
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'group_variables',
|
||||
variable: 'variables',
|
||||
});
|
||||
}
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
$scope.formSave = function() {
|
||||
var json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
|
||||
var group = {
|
||||
variables: json_data,
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
inventory: inventoryData.id
|
||||
};
|
||||
|
||||
GroupManageService.post(group).then(res => {
|
||||
if ($stateParams.group_id) {
|
||||
return GroupManageService.associateGroup(res.data, $stateParams.group_id)
|
||||
.then(() => $state.go('^', null, { reload: true }));
|
||||
} else {
|
||||
$state.go('^.edit', { group_id: res.data.id }, { reload: true });
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
];
|
||||
13
awx/ui/client/src/inventories/groups/add/main.js
Normal file
13
awx/ui/client/src/inventories/groups/add/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildGroupAddState from './build-groups-add-state.factory';
|
||||
import controller from './groups-add.controller';
|
||||
|
||||
export default
|
||||
angular.module('groupAdd', [])
|
||||
.factory('buildGroupsAddState', buildGroupAddState)
|
||||
.controller('GroupAddController', controller);
|
||||
@ -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;
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,58 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbacUiControlService', 'ToJSON',
|
||||
'ParseTypeChange', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'groupData',
|
||||
function($state, $stateParams, $scope, ParseVariableString, rbacUiControlService, ToJSON,
|
||||
ParseTypeChange, GroupManageService, GetChoices, GetBasePath, CreateSelect2, groupData) {
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
$scope = angular.extend($scope, groupData);
|
||||
|
||||
$scope.$watch('summary_fields.user_capabilities.edit', function(val) {
|
||||
$scope.canAdd = val;
|
||||
});
|
||||
|
||||
// init codemirror(s)
|
||||
$scope.variables = $scope.variables === null || $scope.variables === '' ? '---' : ParseVariableString($scope.variables);
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.envParseType = 'yaml';
|
||||
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'group_variables',
|
||||
variable: 'variables',
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
$scope.formSave = function() {
|
||||
var json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
// group fields
|
||||
var group = {
|
||||
variables: json_data,
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
inventory: $scope.inventory,
|
||||
id: groupData.id
|
||||
};
|
||||
GroupManageService.put(group).then(() => $state.go($state.current, null, { reload: true }));
|
||||
};
|
||||
|
||||
}
|
||||
];
|
||||
13
awx/ui/client/src/inventories/groups/edit/main.js
Normal file
13
awx/ui/client/src/inventories/groups/edit/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildGroupsEditState from './build-groups-edit-state.factory';
|
||||
import controller from './groups-edit.controller';
|
||||
|
||||
export default
|
||||
angular.module('groupEdit', [])
|
||||
.factory('buildGroupsEditState', buildGroupsEditState)
|
||||
.controller('GroupEditController', controller);
|
||||
106
awx/ui/client/src/inventories/groups/groups.form.js
Normal file
106
awx/ui/client/src/inventories/groups/groups.form.js
Normal file
@ -0,0 +1,106 @@
|
||||
/*************************************************
|
||||
* 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', 'nestedHostsListState',
|
||||
'buildHostAddState',
|
||||
function(i18n, nestedGroupListState, nestedHostsListState, buildHostAddState){
|
||||
return {
|
||||
addTitle: 'CREATE GROUP',
|
||||
editTitle: '{{ name }}',
|
||||
showTitle: true,
|
||||
name: '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 />  \"somevar\": \"somevalue\",<br /> \"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: 'nested_groups',
|
||||
ngClick: "$state.go('inventories.edit.groups.edit.nested_groups')",
|
||||
include: "NestedGroupListDefinition",
|
||||
includeForm: "NestedGroupFormDefinition",
|
||||
title: i18n._('Groups'),
|
||||
iterator: 'nested_group',
|
||||
listState: nestedGroupListState
|
||||
},
|
||||
nested_hosts: {
|
||||
name: 'nested_hosts',
|
||||
ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts')",
|
||||
include: "NestedHostsListDefinition",
|
||||
title: i18n._('Hosts'),
|
||||
iterator: 'nested_hosts',
|
||||
listState: nestedHostsListState,
|
||||
addState: buildHostAddState,
|
||||
// editState: buildGroupsEditState
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -1,5 +1,5 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
@ -8,30 +8,15 @@ 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,
|
||||
wellOverride: true,
|
||||
index: false,
|
||||
hover: true,
|
||||
'class': 'table-no-border',
|
||||
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,
|
||||
@ -39,24 +24,15 @@ export default {
|
||||
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',
|
||||
},
|
||||
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'}}"
|
||||
}
|
||||
},
|
||||
|
||||
@ -71,11 +47,10 @@ export default {
|
||||
},
|
||||
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",
|
||||
ngDisabled: '!groupsSelected',
|
||||
ngClick: 'setAdhocPattern()',
|
||||
awToolTip: "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or a selection of multiple groups.",
|
||||
dataPlacement: 'top',
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'RUN COMMANDS',
|
||||
showTipWhenDisabled: true,
|
||||
@ -102,28 +77,28 @@ export default {
|
||||
|
||||
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"
|
||||
},
|
||||
// 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)",
|
||||
@ -131,14 +106,14 @@ export default {
|
||||
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 === '')"
|
||||
},
|
||||
// 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',
|
||||
@ -0,0 +1,82 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
import GroupsListController from './groups-list.controller';
|
||||
export default ['GroupList', '$stateExtender', 'templateUrl', '$injector',
|
||||
function(GroupList, $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: {
|
||||
[list.iterator + '_search']: {
|
||||
value: { order_by: field.order_by ? field.order_by : 'name' }
|
||||
},
|
||||
},
|
||||
views: {
|
||||
'related': {
|
||||
templateProvider: function(GroupList, generateList, $templateRequest, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(GroupList);
|
||||
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);
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
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,180 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$rootScope', '$state', '$stateParams', 'GroupList', 'InventoryUpdate',
|
||||
'GroupManageService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
||||
'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
|
||||
function($scope, $rootScope, $state, $stateParams, GroupList, InventoryUpdate,
|
||||
GroupManageService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
||||
GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
|
||||
|
||||
let list = GroupList;
|
||||
|
||||
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 === "inventories.edit.groups") {
|
||||
$scope.rowBeingEdited = $state.params.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
|
||||
$scope.$on('selectedOrDeselected', function(e, value) {
|
||||
let item = value.value;
|
||||
|
||||
if (value.isSelected) {
|
||||
if(!$scope.groupsSelected) {
|
||||
$scope.groupsSelected = [];
|
||||
}
|
||||
$scope.groupsSelected.push(item);
|
||||
} else {
|
||||
_.remove($scope.groupsSelected, { id: item.id });
|
||||
if($scope.groupsSelected.length === 0) {
|
||||
$scope.groupsSelected = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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.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('.', 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("^", 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("^", 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.cancelUpdate = function (id) {
|
||||
CancelSourceUpdate({ scope: $scope, id: id });
|
||||
};
|
||||
|
||||
$scope.copyMoveGroup = function(id){
|
||||
$state.go('inventories.edit.groups.copyMoveGroup', {group_id: id, groups: $stateParams.groups});
|
||||
};
|
||||
|
||||
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
|
||||
if (toState.name === "inventories.edit.groups.edit") {
|
||||
$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();
|
||||
});
|
||||
|
||||
$scope.setAdhocPattern = function(){
|
||||
var pattern = _($scope.groupsSelected)
|
||||
.map(function(item){
|
||||
return item.name;
|
||||
}).value().join(':');
|
||||
|
||||
$state.go('^.adhoc', {pattern: pattern});
|
||||
};
|
||||
|
||||
}];
|
||||
13
awx/ui/client/src/inventories/groups/list/main.js
Normal file
13
awx/ui/client/src/inventories/groups/list/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildGroupsListState from './build-groups-list-state.factory';
|
||||
import controller from './groups-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('groupsList', [])
|
||||
.factory('buildGroupsListState', buildGroupsListState)
|
||||
.controller('GroupsListController', controller);
|
||||
28
awx/ui/client/src/inventories/groups/main.js
Normal file
28
awx/ui/client/src/inventories/groups/main.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import groupList from './list/main';
|
||||
import groupAdd from './add/main';
|
||||
import groupEdit from './edit/main';
|
||||
import nestedGroups from './nested-groups/main';
|
||||
import nestedHosts from './nested-hosts/main';
|
||||
import groupFormDefinition from './groups.form';
|
||||
import groupListDefinition from './groups.list';
|
||||
import service from './groups.service';
|
||||
import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory';
|
||||
|
||||
export default
|
||||
angular.module('group', [
|
||||
groupList.name,
|
||||
groupAdd.name,
|
||||
groupEdit.name,
|
||||
nestedGroups.name,
|
||||
nestedHosts.name
|
||||
])
|
||||
.factory('GroupForm', groupFormDefinition)
|
||||
.value('GroupList', groupListDefinition)
|
||||
.factory('GetHostsStatusMsg', GetHostsStatusMsg)
|
||||
.service('GroupManageService', service);
|
||||
19
awx/ui/client/src/inventories/groups/nested-groups/main.js
Normal file
19
awx/ui/client/src/inventories/groups/nested-groups/main.js
Normal file
@ -0,0 +1,19 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import nestedGroupListState from './nested-groups-list-state.factory';
|
||||
import nestedGroupAddState from './nested-groups-add-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)
|
||||
.factory('nestedGroupAddState', nestedGroupAddState)
|
||||
.value('NestedGroupListDefinition', nestedGroupListDefinition)
|
||||
.factory('NestedGroupFormDefinition', nestedGroupFormDefinition)
|
||||
.controller('NestedGroupsListController', controller);
|
||||
@ -0,0 +1,46 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import GroupAddController from '../add/groups-add.controller';
|
||||
|
||||
export default ['$stateExtender', 'templateUrl', '$injector',
|
||||
function($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 = {
|
||||
name: `${formStateDefinition.name}.${list.iterator}s.add`,
|
||||
url: `/add`,
|
||||
ncyBreadcrumb: {
|
||||
parent: `${formStateDefinition.name}`,
|
||||
label: `${breadcrumbLabel}`
|
||||
},
|
||||
views: {
|
||||
'nestedGroupForm@inventories': {
|
||||
templateProvider: function(GenerateForm, GroupForm) {
|
||||
let form = GroupForm;
|
||||
return GenerateForm.buildHTML(form, {
|
||||
mode: 'add',
|
||||
related: false
|
||||
});
|
||||
},
|
||||
controller: GroupAddController
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
'FormDefinition': [params.form, function(definition) {
|
||||
return definition;
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
state = $stateExtender.buildDefinition(stateConfig);
|
||||
return state;
|
||||
};
|
||||
return val;
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,92 @@
|
||||
/*************************************************
|
||||
* 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`]);
|
||||
}
|
||||
],
|
||||
host: ['$stateParams', 'HostManageService', function($stateParams, HostManageService) {
|
||||
if($stateParams.host_id){
|
||||
return HostManageService.get({ id: $stateParams.host_id }).then(function(res) {
|
||||
return res.data.results[0];
|
||||
});
|
||||
}
|
||||
}],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', 'host', function(InventoryManageService, $stateParams, host) {
|
||||
var id = ($stateParams.inventory_id) ? $stateParams.inventory_id : host.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;
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,173 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$rootScope', '$state', '$stateParams', 'NestedGroupListDefinition', 'InventoryUpdate',
|
||||
'GroupManageService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
||||
'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
|
||||
function($scope, $rootScope, $state, $stateParams, NestedGroupListDefinition, InventoryUpdate,
|
||||
GroupManageService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
||||
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 === "inventories.edit.groups.edit.nested_groups.edit") {
|
||||
$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.createGroup = function(){
|
||||
$state.go('inventories.edit.groups.edit.nested_groups.add');
|
||||
};
|
||||
$scope.editGroup = function(id){
|
||||
$state.go('inventories.edit.groups.edit', {group_id: id});
|
||||
};
|
||||
// $scope.editGroup = function(id){
|
||||
// $state.go('inventories.edit.groups.edit.nested_groups.edit', {nested_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('.', 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("^", 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("^", 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.cancelUpdate = function (id) {
|
||||
CancelSourceUpdate({ scope: $scope, id: id });
|
||||
};
|
||||
|
||||
// $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(){
|
||||
// TODO: implement
|
||||
};
|
||||
|
||||
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
|
||||
if (toState.name === "inventories.edit.groups.edit.nested_groups.edit") {
|
||||
$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();
|
||||
});
|
||||
|
||||
$scope.setAdhocPattern = function(){
|
||||
var pattern = _($scope.groupsSelected)
|
||||
.map(function(item){
|
||||
return item.name;
|
||||
}).value().join(':');
|
||||
|
||||
$state.go('^.adhoc', {pattern: pattern});
|
||||
};
|
||||
|
||||
}];
|
||||
@ -0,0 +1,96 @@
|
||||
/*************************************************
|
||||
* 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.edit.nested_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.nested_groups.edit',
|
||||
detailsClick: "$state.go('inventories.edit.groups.edit.nested_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 />  \"somevar\": \"somevalue\",<br /> \"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
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -0,0 +1,144 @@
|
||||
/*************************************************
|
||||
* 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: "{{ nested_group.hosts_status_tip }}",
|
||||
dataPlacement: "top",
|
||||
icon: "{{ 'fa icon-job-' + nested_group.hosts_status_class }}",
|
||||
columnClass: 'status-column List-staticColumn--smallStatus'
|
||||
},
|
||||
name: {
|
||||
label: 'Groups',
|
||||
key: true,
|
||||
|
||||
// ngClick: "groupSelect(group.id)",
|
||||
ngClick: "editGroup(nested_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',
|
||||
ngDisabled: '!groupsSelected',
|
||||
ngClick: '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.",
|
||||
dataPlacement: 'top',
|
||||
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(nested_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(nested_group.id)",
|
||||
awToolTip: 'Edit group',
|
||||
dataPlacement: "top",
|
||||
ngShow: "nested_group.summary_fields.user_capabilities.edit"
|
||||
},
|
||||
view: {
|
||||
//label: 'Edit',
|
||||
mode: 'all',
|
||||
ngClick: "editGroup(nested_group.id)",
|
||||
awToolTip: 'View group',
|
||||
dataPlacement: "top",
|
||||
ngShow: "!nested_group.summary_fields.user_capabilities.edit"
|
||||
},
|
||||
"delete": {
|
||||
//label: 'Delete',
|
||||
mode: 'all',
|
||||
ngClick: "deleteGroup(nested_group)",
|
||||
awToolTip: 'Delete group',
|
||||
dataPlacement: "top",
|
||||
ngShow: "group.summary_fields.user_capabilities.delete"
|
||||
}
|
||||
}
|
||||
};
|
||||
17
awx/ui/client/src/inventories/groups/nested-hosts/main.js
Normal file
17
awx/ui/client/src/inventories/groups/nested-hosts/main.js
Normal file
@ -0,0 +1,17 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import nestedHostsListState from './nested-hosts-list-state.factory';
|
||||
import nestedHostsListDefinition from './nested-hosts.list';
|
||||
import nestedHostsFormDefinition from './nested-hosts.form';
|
||||
import controller from './nested-hosts-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('nestedHosts', [])
|
||||
.factory('nestedHostsListState', nestedHostsListState)
|
||||
.value('NestedHostsListDefinition', nestedHostsListDefinition)
|
||||
.factory('NestedHostsFormDefinition', nestedHostsFormDefinition)
|
||||
.controller('NestedHostsListController', controller);
|
||||
@ -0,0 +1,79 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
import NestedHostsListController from './nested-hosts-list.controller';
|
||||
export default ['NestedHostsListDefinition', '$stateExtender', 'templateUrl', '$injector',
|
||||
function(NestedHostsListDefinition, $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_hosts`,
|
||||
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(NestedHostsListDefinition, generateList) {
|
||||
let list = _.cloneDeep(NestedHostsListDefinition);
|
||||
|
||||
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: NestedHostsListController
|
||||
}
|
||||
},
|
||||
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 });
|
||||
}
|
||||
path = `api/v2/groups/${$stateParams.group_id}/all_hosts`;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return InventoryManageService.getInventory($stateParams.inventory_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;
|
||||
}
|
||||
];
|
||||
@ -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});
|
||||
};
|
||||
}];
|
||||
@ -0,0 +1,129 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name forms.function:Hosts
|
||||
* @description This form is for adding/editing a host on the inventory page
|
||||
*/
|
||||
|
||||
export default ['i18n', 'nestedGroupListState',
|
||||
function(i18n, nestedGroupListState) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('CREATE HOST'),
|
||||
editTitle: '{{ host.name }}',
|
||||
name: 'host',
|
||||
basePath: 'hosts',
|
||||
well: false,
|
||||
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',
|
||||
ngClick: 'toggleHostEnabled(host)',
|
||||
type: 'toggle',
|
||||
awToolTip: "<p>" +
|
||||
i18n._("Indicates if a host is available and should be included in running jobs.") +
|
||||
"</p><p>" +
|
||||
i18n._("For hosts that are part of an external" +
|
||||
" inventory, this flag cannot be changed. It will be" +
|
||||
" set by the inventory sync process.") +
|
||||
"</p>",
|
||||
dataTitle: i18n._('Host Enabled'),
|
||||
ngDisabled: 'host.has_inventory_sources'
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
label: i18n._('Host Name'),
|
||||
type: 'text',
|
||||
required: true,
|
||||
awPopOver: "<p>" +
|
||||
i18n._("Provide a host name, ip address, or ip address:port. Examples include:") +
|
||||
"</p>" +
|
||||
"<blockquote>myserver.domain.com<br/>" +
|
||||
"127.0.0.1<br />" +
|
||||
"10.1.0.140:25<br />" +
|
||||
"server.example.com:25" +
|
||||
"</blockquote>",
|
||||
dataTitle: i18n._('Host Name'),
|
||||
dataPlacement: 'right',
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
description: {
|
||||
label: i18n._('Description'),
|
||||
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)',
|
||||
type: 'text'
|
||||
},
|
||||
variables: {
|
||||
label: i18n._('Variables'),
|
||||
type: 'textarea',
|
||||
rows: 6,
|
||||
class: 'Form-formGroup--fullWidth',
|
||||
"default": "---",
|
||||
awPopOver: "<p>" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>' + i18n.sprintf(i18n._('View JSON examples at %s'), '<a href="http://www.json.org" target="_blank">www.json.org</a>') + '</p>' +
|
||||
'<p>' + i18n.sprintf(i18n._('View YAML examples at %s'), '<a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a>') + '</p>',
|
||||
dataTitle: i18n._('Host Variables'),
|
||||
dataPlacement: 'right',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
inventory: {
|
||||
type: 'hidden',
|
||||
includeOnEdit: true,
|
||||
includeOnAdd: true
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true,
|
||||
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -0,0 +1,131 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default {
|
||||
name: 'nested_hosts',
|
||||
iterator: 'nested_host',
|
||||
editTitle: '{{ nested_host.name }}', // i don't think this is correct
|
||||
// showTitle: false,
|
||||
well: true,
|
||||
wellOverride: true,
|
||||
index: false,
|
||||
hover: true,
|
||||
// hasChildren: true,
|
||||
multiSelect: true,
|
||||
trackBy: 'nested_host.id',
|
||||
basePath: 'api/v2/groups/{{$stateParams.group_id}}/all_hosts/',
|
||||
|
||||
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: "{{ nested_host.job_status_html }}",
|
||||
dataTitle: "{{ nested_host.job_status_title }}",
|
||||
awToolTip: "{{ nested_host.badgeToolTip }}",
|
||||
dataPlacement: 'top',
|
||||
icon: "{{ 'fa icon-job-' + nested_host.active_failures }}",
|
||||
id: 'active-failures-action',
|
||||
columnClass: 'status-column List-staticColumn--smallStatus'
|
||||
},
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Hosts',
|
||||
ngClick: "editHost(nested_host.id)",
|
||||
ngClass: "{ 'host-disabled-label': !nested_host.enabled }",
|
||||
columnClass: 'col-lg-6 col-md-8 col-sm-8 col-xs-7',
|
||||
dataHostId: "{{ nested_host.id }}",
|
||||
dataType: "nested_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(nested_host.id)",
|
||||
awToolTip: 'Copy or move host to another group',
|
||||
dataPlacement: "top",
|
||||
ngShow: 'nested_host.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
edit: {
|
||||
//label: 'Edit',
|
||||
ngClick: "editHost(nested_host.id)",
|
||||
icon: 'icon-edit',
|
||||
awToolTip: 'Edit host',
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'nested_host.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
view: {
|
||||
//label: 'Edit',
|
||||
ngClick: "editHost(nested_host.id)",
|
||||
awToolTip: 'View host',
|
||||
dataPlacement: 'top',
|
||||
ngShow: '!nested_host.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
"delete": {
|
||||
//label: 'Delete',
|
||||
ngClick: "deleteHost(nested_host.id, nested_host.name)",
|
||||
icon: 'icon-trash',
|
||||
awToolTip: 'Delete host',
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'nested_host.summary_fields.user_capabilities.delete'
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
launch: {
|
||||
mode: 'all',
|
||||
ngDisabled: '!hostsSelected',
|
||||
ngClick: '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.",
|
||||
dataPlacement: 'top',
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'RUN COMMANDS',
|
||||
showTipWhenDisabled: true,
|
||||
tooltipInnerClass: "Tooltip-wide",
|
||||
// TODO: we don't always want to show this
|
||||
ngShow: true
|
||||
},
|
||||
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: '+ ADD HOST',
|
||||
ngShow: 'canAdd',
|
||||
dataPlacement: "top",
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@ -0,0 +1,83 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host',
|
||||
function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host){
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.formCancel = function(){
|
||||
$state.go('^', null, {reload: true});
|
||||
};
|
||||
$scope.toggleHostEnabled = function(){
|
||||
if ($scope.host.has_inventory_sources){
|
||||
return;
|
||||
}
|
||||
$scope.host.enabled = !$scope.host.enabled;
|
||||
};
|
||||
$scope.toggleEnabled = function(){
|
||||
$scope.host.enabled = !$scope.host.enabled;
|
||||
};
|
||||
$scope.groupsTab = function(){
|
||||
let id = $scope.host.summary_fields.inventory.id;
|
||||
$state.go('hosts.edit.nested_groups', {inventory_id: id});
|
||||
};
|
||||
$scope.formSave = function(){
|
||||
var host = {
|
||||
id: $scope.host.id,
|
||||
variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables,
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
enabled: $scope.host.enabled
|
||||
};
|
||||
DashboardHostService.putHost(host).then(function(){
|
||||
$state.go('^', null, {reload: true});
|
||||
});
|
||||
|
||||
};
|
||||
var init = function(){
|
||||
$scope.host = host.data;
|
||||
$scope.name = host.data.name;
|
||||
$scope.description = host.data.description;
|
||||
$scope.variables = getVars(host.data.variables);
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'host_variables',
|
||||
variable: '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;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
}];
|
||||
11
awx/ui/client/src/inventories/hosts/edit/main.js
Normal file
11
awx/ui/client/src/inventories/hosts/edit/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './host-edit.controller';
|
||||
|
||||
export default
|
||||
angular.module('hostsEdit', [])
|
||||
.controller('HostEditController', controller);
|
||||
@ -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,8 @@ export default ['i18n', function(i18n) {
|
||||
formLabelSize: 'col-lg-3',
|
||||
formFieldSize: 'col-lg-9',
|
||||
iterator: 'host',
|
||||
activeEditState: 'hosts.edit',
|
||||
stateTree: 'hosts',
|
||||
headerFields:{
|
||||
enabled: {
|
||||
class: 'Form-header-field',
|
||||
@ -99,5 +102,28 @@ 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('hosts.edit.nested_groups')",
|
||||
ngClick: "groupsTab()",
|
||||
include: "NestedGroupListDefinition",
|
||||
includeForm: "NestedGroupFormDefinition",
|
||||
title: i18n._('Groups'),
|
||||
iterator: 'nested_group',
|
||||
listState: nestedGroupListState
|
||||
},
|
||||
insights: {
|
||||
name: 'insights',
|
||||
title: i18n._('Insights'),
|
||||
skipGenerator: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
120
awx/ui/client/src/inventories/hosts/host.list.js
Normal file
120
awx/ui/client/src/inventories/hosts/host.list.js
Normal file
@ -0,0 +1,120 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
name: 'hosts',
|
||||
iterator: 'host',
|
||||
editTitle: '{{ selected_group }}',
|
||||
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
|
||||
nonstandardSearchParam: {
|
||||
root: 'ansible_facts',
|
||||
param: 'host_filter'
|
||||
},
|
||||
showTitle: false,
|
||||
well: true,
|
||||
index: false,
|
||||
hover: true,
|
||||
hasChildren: true,
|
||||
'class': 'table-no-border',
|
||||
trackBy: 'host.id',
|
||||
basePath: 'hosts',
|
||||
title: false,
|
||||
actionHolderClass: 'List-actionHolder List-actionHolder--leftAlign',
|
||||
|
||||
fields: {
|
||||
toggleHost: {
|
||||
ngDisabled: 'host.has_inventory_sources',
|
||||
label: '',
|
||||
columnClass: 'List-staticColumn--toggle',
|
||||
type: "toggle",
|
||||
ngClick: "toggleHost($event, host)",
|
||||
awToolTip: "<p>" +
|
||||
i18n._("Indicates if a host is available and should be included in running jobs.") +
|
||||
"</p><p>" +
|
||||
i18n._("For hosts that are part of an external" +
|
||||
" inventory, this flag cannot be changed. It will be" +
|
||||
" set by the inventory sync process.") +
|
||||
"</p>",
|
||||
dataPlacement: "right",
|
||||
nosort: true,
|
||||
},
|
||||
active_failures: {
|
||||
label: '',
|
||||
iconOnly: true,
|
||||
nosort: true,
|
||||
// do not remove this ng-click directive
|
||||
// the list generator case to handle fields without ng-click
|
||||
// cannot handle the aw-* directives
|
||||
ngClick: 'noop()',
|
||||
awPopOver: "{{ host.job_status_html }}",
|
||||
dataTitle: "{{ host.job_status_title }}",
|
||||
awToolTip: "{{ host.badgeToolTip }}",
|
||||
dataPlacement: 'top',
|
||||
icon: "{{ 'fa icon-job-' + host.active_failures }}",
|
||||
id: 'active-failures-action',
|
||||
columnClass: 'status-column List-staticColumn--smallStatus'
|
||||
},
|
||||
name: {
|
||||
key: true,
|
||||
label: i18n._('Name'),
|
||||
ngClick: "editHost(host.id)",
|
||||
columnClass: 'col-lg-6 col-md-8 col-sm-8 col-xs-7',
|
||||
dataHostId: "{{ host.id }}",
|
||||
dataType: "host",
|
||||
class: 'InventoryManage-breakWord'
|
||||
},
|
||||
inventory_name: {
|
||||
label: i18n._('Inventory'),
|
||||
sourceModel: 'inventory',
|
||||
sourceField: 'name',
|
||||
columnClass: 'col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis',
|
||||
linkTo: "{{ '/#/inventories/' + host.inventory_id }}"
|
||||
},
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
|
||||
columnClass: 'col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right',
|
||||
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'
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
refresh: {
|
||||
mode: 'all',
|
||||
awToolTip: "Refresh the page",
|
||||
ngClick: "refreshGroups()",
|
||||
ngShow: "socketStatus == 'error'",
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'REFRESH'
|
||||
},
|
||||
smart_inventory: {
|
||||
mode: 'all',
|
||||
ngClick: "smartInventory()",
|
||||
awToolTip: "Create a new Smart Inventory from search results.",
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'SMART INVENTORY',
|
||||
ngShow: 'canAdd',
|
||||
dataPlacement: "top",
|
||||
ngDisabled: '!enableSmartInventoryButton'
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
139
awx/ui/client/src/inventories/hosts/list/host-list.controller.js
Normal file
139
awx/ui/client/src/inventories/hosts/list/host-list.controller.js
Normal file
@ -0,0 +1,139 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
function HostsList($scope, HostsList, $rootScope, GetBasePath,
|
||||
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
|
||||
HostManageService, SetStatus) {
|
||||
|
||||
let list = HostsList;
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function setJobStatus(){
|
||||
_.forEach($scope.hosts, function(value) {
|
||||
SetStatus({
|
||||
scope: $scope,
|
||||
host: value
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.createHost = function(){
|
||||
$state.go('hosts.add');
|
||||
};
|
||||
$scope.editHost = function(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>';
|
||||
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() {
|
||||
// Gather up search terms and pass them to the add smart inventory form
|
||||
let stateParamsCopy = angular.copy($state.params.host_search);
|
||||
let defaults = _.find($state.$current.path, (step) => {
|
||||
if(step && step.params && step.params.hasOwnProperty(`host_search`)){
|
||||
return step.params.hasOwnProperty(`host_search`);
|
||||
}
|
||||
}).params[`host_search`].config.value;
|
||||
|
||||
// Strip defaults out of the state params copy
|
||||
angular.forEach(Object.keys(defaults), function(value) {
|
||||
delete stateParamsCopy[value];
|
||||
});
|
||||
|
||||
$state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify(stateParamsCopy)});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath',
|
||||
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
|
||||
'HostManageService', 'SetStatus', HostsList
|
||||
];
|
||||
11
awx/ui/client/src/inventories/hosts/list/main.js
Normal file
11
awx/ui/client/src/inventories/hosts/list/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './host-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('hostsList', [])
|
||||
.controller('HostListController', controller);
|
||||
26
awx/ui/client/src/inventories/hosts/main.js
Normal file
26
awx/ui/client/src/inventories/hosts/main.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
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';
|
||||
import SmartInventory from './smart-inventory/main';
|
||||
|
||||
export default
|
||||
angular.module('host', [
|
||||
hostEdit.name,
|
||||
hostList.name,
|
||||
SmartInventory.name,
|
||||
])
|
||||
.factory('HostsForm', HostsForm)
|
||||
.factory('HostsList', HostsList)
|
||||
.factory('SetStatus', SetStatus)
|
||||
.factory('SetEnabledMsg', SetEnabledMsg)
|
||||
.service('HostManageService', HostManageService);
|
||||
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './smart-inventory-add.controller';
|
||||
|
||||
export default
|
||||
angular.module('smartInventoryAdd', [])
|
||||
.controller('SmartInventoryAddController', controller);
|
||||
@ -0,0 +1,106 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name controllers.function:Inventories
|
||||
* @description This controller's for the Inventory page
|
||||
*/
|
||||
|
||||
function SmartInventoryAdd($scope, $location,
|
||||
GenerateForm, SmartInventoryForm, 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 = SmartInventoryForm;
|
||||
|
||||
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: 'smartinventory_variables'
|
||||
});
|
||||
|
||||
$scope.dynamic_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : '';
|
||||
}
|
||||
|
||||
// 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('/inventories/' + 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('hosts');
|
||||
};
|
||||
}
|
||||
|
||||
export default ['$scope', '$location',
|
||||
'GenerateForm', 'SmartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert',
|
||||
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange',
|
||||
'Wait', 'ToJSON', '$state', SmartInventoryAdd
|
||||
];
|
||||
@ -0,0 +1,15 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$scope', 'QuerySet',
|
||||
function($scope, qs) {
|
||||
$scope.hostFilterTags = [];
|
||||
|
||||
$scope.$watch('hostFilter', function(){
|
||||
$scope.hostFilterTags = qs.stripDefaultParams($scope.hostFilter);
|
||||
});
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,25 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import dynamicInventoryHostFilterController from './dynamic-inventory-host-filter.controller';
|
||||
|
||||
export default ['templateUrl', '$compile',
|
||||
function(templateUrl, $compile) {
|
||||
return {
|
||||
scope: {
|
||||
hostFilter: '='
|
||||
},
|
||||
restrict: 'E',
|
||||
templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter'),
|
||||
controller: dynamicInventoryHostFilterController,
|
||||
link: function(scope) {
|
||||
scope.openHostFilterModal = function() {
|
||||
$('#content-container').append($compile('<host-filter-modal host-filter="hostFilter"></host-filter-modal>')(scope));
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,12 @@
|
||||
<div class="input-group Form-mixedInputGroup">
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="Form-lookupButton btn btn-default" ng-click="openHostFilterModal()">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
<span class="form-control Form-textInput input-medium lookup" style="padding: 4px 6px;">
|
||||
<span class="LabelList-tag" ng-repeat="tag in hostFilterTags">
|
||||
<span class="LabelList-name">{{tag}}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@ -0,0 +1,81 @@
|
||||
export default ['templateUrl', function(templateUrl) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
hostFilter: '='
|
||||
},
|
||||
templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal'),
|
||||
link: function(scope, element) {
|
||||
|
||||
$('#host-filter-modal').on('hidden.bs.modal', function () {
|
||||
$('#host-filter-modal').off('hidden.bs.modal');
|
||||
$(element).remove();
|
||||
});
|
||||
|
||||
scope.showModal = function() {
|
||||
$('#host-filter-modal').modal('show');
|
||||
};
|
||||
|
||||
scope.destroyModal = function() {
|
||||
$('#host-filter-modal').modal('hide');
|
||||
};
|
||||
|
||||
},
|
||||
controller: ['$scope', 'QuerySet', 'GetBasePath', 'HostsList', '$compile', 'generateList', function($scope, qs, GetBasePath, HostsList, $compile, GenerateList) {
|
||||
|
||||
function init() {
|
||||
|
||||
$scope.host_default_params = {
|
||||
order_by: 'name',
|
||||
page_size: 5
|
||||
};
|
||||
|
||||
$scope.host_queryset = _.merge({
|
||||
order_by: 'name',
|
||||
page_size: 5
|
||||
}, $scope.hostFilter ? $scope.hostFilter : {});
|
||||
|
||||
// Fire off the initial search
|
||||
qs.search(GetBasePath('hosts'), $scope.host_queryset)
|
||||
.then(res => {
|
||||
$scope.host_dataset = res.data;
|
||||
$scope.hosts = $scope.host_dataset.results;
|
||||
|
||||
let hostList = _.cloneDeep(HostsList);
|
||||
delete hostList.fields.toggleHost;
|
||||
delete hostList.fields.active_failures;
|
||||
delete hostList.fields.inventory_name;
|
||||
let html = GenerateList.build({
|
||||
list: hostList,
|
||||
input_type: 'foobar',
|
||||
mode: 'lookup'
|
||||
});
|
||||
|
||||
$scope.list = hostList;
|
||||
|
||||
$('#foobar').append($compile(html)($scope));
|
||||
|
||||
$scope.showModal();
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
$scope.cancelForm = function() {
|
||||
$scope.destroyModal();
|
||||
};
|
||||
|
||||
$scope.saveForm = function() {
|
||||
// Strip defaults out of the state params copy
|
||||
angular.forEach(Object.keys($scope.host_default_params), function(value) {
|
||||
delete $scope.host_queryset[value];
|
||||
});
|
||||
|
||||
$scope.hostFilter = angular.copy($scope.host_queryset);
|
||||
|
||||
$scope.destroyModal();
|
||||
};
|
||||
|
||||
}]
|
||||
};
|
||||
}];
|
||||
@ -0,0 +1,21 @@
|
||||
<div id="host-filter-modal" class="Lookup modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header Form-header">
|
||||
<div class="Form-title Form-title--uppercase">DYNAMIC HOSTS</div>
|
||||
<!-- optional: transclude header fields -->
|
||||
<div class="Form-header--fields"></div>
|
||||
<div class="Form-exitHolder">
|
||||
<button type="button" class="Form-exit" ng-click="cancelForm()">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" id="foobar"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="cancelForm()" class="Lookup-cancel btn btn-default">CANCEL</button>
|
||||
<button type="button" ng-click="saveForm()" class="Lookup-save btn btn-primary">SAVE</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './smart-inventory-edit.controller';
|
||||
|
||||
export default
|
||||
angular.module('smartInventoryEdit', [])
|
||||
.controller('SmartInventoryEditController', controller);
|
||||
@ -0,0 +1,14 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
function SmartInventoryEdit() {
|
||||
|
||||
console.log('inside smart inventory add');
|
||||
|
||||
}
|
||||
|
||||
export default [ SmartInventoryEdit
|
||||
];
|
||||
20
awx/ui/client/src/inventories/hosts/smart-inventory/main.js
Normal file
20
awx/ui/client/src/inventories/hosts/smart-inventory/main.js
Normal file
@ -0,0 +1,20 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import smartInventoryAdd from './add/main';
|
||||
import smartInventoryEdit from './edit/main';
|
||||
import SmartInventoryForm from './smart-inventory.form';
|
||||
import dynamicInventoryHostFilter from './dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive';
|
||||
import hostFilterModal from './dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive';
|
||||
|
||||
export default
|
||||
angular.module('smartInventory', [
|
||||
smartInventoryAdd.name,
|
||||
smartInventoryEdit.name
|
||||
])
|
||||
.factory('SmartInventoryForm', SmartInventoryForm)
|
||||
.directive('dynamicInventoryHostFilter', dynamicInventoryHostFilter)
|
||||
.directive('hostFilterModal', hostFilterModal);
|
||||
@ -0,0 +1,182 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['i18n', 'buildHostListState', function(i18n, buildHostListState) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('NEW SMART INVENTORY'),
|
||||
editTitle: '{{ inventory_name }}',
|
||||
name: 'smartinventory',
|
||||
basePath: 'inventory',
|
||||
breadcrumbName: 'SMART INVENTORY',
|
||||
stateTree: 'hosts',
|
||||
|
||||
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'
|
||||
},
|
||||
dynamic_hosts: {
|
||||
label: i18n._('Dynamic Hosts'),
|
||||
type: 'custom',
|
||||
control: '<dynamic-inventory-host-filter host-filter="dynamic_hosts"></dynamic-inventory-host-filter>',
|
||||
basePath: 'hosts',
|
||||
list: 'HostsList',
|
||||
sourceModel: 'host',
|
||||
sourceField: 'name',
|
||||
required: true
|
||||
// TODO: add required, ngDisabled, awLookupWhen (?)
|
||||
},
|
||||
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 /> \"somevar\": \"somevalue\",<br /> \"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: '+ ADD',
|
||||
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
key: true,
|
||||
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',
|
||||
}
|
||||
}
|
||||
},
|
||||
hosts: {
|
||||
name: 'hosts',
|
||||
include: "RelatedHostsListDefinition",
|
||||
title: i18n._('Hosts'),
|
||||
iterator: 'host',
|
||||
listState: buildHostListState,
|
||||
// addState: buildGroupsAddState,
|
||||
// editState: buildGroupsEditState
|
||||
},
|
||||
//this is a placeholder for when we're ready for completed jobs
|
||||
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: '+ 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};}];
|
||||
57
awx/ui/client/src/inventories/insights/insights.block.less
Normal file
57
awx/ui/client/src/inventories/insights/insights.block.less
Normal file
@ -0,0 +1,57 @@
|
||||
@import "../../shared/branding/colors.default.less";
|
||||
|
||||
.InsightsNav{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
border: 1px solid #B7B7B7;
|
||||
border-radius:5px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
.InsightsNav-rightSide{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 0px 10px 0px
|
||||
}
|
||||
|
||||
.InsightsNav-leftSide{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.InsightsNav-totalIssues{
|
||||
background-color: @default-link;
|
||||
color: @default-bg;
|
||||
}
|
||||
|
||||
.InsightsNav-criticalIssues{
|
||||
background-color: @default-err;
|
||||
}
|
||||
|
||||
.InsightsNav-highIssues{
|
||||
background-color:@default-warning;
|
||||
}
|
||||
|
||||
.InsightsNav-mediumIssues{
|
||||
background-color: @default-succ;
|
||||
}
|
||||
|
||||
.InsightsNav-lowIssues{
|
||||
background-color: @default-succ;
|
||||
}
|
||||
|
||||
.InsightsNav-solvableBadge{
|
||||
background-color: @b7grey;
|
||||
}
|
||||
.InsightsNav-solvableBadge:last-of-type{
|
||||
margin-right: 20px;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default [
|
||||
function () {
|
||||
|
||||
function init() {
|
||||
// $scope.insights
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
}];
|
||||
20
awx/ui/client/src/inventories/insights/insights.partial.html
Normal file
20
awx/ui/client/src/inventories/insights/insights.partial.html
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="InsightsNav">
|
||||
<div class="InsightsNav-rightSide">
|
||||
<div class="JobResults-badgeTitle">Total Issues</div>
|
||||
<span class="badge List-titleBadge InsightsNav-totalIssues">4</span>
|
||||
<div class="JobResults-badgeTitle">Critical</div>
|
||||
<span class="badge List-titleBadge InsightsNav-criticalIssues">1</span>
|
||||
<div class="JobResults-badgeTitle">High</div>
|
||||
<span class="badge List-titleBadge InsightsNav-highIssues">1</span>
|
||||
<div class="JobResults-badgeTitle">Medium</div>
|
||||
<span class="badge List-titleBadge InsightsNav-mediumIssues">1</span>
|
||||
<div class="JobResults-badgeTitle">Low</div>
|
||||
<span class="badge List-titleBadge InsightsNav-lowIssues">1</span>
|
||||
</div>
|
||||
<div class="InsightsNav-leftSide">
|
||||
<div class="JobResults-badgeTitle">Solvable With Playbook</div>
|
||||
<span class="badge List-titleBadge InsightsNav-solvableBadge">4</span>
|
||||
<div class="JobResults-badgeTitle">Not Solvable With Playbook</div>
|
||||
<span class="badge List-titleBadge InsightsNav-solvableBadge">1</span>
|
||||
</div>
|
||||
</div>
|
||||
11
awx/ui/client/src/inventories/insights/main.js
Normal file
11
awx/ui/client/src/inventories/insights/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './insights.controller';
|
||||
|
||||
export default
|
||||
angular.module('insightsDashboard', [])
|
||||
.controller('InsightsController', controller);
|
||||
21
awx/ui/client/src/inventories/inventories.partial.html
Normal file
21
awx/ui/client/src/inventories/inventories.partial.html
Normal file
@ -0,0 +1,21 @@
|
||||
<div class="tab-pane" id="inventories-panel">
|
||||
<aw-limit-panels max-panels="2" panel-container="inventories-panel"></aw-limit-panels>
|
||||
<div ui-view="copyMove"></div>
|
||||
<div ui-view="adhocForm"></div>
|
||||
<div ui-view="hostForm"></div>
|
||||
<div ui-view="nestedGroupForm"></div>
|
||||
<div ui-view="groupForm"></div>
|
||||
<div ui-view="sourcesForm"></div>
|
||||
<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('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>
|
||||
<div ui-view="list"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,5 +1,5 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
@ -10,8 +10,33 @@
|
||||
* @description This form is for adding/editing an inventory
|
||||
*/
|
||||
|
||||
export default ['i18n', function(i18n) {
|
||||
return {
|
||||
export default ['i18n', 'buildGroupsListState', 'buildGroupsAddState',
|
||||
'buildGroupsEditState', 'buildHostListState', 'buildHostAddState',
|
||||
'buildHostEditState', 'buildSourcesListState', 'buildSourcesAddState',
|
||||
'buildSourcesEditState', 'buildInventoryCompletedJobsState',
|
||||
'InventoryCompletedJobsList',
|
||||
function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
|
||||
buildHostListState, buildHostAddState, buildHostEditState,
|
||||
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 {
|
||||
|
||||
addTitle: i18n._('NEW INVENTORY'),
|
||||
editTitle: '{{ inventory_name }}',
|
||||
@ -50,7 +75,8 @@ export default ['i18n', function(i18n) {
|
||||
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg',
|
||||
awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg'
|
||||
},
|
||||
variables: {
|
||||
inventory_variables: {
|
||||
realName: 'variables',
|
||||
label: i18n._('Variables'),
|
||||
type: 'textarea',
|
||||
class: 'Form-formGroup--fullWidth',
|
||||
@ -90,7 +116,7 @@ export default ['i18n', function(i18n) {
|
||||
name: 'permissions',
|
||||
awToolTip: i18n._('Please save before assigning permissions'),
|
||||
dataPlacement: 'top',
|
||||
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/',
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/access_list/',
|
||||
type: 'collection',
|
||||
title: i18n._('Permissions'),
|
||||
iterator: 'permission',
|
||||
@ -112,6 +138,7 @@ export default ['i18n', function(i18n) {
|
||||
},
|
||||
fields: {
|
||||
username: {
|
||||
key: true,
|
||||
label: i18n._('User'),
|
||||
linkBase: 'users',
|
||||
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
|
||||
@ -129,7 +156,36 @@ export default ['i18n', function(i18n) {
|
||||
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
name: 'groups',
|
||||
include: "GroupList",
|
||||
title: i18n._('Groups'),
|
||||
iterator: 'group',
|
||||
listState: buildGroupsListState,
|
||||
addState: buildGroupsAddState,
|
||||
editState: buildGroupsEditState
|
||||
},
|
||||
hosts: {
|
||||
name: 'hosts',
|
||||
include: "RelatedHostsListDefinition",
|
||||
title: i18n._('Hosts'),
|
||||
iterator: 'host',
|
||||
listState: buildHostListState,
|
||||
addState: buildHostAddState,
|
||||
editState: buildHostEditState
|
||||
},
|
||||
inventory_sources: {
|
||||
name: 'inventory_sources',
|
||||
include: "SourcesListDefinition",
|
||||
includeForm: "SourcesFormDefinition",
|
||||
title: i18n._('Sources'),
|
||||
iterator: 'inventory_source',
|
||||
listState: buildSourcesListState,
|
||||
addState: buildSourcesAddState,
|
||||
editState: buildSourcesEditState
|
||||
},
|
||||
completed_jobs: completed_jobs_object
|
||||
}
|
||||
|
||||
};}];
|
||||
|
||||
@ -17,22 +17,17 @@ export default ['i18n', function(i18n) {
|
||||
index: false,
|
||||
hover: true,
|
||||
basePath: 'inventory',
|
||||
title: false,
|
||||
|
||||
fields: {
|
||||
status: {
|
||||
label: '',
|
||||
columnClass: 'List-staticColumn--mediumStatus',
|
||||
columnClass: 'col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus',
|
||||
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)",
|
||||
@ -42,11 +37,11 @@ export default ['i18n', function(i18n) {
|
||||
name: {
|
||||
key: true,
|
||||
label: i18n._('Name'),
|
||||
columnClass: 'col-md-5 col-sm-5 col-xs-8 List-staticColumnAdjacent',
|
||||
columnClass: 'col-md-5 col-sm-4 col-xs-6 List-staticColumnAdjacent',
|
||||
modalColumnClass: 'col-md-11',
|
||||
linkTo: '/#/inventories/{{inventory.id}}/manage',
|
||||
awToolTip: "{{ inventory.description }}",
|
||||
awTipPlacement: "top"
|
||||
awTipPlacement: "top",
|
||||
linkTo: '/#/inventories/basic_inventory/{{inventory.id}}'
|
||||
},
|
||||
organization: {
|
||||
label: i18n._('Organization'),
|
||||
@ -55,18 +50,32 @@ export default ['i18n', function(i18n) {
|
||||
sourceModel: 'organization',
|
||||
sourceField: 'name',
|
||||
excludeModal: true,
|
||||
columnClass: 'col-md-5 col-sm-3 hidden-xs'
|
||||
columnClass: 'col-md-4 col-sm-2 hidden-xs'
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
mode: 'all', // One of: edit, select, all
|
||||
ngClick: 'addInventory()',
|
||||
type: 'buttonDropdown',
|
||||
basePaths: ['inventories'],
|
||||
awToolTip: i18n._('Create a new inventory'),
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
actionClass: 'btn List-dropdownSuccess',
|
||||
buttonContent: '+ ' + i18n._('ADD'),
|
||||
ngShow: 'canAdd'
|
||||
options: [
|
||||
{
|
||||
optionContent: i18n._('Inventory'),
|
||||
optionSref: 'inventories.add',
|
||||
ngShow: 'canAddInventory'
|
||||
},
|
||||
{
|
||||
optionContent: i18n._('Smart Inventory'),
|
||||
optionSref: 'inventories.addSmartInventory',
|
||||
//TODO: this should have its own permission
|
||||
ngShow: 'canAddInventory'
|
||||
}
|
||||
],
|
||||
ngShow: 'canAddInventory || canAddSmartInventory || canAddSCMInventory'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -23,8 +23,8 @@ function InventoriesList($scope, $rootScope, $location,
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd('inventory')
|
||||
.then(function(params) {
|
||||
$scope.canAdd = params.canAdd;
|
||||
.then(function(canAdd) {
|
||||
$scope.canAddInventory = canAdd;
|
||||
});
|
||||
|
||||
$scope.$watchCollection(list.name, function(){
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
import controller from './inventory-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('inventoryList', [])
|
||||
angular.module('InventoryList', [])
|
||||
.controller('InventoryListController', controller);
|
||||
|
||||
@ -4,230 +4,149 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import adhoc from './adhoc/main';
|
||||
import host from './hosts/main';
|
||||
import group from './groups/main';
|
||||
import sources from './sources/main';
|
||||
import relatedHost from './related-hosts/main';
|
||||
import inventoryCompletedJobs from './completed_jobs/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 InventoryManageService from './inventory-manage.service';
|
||||
import adHocRoute from './adhoc/adhoc.route';
|
||||
import ansibleFacts from './ansible_facts/main';
|
||||
import insights from './insights/main';
|
||||
import { copyMoveGroupRoute, copyMoveHostRoute } from './copy-move/copy-move.route';
|
||||
import copyMove from './copy-move/main';
|
||||
export default
|
||||
angular.module('inventory', [
|
||||
adhoc.name,
|
||||
host.name,
|
||||
group.name,
|
||||
sources.name,
|
||||
relatedHost.name,
|
||||
inventoryCompletedJobs.name,
|
||||
inventoryAdd.name,
|
||||
inventoryEdit.name,
|
||||
inventoryList.name,
|
||||
inventoryManage.name,
|
||||
ansibleFacts.name,
|
||||
insights.name,
|
||||
copyMove.name
|
||||
])
|
||||
.factory('InventoryList', InventoryList)
|
||||
.factory('InventoryForm', InventoryForm)
|
||||
.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 stateTree, inventories,
|
||||
addGroup, editGroup, addHost, editHost,
|
||||
listSchedules, addSchedule, editSchedule, adhocCredentialLookup,
|
||||
stateDefinitions = stateDefinitionsProvider.$get(),
|
||||
stateExtender = $stateExtenderProvider.$get();
|
||||
.factory('InventoryList', InventoryList)
|
||||
.service('InventoryManageService', InventoryManageService)
|
||||
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
|
||||
function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) {
|
||||
let stateDefinitions = stateDefinitionsProvider.$get(),
|
||||
stateExtender = $stateExtenderProvider.$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'
|
||||
},
|
||||
function factsConfig(stateName) {
|
||||
return {
|
||||
name: stateName,
|
||||
url: '/ansible_facts',
|
||||
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')
|
||||
label: N_("FACTS")
|
||||
},
|
||||
views: {
|
||||
'related': {
|
||||
controller: 'AnsibleFactsController',
|
||||
templateUrl: templateUrl('inventories/ansible_facts/ansible_facts')
|
||||
}
|
||||
},
|
||||
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);
|
||||
Facts: ['$stateParams', 'GetBasePath', 'Rest',
|
||||
function($stateParams, GetBasePath, Rest) {
|
||||
let ansibleFactsUrl = GetBasePath('hosts') + $stateParams.host_id + '/ansible_facts';
|
||||
Rest.setUrl(ansibleFactsUrl);
|
||||
return Rest.get()
|
||||
.success(function(data) {
|
||||
return 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: () => ''
|
||||
},
|
||||
// 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>";
|
||||
},
|
||||
controller: 'schedulerListController'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
addSchedule = {
|
||||
name: 'inventoryManage.editGroup.schedules.add',
|
||||
url: '/add',
|
||||
function insightsConfig(stateName) {
|
||||
return {
|
||||
name: stateName,
|
||||
url: '/insights',
|
||||
ncyBreadcrumb: {
|
||||
label: N_("CREATE SCHEDULE")
|
||||
label: N_("INSIGHTS")
|
||||
},
|
||||
views: {
|
||||
'form': {
|
||||
controller: 'schedulerAddController',
|
||||
templateUrl: templateUrl("scheduler/schedulerForm")
|
||||
'related': {
|
||||
controller: 'InsightsController',
|
||||
templateUrl: templateUrl('inventories/insights/insights')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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()
|
||||
};
|
||||
});
|
||||
Facts: ['$stateParams', 'GetBasePath', 'Rest',
|
||||
function($stateParams, GetBasePath, Rest) {
|
||||
let ansibleFactsUrl = GetBasePath('hosts') + $stateParams.host_id + '/ansible_facts';
|
||||
Rest.setUrl(ansibleFactsUrl);
|
||||
return Rest.get()
|
||||
.success(function(data) {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// host state nodes
|
||||
addHost = stateDefinitions.generateTree({
|
||||
url: '/add-host',
|
||||
name: 'inventoryManage.addHost',
|
||||
function generateInventoryStates() {
|
||||
|
||||
let basicInventoryAdd = stateDefinitions.generateTree({
|
||||
name: 'inventories.add', // top-most node in the generated tree (will replace this state definition)
|
||||
url: '/basic_inventory/add',
|
||||
modes: ['add'],
|
||||
form: 'HostForm',
|
||||
form: 'InventoryForm',
|
||||
controllers: {
|
||||
add: 'HostsAddController'
|
||||
add: 'InventoryAddController'
|
||||
}
|
||||
});
|
||||
|
||||
editHost = stateDefinitions.generateTree({
|
||||
url: '/edit-host/:host_id',
|
||||
name: 'inventoryManage.editHost',
|
||||
let basicInventoryEdit = stateDefinitions.generateTree({
|
||||
name: 'inventories.edit',
|
||||
url: '/basic_inventory/:inventory_id',
|
||||
modes: ['edit'],
|
||||
form: 'HostForm',
|
||||
form: 'InventoryForm',
|
||||
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}}",
|
||||
},
|
||||
edit: 'InventoryEditController'
|
||||
}
|
||||
});
|
||||
|
||||
adhocCredentialLookup = {
|
||||
let smartInventoryAdd = stateDefinitions.generateTree({
|
||||
name: 'inventories.addSmartInventory', // top-most node in the generated tree (will replace this state definition)
|
||||
url: '/smart_inventory/add?hostfilter',
|
||||
modes: ['add'],
|
||||
form: 'SmartInventoryForm',
|
||||
controllers: {
|
||||
add: 'SmartInventoryAddController'
|
||||
}
|
||||
});
|
||||
|
||||
let smartInventoryEdit = stateDefinitions.generateTree({
|
||||
name: 'inventories.editSmartInventory',
|
||||
url: '/smart_inventory/:inventory_id',
|
||||
modes: ['edit'],
|
||||
form: 'SmartInventoryForm',
|
||||
controllers: {
|
||||
edit: 'SmartInventoryEditController'
|
||||
}
|
||||
});
|
||||
|
||||
let adhocCredentialLookup = {
|
||||
searchPrefix: 'credential',
|
||||
name: 'inventoryManage.adhoc.credential',
|
||||
name: 'inventories.edit.adhoc.credential',
|
||||
url: '/credential',
|
||||
data: {
|
||||
formChildState: true
|
||||
@ -276,40 +195,230 @@ angular.module('inventory', [
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open');
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let listSchedules = {
|
||||
name: 'inventories.edit.inventory_sources.edit.schedules',
|
||||
url: '/schedules',
|
||||
searchPrefix: 'schedule',
|
||||
ncyBreadcrumb: {
|
||||
label: N_('SCHEDULES')
|
||||
},
|
||||
resolve: {
|
||||
Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', 'inventorySourceData',
|
||||
function(list, qs, $stateParams, GetBasePath, inventorySourceData) {
|
||||
let path = `${inventorySourceData.related.schedules}`;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
],
|
||||
ParentObject: ['inventorySourceData', function(inventorySourceData) {
|
||||
return inventorySourceData;
|
||||
}],
|
||||
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', 'inventorySourceData',
|
||||
(SchedulesList, inventorySourceData) => {
|
||||
let list = _.cloneDeep(SchedulesList);
|
||||
list.basePath = `${inventorySourceData.related.schedules}`;
|
||||
return list;
|
||||
}
|
||||
]
|
||||
},
|
||||
views: {
|
||||
// clear form template when views render in this substate
|
||||
'form': {
|
||||
templateProvider: () => ''
|
||||
},
|
||||
// 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>";
|
||||
},
|
||||
controller: 'schedulerListController'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let addSchedule = {
|
||||
name: 'inventories.edit.inventory_sources.edit.schedules.add',
|
||||
url: '/add',
|
||||
ncyBreadcrumb: {
|
||||
label: N_("CREATE SCHEDULE")
|
||||
},
|
||||
views: {
|
||||
'form': {
|
||||
controller: 'schedulerAddController',
|
||||
templateUrl: templateUrl("scheduler/schedulerForm")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let editSchedule = {
|
||||
name: 'inventories.edit.inventory_sources.edit.schedules.edit',
|
||||
url: '/:schedule_id',
|
||||
ncyBreadcrumb: {
|
||||
label: "{{schedule_obj.name}}"
|
||||
},
|
||||
views: {
|
||||
'form': {
|
||||
templateUrl: templateUrl("scheduler/schedulerForm"),
|
||||
controller: 'schedulerEditController',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let relatedHostsAnsibleFacts = factsConfig('inventories.edit.hosts.edit.ansible_facts');
|
||||
let nestedHostsAnsibleFacts = factsConfig('inventories.edit.groups.edit.nested_hosts.edit.ansible_facts');
|
||||
let relatedHostsInsights = insightsConfig('inventories.edit.hosts.edit.insights');
|
||||
let nestedHostsInsights = insightsConfig('inventories.edit.groups.edit.nested_hosts.edit.insights');
|
||||
|
||||
return Promise.all([
|
||||
inventories,
|
||||
addGroup,
|
||||
editGroup,
|
||||
addHost,
|
||||
editHost,
|
||||
basicInventoryAdd,
|
||||
basicInventoryEdit,
|
||||
smartInventoryAdd,
|
||||
smartInventoryEdit
|
||||
]).then((generated) => {
|
||||
return {
|
||||
states: _.reduce(generated, (result, definition) => {
|
||||
return result.concat(definition.states);
|
||||
}, [
|
||||
stateExtender.buildDefinition(inventoryManageListRoute),
|
||||
stateExtender.buildDefinition(copyMoveGroupRoute),
|
||||
stateExtender.buildDefinition(copyMoveHostRoute),
|
||||
stateExtender.buildDefinition({
|
||||
name: 'inventories', // top-most node in the generated tree (will replace this state definition)
|
||||
route: '/inventories',
|
||||
ncyBreadcrumb: {
|
||||
label: N_('INVENTORIES')
|
||||
},
|
||||
views: {
|
||||
'@': {
|
||||
templateUrl: templateUrl('inventories/inventories')
|
||||
},
|
||||
'list@inventories': {
|
||||
templateProvider: function(InventoryList, generateList) {
|
||||
let html = generateList.build({
|
||||
list: InventoryList,
|
||||
mode: 'edit'
|
||||
});
|
||||
return html;
|
||||
},
|
||||
controller: 'InventoryListController'
|
||||
}
|
||||
},
|
||||
searchPrefix: 'inventory',
|
||||
resolve: {
|
||||
Dataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
stateExtender.buildDefinition(adHocRoute),
|
||||
stateExtender.buildDefinition(adhocCredentialLookup)
|
||||
|
||||
stateExtender.buildDefinition(adhocCredentialLookup),
|
||||
stateExtender.buildDefinition(listSchedules),
|
||||
stateExtender.buildDefinition(addSchedule),
|
||||
stateExtender.buildDefinition(editSchedule),
|
||||
stateExtender.buildDefinition(relatedHostsAnsibleFacts),
|
||||
stateExtender.buildDefinition(nestedHostsAnsibleFacts),
|
||||
stateExtender.buildDefinition(relatedHostsInsights),
|
||||
stateExtender.buildDefinition(nestedHostsInsights),
|
||||
stateExtender.buildDefinition(copyMoveGroupRoute),
|
||||
stateExtender.buildDefinition(copyMoveHostRoute)
|
||||
])
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
stateTree = {
|
||||
name: 'inventories',
|
||||
url: '/inventories',
|
||||
ncyBreadcrumb: {
|
||||
label: N_("INVENTORIES")
|
||||
},
|
||||
lazyLoad: () => generateStateTree()
|
||||
let generateHostStates = function(){
|
||||
let hostTree = stateDefinitions.generateTree({
|
||||
parent: 'hosts', // top-most node in the generated tree (will replace this state definition)
|
||||
modes: ['edit'],
|
||||
list: 'HostsList',
|
||||
form: 'HostsForm',
|
||||
controllers: {
|
||||
list: 'HostListController',
|
||||
edit: 'HostEditController'
|
||||
},
|
||||
urls: {
|
||||
list: '/hosts'
|
||||
},
|
||||
resolve: {
|
||||
edit: {
|
||||
host: ['Rest', '$stateParams', 'GetBasePath',
|
||||
function(Rest, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath('hosts') + $stateParams.host_id;
|
||||
Rest.setUrl(path);
|
||||
return Rest.get();
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let hostAnsibleFacts = factsConfig('hosts.edit.ansible_facts');
|
||||
let hostInsights = insightsConfig('hosts.edit.insights');
|
||||
|
||||
return Promise.all([
|
||||
hostTree
|
||||
]).then((generated) => {
|
||||
return {
|
||||
states: _.reduce(generated, (result, definition) => {
|
||||
return result.concat(definition.states);
|
||||
}, [
|
||||
stateExtender.buildDefinition(hostAnsibleFacts),
|
||||
stateExtender.buildDefinition(hostInsights)
|
||||
])
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
$stateProvider.state(stateTree);
|
||||
$stateProvider.state({
|
||||
name: 'hosts',
|
||||
url: '/hosts',
|
||||
lazyLoad: () => generateHostStates()
|
||||
});
|
||||
|
||||
$stateProvider.state({
|
||||
name: 'inventories',
|
||||
url: '/inventories',
|
||||
lazyLoad: () => generateInventoryStates()
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
<div class="Panel" id="adhoc">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
</div>
|
||||
</div>
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
}];
|
||||
@ -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>
|
||||
@ -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'
|
||||
];
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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 />  \"somevar\": \"somevalue\",<br /> \"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 » us-east-1b</strong></li>" +
|
||||
"<li>Image ID: <strong>images » ami-b007ab1e</strong></li>" +
|
||||
"<li>Instance ID: <strong>instances » i-ca11ab1e</strong></li>" +
|
||||
"<li>Instance Type: <strong>types » type_m1_medium</strong></li>" +
|
||||
"<li>Key Name: <strong>keys » key_testing</strong></li>" +
|
||||
"<li>Region: <strong>regions » us-east-1</strong></li>" +
|
||||
"<li>Security Group: <strong>security_groups » security_group_default</strong></li>" +
|
||||
"<li>Tags: <strong>tags » tag_Name » tag_Name_host1</strong></li>" +
|
||||
"<li>VPC ID: <strong>vpcs » vpc-5ca1ab1e</strong></li>" +
|
||||
"<li>Tag None: <strong>tags » 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 /> \"somevar\": \"somevalue\",<br /> \"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 /> \"somevar\": \"somevalue\",<br /> \"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 /> \"somevar\": \"somevalue\",<br /> \"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 /> \"somevar\": \"somevalue\",<br /> \"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;
|
||||
};
|
||||
}];
|
||||
@ -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);
|
||||
@ -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});
|
||||
});
|
||||
};
|
||||
}];
|
||||
@ -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();
|
||||
});
|
||||
}];
|
||||
@ -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);
|
||||
@ -1,3 +0,0 @@
|
||||
.InventoryManage-container{
|
||||
margin-top: -36px;
|
||||
}
|
||||
@ -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.";
|
||||
});
|
||||
}];
|
||||
@ -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>
|
||||
@ -1,141 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import { templateUrl } from '../../shared/template-url/template-url.factory';
|
||||
import InventoriesManage from './inventory-manage.controller';
|
||||
import BreadcrumbsController from './breadcrumbs/breadcrumbs.controller';
|
||||
import HostsListController from './hosts/hosts-list.controller';
|
||||
import GroupsListController from './groups/groups-list.controller';
|
||||
|
||||
export default {
|
||||
name: 'inventoryManage',
|
||||
data: {
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed"]
|
||||
}
|
||||
}
|
||||
},
|
||||
// instead of a single 'searchPrefix' attribute, provide hard-coded search params
|
||||
url: '/inventories/:inventory_id/manage?{group:int}{group_search:queryset}{host_search:queryset}',
|
||||
params: {
|
||||
group: {
|
||||
array: true
|
||||
},
|
||||
group_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
},
|
||||
squash: true,
|
||||
dynamic: true
|
||||
},
|
||||
host_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
},
|
||||
squash: true,
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
skip: true // Never display this state in ncy-breadcrumb.
|
||||
},
|
||||
// enforce uniqueness in group param
|
||||
onEnter: function($stateParams) {
|
||||
$stateParams.group = _.uniq($stateParams.group);
|
||||
},
|
||||
resolve: {
|
||||
groupsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide this node's children
|
||||
InventoryManageService.childGroupsUrl(_.last($stateParams.group)) :
|
||||
// root context - provide root nodes
|
||||
InventoryManageService.rootGroupsUrl($stateParams.inventory_id);
|
||||
}],
|
||||
hostsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide all hosts managed by nodes
|
||||
InventoryManageService.childHostsUrl(_.last($stateParams.group)) :
|
||||
// root context - provide all hosts in an inventory
|
||||
InventoryManageService.rootHostsUrl($stateParams.inventory_id);
|
||||
}],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data);
|
||||
}],
|
||||
breadCrumbData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide breadcrumb data
|
||||
InventoryManageService.getBreadcrumbs($stateParams.group).then(res => res.data.results) :
|
||||
// root context
|
||||
false;
|
||||
}],
|
||||
groupsDataset: ['InventoryGroups', 'QuerySet', '$stateParams', 'groupsUrl', (list, qs, $stateParams, groupsUrl) => {
|
||||
let path = groupsUrl;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}],
|
||||
hostsDataset: ['InventoryHosts', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => {
|
||||
let path = hostsUrl;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}]
|
||||
},
|
||||
views: {
|
||||
// target the ui-view with name "groupBreadcrumbs" at the root view
|
||||
'groupBreadcrumbs@': {
|
||||
controller: BreadcrumbsController,
|
||||
templateUrl: templateUrl('inventories/manage/breadcrumbs/breadcrumbs')
|
||||
},
|
||||
// target the un-named ui-view @ root level
|
||||
'@': {
|
||||
templateUrl: templateUrl('inventories/manage/inventory-manage'),
|
||||
controller: InventoriesManage
|
||||
},
|
||||
// target ui-views with name@inventoryManage state
|
||||
'groupsList@inventoryManage': {
|
||||
templateProvider: function(InventoryGroups, generateList, $templateRequest, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(InventoryGroups);
|
||||
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'
|
||||
});
|
||||
html = generateList.wrapPanel(html);
|
||||
// Include the custom group delete modal template
|
||||
return $templateRequest(templateUrl('inventories/manage/groups/groups-list')).then((template) => {
|
||||
return html.concat(template);
|
||||
});
|
||||
},
|
||||
controller: GroupsListController
|
||||
},
|
||||
'hostsList@inventoryManage': {
|
||||
templateProvider: function(InventoryHosts, generateList, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(InventoryHosts);
|
||||
if($stateParams && $stateParams.group) {
|
||||
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts';
|
||||
}
|
||||
let html = generateList.build({
|
||||
list: list,
|
||||
mode: 'edit'
|
||||
});
|
||||
return generateList.wrapPanel(html);
|
||||
},
|
||||
controller: HostsListController
|
||||
}
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user