Merge pull request #6109 from ansible/inventory-rework

New Inventory Pages
This commit is contained in:
Jared Tabor 2017-05-01 16:33:05 -04:00 committed by GitHub
commit 2c0a24f408
145 changed files with 6236 additions and 1494 deletions

View File

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

View File

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

View File

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

View File

@ -93,6 +93,10 @@ body {
margin-top: 20px;
}
.Panel-hidden {
display: none;
}
.btn{
text-transform: uppercase;
}

View File

@ -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] === '') {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,80 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import JobsListController from '../../jobs/jobs-list.controller';
export default ['InventoryCompletedJobsList', '$stateExtender', 'templateUrl', '$injector',
function(InventoryCompletedJobsList, $stateExtender, templateUrl, $injector){
var val = function(field, formStateDefinition) {
let state,
list = field.include ? $injector.get(field.include) : field,
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
stateConfig = {
// searchPrefix: `${list.iterator}`,
name: `${formStateDefinition.name}.${list.iterator}s`,
url: `/${list.iterator}s`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${breadcrumbLabel}`
},
params: {
completed_job_search: {
value: {
or__job__inventory: '',
or__adhoccommand__inventory: '',
or__inventoryupdate__inventory_source__inventory: ''
},
squash: ''
}
},
views: {
'related': {
templateProvider: function(FormDefinition, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: `${list.iterator}s`,
form: typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition
});
return html;
},
controller: JobsListController
}
},
resolve: {
ListDefinition: () => {
return list;
},
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => {
// allow related list definitions to use interpolated $rootScope / $stateParams in basePath field
let path, interpolator;
if (GetBasePath(list.basePath)) {
path = GetBasePath(list.basePath);
} else {
interpolator = $interpolate(list.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
}
$stateParams[`${list.iterator}_search`].or__job__inventory = $stateParams.inventory_id;
$stateParams[`${list.iterator}_search`].or__adhoccommand__inventory = $stateParams.inventory_id;
$stateParams[`${list.iterator}_search`].or__inventoryupdate__inventory_source__inventory = $stateParams.inventory_id;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
};
state = $stateExtender.buildDefinition(stateConfig);
// appy any default search parameters in form definition
if (field.search) {
state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
}
return state;
};
return val;
}
];

View File

@ -0,0 +1,88 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
return {
// These tooltip fields are consumed to build disabled related tabs tooltips in the form > add view
awToolTip: i18n._('Please save and run a job to view'),
dataPlacement: 'top',
name: 'completed_jobs',
basePath: 'unified_jobs',
iterator: 'completed_job',
search: {
"or__job__inventory": ''
},
editTitle: i18n._('COMPLETED JOBS'),
index: false,
hover: true,
well: false,
emptyListText: i18n._('No completed jobs'),
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
awToolTip: "{{ completed_job.status_tip }}",
awTipPlacement: "right",
dataTitle: "{{ completed_job.status_popover_title }}",
icon: 'icon-job-{{ completed_job.status }}',
iconOnly: true,
ngClick:"viewjobResults(completed_job)",
},
id: {
label: 'ID',
ngClick:"viewjobResults(completed_job)",
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
awToolTip: "{{ completed_job.status_tip }}",
dataPlacement: 'top'
},
name: {
label: i18n._('Name'),
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
ngClick: "viewjobResults(completed_job)",
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},
type: {
label: i18n._('Type'),
ngBind: 'completed_job.type_label',
link: false,
columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs",
},
finished: {
label: i18n._('Finished'),
noLink: true,
filter: "longDate",
columnClass: "col-lg-3 col-md-3 col-sm-3 hidden-xs",
key: true,
desc: true
}
},
actions: { },
fieldActions: {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: {
icon: 'icon-rocket',
mode: 'all',
ngClick: 'relaunchJob($event, completed_job.id)',
awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top',
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start"
},
"delete": {
mode: 'all',
ngClick: 'deleteJob(completed_job.id)',
awToolTip: i18n._('Delete the job'),
dataPlacement: 'top',
ngShow: 'completed_job.summary_fields.user_capabilities.delete'
}
}
};}];

View File

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

View File

@ -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();
}];

View File

@ -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'
};

View File

@ -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();
}];

View 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};

View File

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

View File

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

View File

@ -0,0 +1,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;
}
];

View File

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

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

View File

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

View File

@ -0,0 +1,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 }));
};
}
];

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

View 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 />&emsp; \"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body',
tab: 'properties'
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
nested_groups: {
name: '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
},
}
};
}];

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

@ -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 />&emsp; \"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataContainer: 'body',
tab: 'properties'
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
nested_groups: {
name: 'related_groups',
ngClick: "$state.go('inventories.edit.groups.edit.related_groups')",
include: "RelatedGroupListDefinition",
includeForm: "RelatedGroupFormDefinition",
title: i18n._('Groups'),
iterator: 'related_group',
listState: nestedGroupListState
},
}
};
}];

View File

@ -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: '&#43; ADD GROUP',
ngShow: 'canAdd',
dataPlacement: "top",
}
},
fieldActions: {
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right',
// group_update: {
// //label: 'Sync',
// mode: 'all',
// ngClick: 'updateGroup(group)',
// awToolTip: "{{ group.launch_tooltip }}",
// dataTipWatch: "group.launch_tooltip",
// ngShow: "(group.status !== 'running' && group.status " +
// "!== 'pending' && group.status !== 'updating') && group.summary_fields.user_capabilities.start",
// ngClass: "group.launch_class",
// dataPlacement: "top",
// },
// cancel: {
// //label: 'Cancel',
// mode: 'all',
// ngClick: "cancelUpdate(group.id)",
// awToolTip: "Cancel sync process",
// 'class': 'red-txt',
// ngShow: "(group.status == 'running' || group.status == 'pending' " +
// "|| group.status == 'updating') && group.summary_fields.user_capabilities.start",
// dataPlacement: "top",
// iconClass: "fa fa-minus-circle"
// },
copy: {
mode: 'all',
ngClick: "copyMoveGroup(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"
}
}
};

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

View File

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

View File

@ -0,0 +1,162 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostManageService', 'SetStatus',
function($scope, NestedHostsListDefinition, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostManageService, SetStatus) {
let list = NestedHostsListDefinition;
init();
function init(){
$scope.canAdd = false;
$scope.enableSmartInventoryButton = false;
rbacUiControlService.canAdd('hosts')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// Search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$rootScope.flashMessage = null;
$scope.$watchCollection(list.name, function() {
$scope[list.name] = _.map($scope.nested_hosts, function(value) {
value.inventory_name = value.summary_fields.inventory.name;
value.inventory_id = value.summary_fields.inventory.id;
return value;
});
setJobStatus();
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if(toState.name === 'hosts.addSmartInventory') {
$scope.enableSmartInventoryButton = false;
}
else {
if(toParams && toParams.host_search) {
let hasMoreThanDefaultKeys = false;
angular.forEach(toParams.host_search, function(value, key) {
if(key !== 'order_by' && key !== 'page_size') {
hasMoreThanDefaultKeys = true;
}
});
$scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false;
}
else {
$scope.enableSmartInventoryButton = false;
}
}
});
$scope.$on('selectedOrDeselected', function(e, value) {
let item = value.value;
if (value.isSelected) {
if(!$scope.hostsSelected) {
$scope.hostsSelected = [];
}
$scope.hostsSelected.push(item);
} else {
_.remove($scope.hostsSelected, { id: item.id });
if($scope.hostsSelected.length === 0) {
$scope.hostsSelected = null;
}
}
$scope.systemTrackingDisabled = ($scope.hostsSelected && $scope.hostsSelected.length > 2) ? true : false;
});
}
function setJobStatus(){
_.forEach($scope.hosts, function(value) {
SetStatus({
scope: $scope,
host: value
});
});
}
$scope.createHost = function(){
$state.go('inventories.edit.groups.edit.nested_hosts.add');
};
$scope.editHost = function(id){
$state.go('inventories.edit.groups.edit.nested_hosts.edit', {host_id: id});
};
$scope.deleteHost = function(id, name){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the host below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(name) + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
Wait('start');
HostManageService.delete(id).then(() => {
$('#prompt-modal').modal('hide');
if (parseInt($state.params.host_id) === id) {
$state.go("hosts", null, {reload: true});
} else {
$state.go($state.current.name, null, {reload: true});
}
Wait('stop');
});
};
// Prompt depends on having $rootScope.promptActionBtnClass available...
Prompt({
hdr: 'Delete Host',
body: body,
action: action,
actionText: 'DELETE',
});
$rootScope.promptActionBtnClass = 'Modal-errorButton';
};
$scope.toggleHost = function(event, host) {
try {
$(event.target).tooltip('hide');
} catch (e) {
// ignore
}
host.enabled = !host.enabled;
HostManageService.put(host).then(function(){
$state.go($state.current, null, {reload: true});
});
};
$scope.smartInventory = function() {
$state.go('inventories.addSmartInventory');
};
$scope.systemTracking = function(){
var hostIds = _.map($scope.hostsSelected, (host) => host.id);
$state.go('systemTracking', {
inventoryId: $state.params.inventory_id,
hosts: $scope.hostsSelected,
hostIds: hostIds
});
};
$scope.setAdhocPattern = function(){
var pattern = _($scope.hostsSelected)
.map(function(item){
return item.name;
}).value().join(':');
$state.go('^.adhoc', {pattern: pattern});
};
$scope.copyMoveHost = function(id) {
$state.go('inventories.edit.hosts.copyMoveHost', {host_id: id});
};
}];

View File

@ -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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>' + i18n.sprintf(i18n._('View JSON examples at %s'), '<a href="http://www.json.org" target="_blank">www.json.org</a>') + '</p>' +
'<p>' + i18n.sprintf(i18n._('View YAML examples at %s'), '<a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a>') + '</p>',
dataTitle: i18n._('Host Variables'),
dataPlacement: 'right',
dataContainer: 'body'
},
inventory: {
type: 'hidden',
includeOnEdit: true,
includeOnAdd: true
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
ansible_facts: {
name: 'ansible_facts',
title: i18n._('Facts'),
skipGenerator: true
},
nested_groups: {
name: 'nested_groups',
ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts.edit.nested_groups')",
include: "NestedGroupListDefinition",
includeForm: "NestedGroupFormDefinition",
title: i18n._('Groups'),
iterator: 'nested_group',
listState: nestedGroupListState
},
insights: {
name: 'insights',
title: i18n._('Insights'),
skipGenerator: true
}
}
};
}];

View File

@ -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: '&#43; ADD HOST',
ngShow: 'canAdd',
dataPlacement: "top",
}
}
};

View File

@ -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();
}];

View File

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

View File

@ -10,7 +10,8 @@
* @description This form is for adding/editing a host on the inventory page
*/
export default ['i18n', function(i18n) {
export default ['i18n', 'nestedGroupListState',
function(i18n, nestedGroupListState) {
return {
addTitle: i18n._('CREATE HOST'),
@ -21,6 +22,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
}
}
};
}];

View 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'
}
}
};
}];

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
};
}]
};
}];

View File

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

View File

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

View File

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

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

View File

@ -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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>' + i18n.sprintf(i18n._('View JSON examples at %s'), '<a href="http://www.json.org" target="_blank">www.json.org</a>') + '</p>' +
'<p>' + i18n.sprintf(i18n._('View YAML examples at %s'), '<a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a>') + '</p>',
dataTitle: i18n._('Inventory Variables'),
dataPlacement: 'right',
dataContainer: 'body',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
permissions: {
name: 'permissions',
awToolTip: i18n._('Please save before assigning permissions'),
dataPlacement: 'top',
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/',
type: 'collection',
title: i18n._('Permissions'),
iterator: 'permission',
index: false,
open: false,
search: {
order_by: 'username'
},
actions: {
add: {
label: i18n._('Add'),
ngClick: "$state.go('.add')",
awToolTip: i18n._('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
fields: {
username: {
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: '&#43; ADD',
// ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
fields: {
name: {
label: i18n._('Name'),
// linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
}
}
}
}
};}];

View File

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

View File

@ -0,0 +1,16 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default [
function () {
function init() {
// $scope.insights
}
init();
}];

View 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>

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

View 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>

View File

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

View File

@ -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: '&#43; ' + 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'
}
},

View File

@ -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(){

View File

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

View File

@ -4,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()
});
}
]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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