Smart inventory implementation

This commit is contained in:
Michael Abashian 2017-05-22 16:55:17 -04:00
parent 5d867d51f4
commit 1176b9b057
62 changed files with 963 additions and 999 deletions

View File

@ -59,6 +59,7 @@
min-height: 40px;
}
.Form-title--is_smartinventory,
.Form-title--is_superuser,
.Form-title--is_system_auditor,
.Form-title--is_ldap_user,
@ -665,6 +666,19 @@ input[type='radio']:checked:before {
.noselect;
}
.Form-textInput--variableHeight {
height: inherit;
min-height: 30px;
}
.Form-variableHeightButtonGroup {
height: 100%;
}
.Form-lookupButton--variableHeight {
height: 100%;
}
@media only screen and (max-width: 650px) {
.Form-formGroup {
flex: 1 0 auto;

View File

@ -56,7 +56,7 @@ export default ['i18n', function(i18n) {
'used by Projects.') + '</dd>\n' +
'<dt>' + i18n._('Cloud') + '</dt>\n' +
'<dd>' + i18n._('Usernames, passwords, and access keys for authenticating to the specified cloud or infrastructure ' +
'provider. These are used for dynamic inventory sources and for cloud provisioning and deployment ' +
'provider. These are used for smart inventory sources and for cloud provisioning and deployment ' +
'in playbook runs.') + '</dd>\n' +
'</dl>\n',
dataTitle: i18n._('Kind'),

View File

@ -78,7 +78,7 @@ export default ['i18n', function(i18n) {
'used by Projects.') + '</dd>\n' +
'<dt>' + i18n._('Others (Cloud Providers)') + '</dt>\n' +
'<dd>' + i18n._('Usernames, passwords, and access keys for authenticating to the specified cloud or infrastructure ' +
'provider. These are used for dynamic inventory sources and for cloud provisioning and deployment ' +
'provider. These are used for smart inventory sources and for cloud provisioning and deployment ' +
'in playbook runs.') + '</dd>\n' +
'</dl>\n',
dataTitle: i18n._('Type'),

View File

@ -34,12 +34,12 @@ export default
function createCounts(data) {
scope.counts = _.map([
{
url: "/#/home/hosts",
url: "/#/hosts",
number: scope.data.hosts.total,
label: i18n._("Hosts")
},
{
url: "/#/home/hosts?host_search=has_active_failures:true",
url: "/#/hosts?host_search=has_active_failures:true",
number: scope.data.hosts.failed,
label: i18n._("Failed Hosts"),
isFailureCount: true

View File

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

@ -1,58 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', '$state', '$stateParams', 'GetBasePath', 'DashboardHostsList',
'generateList', 'SetStatus', 'DashboardHostService', '$rootScope', 'Dataset',
function($scope, $state, $stateParams, GetBasePath, DashboardHostsList,
GenerateList, SetStatus, DashboardHostService, $rootScope, Dataset) {
let list = DashboardHostsList;
init();
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$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();
});
}
function setJobStatus(){
_.forEach($scope.hosts, function(value) {
SetStatus({
scope: $scope,
host: value
});
});
}
$scope.editHost = function(id) {
$state.go('dashboardHosts.edit', { host_id: id });
};
$scope.toggleHostEnabled = function(host) {
if (host.has_inventory_sources){
return;
}
DashboardHostService.setHostStatus(host, !host.enabled)
.then(function(res) {
var index = _.findIndex($scope.hosts, function(o) {
return o.id === res.data.id;
});
$scope.hosts[index].enabled = res.data.enabled;
});
};
}
];

View File

@ -1,83 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n){
return {
editTitle: '{{host.name}}',
name: 'host',
well: true,
formLabelSize: 'col-lg-3',
formFieldSize: 'col-lg-9',
iterator: 'host',
basePath: 'hosts',
headerFields:{
enabled: {
//flag: 'host.enabled',
class: 'Form-header-field',
ngClick: 'toggleHostEnabled()',
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',
value: '{{name}}',
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'
},
description: {
label: i18n._('Description'),
type: 'text',
},
variables: {
label: i18n._('Variables'),
type: 'textarea',
rows: 6,
class: 'modal-input-xlarge Form-textArea Form-formGroup--fullWidth',
dataTitle: i18n._('Host Variables'),
dataPlacement: 'right',
dataContainer: 'body',
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>',
}
},
buttons: {
cancel: {
ngClick: 'formCancel()'
},
save: {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "host_form.$invalid"//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
}
}
};
}];

View File

@ -1,75 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default [ 'i18n', function(i18n){
return {
name: 'hosts',
iterator: 'host',
selectTitle: i18n._('Add Existing Hosts'),
editTitle: i18n._('HOSTS'),
listTitle: i18n._('HOSTS'),
index: false,
hover: true,
well: true,
emptyListText: i18n._('NO HOSTS FOUND'),
fields: {
status: {
basePath: 'unified_jobs',
label: '',
iconOnly: true,
nosort: true,
icon: 'icon-job-{{ host.active_failures }}',
awToolTip: '{{ host.badgeToolTip }}',
awTipPlacement: 'right',
dataPlacement: 'right',
awPopOver: '{{ host.job_status_html }}',
dataTitle: '{{host.job_status_title}}',
ngClick:'viewHost(host.id)',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus'
},
name: {
key: true,
label: i18n._('Name'),
columnClass: 'col-lg-5 col-md-5 col-sm-5 col-xs-8 ellipsis List-staticColumnAdjacent',
ngClick: 'editHost(host.id)'
},
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 }}"
},
enabled: {
label: i18n._('Status'),
columnClass: 'List-staticColumn--toggle',
type: 'toggle',
ngClick: 'toggleHostEnabled(host)',
nosort: true,
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'
}
},
fieldActions: {
columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-4',
edit: {
label: i18n._('Edit'),
ngClick: 'editHost(host.id)',
icon: 'icon-edit',
awToolTip: i18n._('Edit host'),
dataPlacement: 'top'
}
},
actions: {
}
};
}];

View File

@ -1,30 +0,0 @@
export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
setHostStatus: function(host, enabled){
var url = GetBasePath('hosts') + host.id;
Rest.setUrl(url);
return Rest.put({enabled: enabled, name: host.name})
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
putHost: function(host){
var url = GetBasePath('hosts') + host.id;
Rest.setUrl(url);
return Rest.put(host)
.success(function(data){
return data;
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
};
}];

View File

@ -1,60 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import list from './dashboard-hosts.list';
import form from './dashboard-hosts.form';
import listController from './dashboard-hosts-list.controller';
import editController from './dashboard-hosts-edit.controller';
import service from './dashboard-hosts.service';
import { N_ } from '../../../i18n';
export default
angular.module('dashboardHosts', [])
.service('DashboardHostService', service)
.factory('DashboardHostsList', list)
.factory('DashboardHostsForm', form)
.config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, stateDefinitionsProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
$stateProvider.state({
name: 'dashboardHosts',
url: '/home/hosts',
lazyLoad: () => stateDefinitions.generateTree({
urls: {
list: '/home/hosts'
},
parent: 'dashboardHosts',
modes: ['edit'],
list: 'DashboardHostsList',
form: 'DashboardHostsForm',
controllers: {
list: listController,
edit: editController
},
resolve: {
edit: {
host: ['Rest', '$stateParams', 'GetBasePath',
function(Rest, $stateParams, GetBasePath) {
let path = GetBasePath('hosts') + $stateParams.host_id;
Rest.setUrl(path);
return Rest.get();
}
]
}
},
data: {
activityStream: true,
activityStreamTarget: 'host'
},
ncyBreadcrumb: {
parent: 'dashboard',
label: N_("HOSTS")
},
})
});
}
]);

View File

@ -2,8 +2,7 @@ import dashboardCounts from './counts/main';
import dashboardGraphs from './graphs/main';
import dashboardLists from './lists/main';
import dashboardDirective from './dashboard.directive';
import dashboardHosts from './hosts/main';
export default
angular.module('dashboard', [dashboardHosts.name, dashboardCounts.name, dashboardGraphs.name, dashboardLists.name])
angular.module('dashboard', [dashboardCounts.name, dashboardGraphs.name, dashboardLists.name])
.directive('dashboard', dashboardDirective);

View File

@ -1,6 +1,5 @@
export default {
searchPrefix: 'credential',
name: 'inventories.edit.adhoc.credential',
url: '/credential',
data: {
formChildState: true

View File

@ -22,7 +22,7 @@ function adhocController($q, $scope, $stateParams,
var privateFn = {};
this.privateFn = privateFn;
var id = $stateParams.inventory_id,
var id = $stateParams.inventory_id ? $stateParams.inventory_id : $stateParams.smartinventory_id,
hostPattern = $stateParams.pattern;
// note: put any urls that the controller will use in here!!!!
@ -189,7 +189,7 @@ function adhocController($q, $scope, $stateParams,
// launch the job with the provided form data
$scope.launchJob = function () {
var adhocUrl = GetBasePath('inventory') + $stateParams.inventory_id +
var adhocUrl = GetBasePath('inventory') + id +
'/ad_hoc_commands/', fld, data={}, html;
html = '<form class="ng-valid ng-valid-required" ' +
@ -233,8 +233,7 @@ function adhocController($q, $scope, $stateParams,
];
}
// Launch the adhoc job
Rest.setUrl(GetBasePath('inventory') +
$stateParams.inventory_id + '/ad_hoc_commands/');
Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/');
Rest.post(data)
.success(function (data) {
Wait('stop');

View File

@ -18,7 +18,6 @@ export default {
data: {
formChildState: true
},
name: 'inventories.edit.adhoc',
views: {
'adhocForm@inventories': {
templateUrl: templateUrl('inventories/adhoc/adhoc'),

View File

@ -1,7 +1,6 @@
import { N_ } from '../../i18n';
export default {
name: "inventories.edit.completed_jobs",
url: "/completed_jobs",
params: {
completed_job_search: {
@ -14,7 +13,6 @@ export default {
}
},
ncyBreadcrumb: {
parent: "inventories.edit",
label: N_("COMPLETED JOBS")
},
views: {
@ -46,9 +44,11 @@ export default {
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;
let inventory_id = $stateParams.inventory_id ? $stateParams.inventory_id : $stateParams.smartinventory_id;
$stateParams[`${list.iterator}_search`].or__job__inventory = inventory_id;
$stateParams[`${list.iterator}_search`].or__adhoccommand__inventory = inventory_id;
$stateParams[`${list.iterator}_search`].or__inventoryupdate__inventory_source__inventory = inventory_id;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}

View File

@ -41,22 +41,17 @@ export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePat
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if(toState.name === 'hosts.addSmartInventory') {
$scope.enableSmartInventoryButton = false;
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 {
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.enableSmartInventoryButton = false;
}
});

View File

@ -5,8 +5,8 @@
*************************************************/
export default
['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host', '$rootScope',
function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host, $rootScope){
['$scope', '$state', '$stateParams', 'GenerateForm', 'ParseTypeChange', 'HostManageService', 'host', '$rootScope',
function($scope, $state, $stateParams, GenerateForm, ParseTypeChange, HostManageService, host, $rootScope){
$scope.parseType = 'yaml';
$scope.formCancel = function(){
$state.go('^', null, {reload: true});
@ -32,8 +32,8 @@
description: $scope.description,
enabled: $scope.host.enabled
};
DashboardHostService.putHost(host).then(function(){
$state.go('^', null, {reload: true});
HostManageService.put(host).then(function(){
$state.go('.', null, {reload: true});
});
};

View File

@ -10,8 +10,7 @@ export default ['i18n', function(i18n) {
iterator: 'host',
editTitle: '{{ selected_group }}',
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
nonstandardSearchParam: {
root: 'ansible_facts',
singleSearchParam: {
param: 'host_filter'
},
showTitle: false,
@ -67,13 +66,13 @@ export default ['i18n', function(i18n) {
dataType: "host",
class: 'InventoryManage-breakWord'
},
inventory_name: {
inventory: {
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 }}"
},
ngClick: "editInventory(host)"
}
},
fieldActions: {

View File

@ -0,0 +1,97 @@
<div class="tab-pane" id="hosts-panel">
<aw-limit-panels max-panels="2" panel-container="hosts-panel"></aw-limit-panels>
<div ui-view="form"></div>
<div 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')" translate>INVENTORIES</div>
<div class="Form-tab Form-tab--notitle is-selected" ng-click="$state.go('hosts')" translate>HOSTS</div>
</div>
</div>
</div>
<div>
<div class="List-actionHolder List-actionHolder--leftAlign">
<div class="List-actions">
<div ng-include="'/static/partials/shared/list-generator/list-actions.partial.html'"></div>
</div>
</div>
<div ng-hide="hosts.length === 0 && (searchTags | isEmpty)">
<smart-search
django-model="hosts"
search-size="col-lg-12 col-md-12 col-sm-12 col-xs-12"
single-search-param="host_filter"
base-path="hosts"
iterator="host"
dataset="host_dataset"
list="list"
collection="hosts"
default-params="host_default_params"
query-set="host_queryset"
search-tags="searchTags">
</smart-search>
</div>
<div class="row" ng-show="hosts.length === 0 && !(searchTags | isEmpty)">
<div class="col-lg-12 List-searchNoResults">No records matched your search.</div>
</div>
<div class="List-noItems" ng-show="hosts.length === 0 && (searchTags | isEmpty)">PLEASE ADD ITEMS TO THIS LIST</div>
<div class="list-table-container" ng-show="hosts.length > 0">
<table id="hosts_table" class="List-table table-no-border" is-extended="false">
<thead>
<tr class="List-tableHeaderRow">
<th base-path="hosts" collection="hosts" dataset="host_dataset" column-sort="" column-field="toggleHost" column-iterator="host" column-no-sort="true" column-label="" column-custom-class="List-staticColumn--toggle" query-set="host_queryset"></th>
<th base-path="hosts" collection="hosts" dataset="host_dataset" column-sort="" column-field="active_failures" column-iterator="host" column-no-sort="true" column-label="" column-custom-class="status-column List-staticColumn--smallStatus" query-set="host_queryset"></th>
<th base-path="hosts" collection="hosts" dataset="host_dataset" column-sort="" column-field="name" column-iterator="host" column-no-sort="undefined" column-label="Name" column-custom-class="col-lg-6 col-md-8 col-sm-8 col-xs-7" query-set="host_queryset"></th>
<th base-path="hosts" collection="hosts" dataset="host_dataset" column-sort="" column-field="inventory" column-iterator="host" column-no-sort="undefined" column-label="Inventory" column-custom-class="col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis" query-set="host_queryset"></th>
<th class="List-tableHeader List-tableHeader--actions actions-column col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-class="[host.active_class]" id="{{ host.id }}" class="List-tableRow host_class" ng-repeat="host in hosts track by host.id">
<td class="List-tableCell toggleHost-column List-staticColumn--toggle">
<div class="ScheduleToggle" ng-class="{'is-on': host.enabled, 'ScheduleToggle--disabled': host.has_inventory_sources}" aw-tool-tip="<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>" data-placement="right" data-tip-watch="undefined">
<button ng-disabled="host.has_inventory_sources" ng-show="host.enabled" class="ScheduleToggle-switch is-on" ng-click="toggleHost($event, host)">ON</button>
<button ng-disabled="host.has_inventory_sources" ng-show="!host.enabled" class="ScheduleToggle-switch" ng-click="toggleHost($event, host)">OFF</button>
</div>
</td>
<td class="List-tableCell active_failures-column status-column List-staticColumn--smallStatus">
<div class="host-name">
<a href="" ng-click="noop()" aw-tool-tip="{{ host.badgeToolTip }}" aw-pop-over="{{ host.job_status_html }}" data-placement="top" over-title="{{ host.job_status_title }}">
<i class="fa {{ 'fa icon-job-' + host.active_failures }}"></i>
</a>
</div>
</td>
<td class="List-tableCell name-column InventoryManage-breakWord col-lg-6 col-md-8 col-sm-8 col-xs-7">
<div class="host-name">
<a href="" ng-click="editHost(host.id)">{{host.name }}</a>
</div>
</td>
<td class="List-tableCell inventory_name-column col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis">
<div class="host-name">
<a href="" ng-click="editInventory(host)">{{host.inventory_name }}</a>
</div>
</td>
<td class="List-actionsContainer">
<div class="List-actionButtonCell List-tableCell">
<button id="edit-action" class="List-actionButton " ng-class="{'List-editButton--selected' : $stateParams['host_id'] == host.id}" data-placement="top" ng-click="editHost(host.id)" aw-tool-tip="Edit host" ng-show="host.summary_fields.user_capabilities.edit">
<i class="fa fa-pencil"></i>
</button>
<button id="view-action" class="List-actionButton " data-placement="top" ng-click="editHost(host.id)" aw-tool-tip="View host" ng-show="!host.summary_fields.user_capabilities.edit"><i class="fa fa-search-plus"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div><!-- table container -->
<paginate
base-path="hosts"
collection="hosts"
dataset="host_dataset"
iterator="host"
query-set="host_queryset">
</paginate>
</div>
</div>
</div>

View File

@ -39,22 +39,17 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if(toState.name === 'hosts.addSmartInventory') {
$scope.enableSmartInventoryButton = false;
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 {
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.enableSmartInventoryButton = false;
}
});
@ -131,6 +126,17 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
$state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify(stateParamsCopy)});
};
$scope.editInventory = function(host) {
if(host.summary_fields && host.summary_fields.inventory) {
if(host.summary_fields.inventory.kind && host.summary_fields.inventory.kind === 'smart') {
$state.go('inventories.editSmartInventory', {smartinventory_id: host.inventory});
}
else {
$state.go('inventories.edit', {inventory_id: host.inventory});
}
}
};
}
export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath',

View File

@ -11,13 +11,12 @@
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,
hostList.name
])
.factory('HostsForm', HostsForm)
.factory('HostsList', HostsList)

View File

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

@ -1,12 +0,0 @@
<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

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

View File

@ -1,179 +0,0 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', function(i18n) {
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'
},
//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

@ -37,11 +37,16 @@ export default ['i18n', function(i18n) {
name: {
key: true,
label: i18n._('Name'),
columnClass: 'col-md-5 col-sm-4 col-xs-6 List-staticColumnAdjacent',
columnClass: 'col-md-4 col-sm-3 col-xs-6 List-staticColumnAdjacent',
modalColumnClass: 'col-md-11',
awToolTip: "{{ inventory.description }}",
awTipPlacement: "top",
linkTo: '/#/inventories/basic_inventory/{{inventory.id}}'
ngClick: 'editInventory(inventory)'
},
kind: {
label: i18n._('Type'),
ngBind: 'inventory.kind_label',
columnClass: 'col-md-2 col-sm-2 hidden-xs'
},
organization: {
label: i18n._('Organization'),
@ -50,7 +55,7 @@ export default ['i18n', function(i18n) {
sourceModel: 'organization',
sourceField: 'name',
excludeModal: true,
columnClass: 'col-md-4 col-sm-2 hidden-xs'
columnClass: 'col-md-3 col-sm-2 hidden-xs'
}
},
@ -71,7 +76,6 @@ export default ['i18n', function(i18n) {
{
optionContent: i18n._('Smart Inventory'),
optionSref: 'inventories.addSmartInventory',
//TODO: this should have its own permission
ngShow: 'canAddInventory'
}
],
@ -81,18 +85,18 @@ export default ['i18n', function(i18n) {
fieldActions: {
columnClass: 'col-md-2 col-sm-4 col-xs-4',
columnClass: 'col-md-2 col-sm-3 col-xs-4',
edit: {
label: i18n._('Edit'),
ngClick: 'editInventory(inventory.id)',
ngClick: 'editInventory(inventory)',
awToolTip: i18n._('Edit inventory'),
dataPlacement: 'top',
ngShow: 'inventory.summary_fields.user_capabilities.edit'
},
view: {
label: i18n._('View'),
ngClick: 'editInventory(inventory.id)',
ngClick: 'editInventory(inventory)',
awToolTip: i18n._('View inventory'),
dataPlacement: 'top',
ngShow: '!inventory.summary_fields.user_capabilities.edit'

View File

@ -28,7 +28,7 @@ function InventoriesList($scope, $rootScope, $location,
});
$scope.$watchCollection(list.name, function(){
_.forEach($scope[list.name], buildStatusIndicators);
_.forEach($scope[list.name], processInventoryRow);
});
// Search init
@ -40,6 +40,11 @@ function InventoriesList($scope, $rootScope, $location,
}
function processInventoryRow(inventory) {
buildStatusIndicators(inventory);
buildInventoryTypeLabel(inventory);
}
function buildStatusIndicators(inventory){
inventory.launch_class = "";
if (inventory.has_inventory_sources) {
@ -71,6 +76,10 @@ function InventoriesList($scope, $rootScope, $location,
}
}
function buildInventoryTypeLabel(inventory) {
inventory.kind_label = inventory.kind === '' ? 'Inventory' : (inventory.kind === 'smart' ? 'Smart Inventory': 'Inventory');
}
function ellipsis(a) {
if (a.length > 20) {
return a.substr(0,20) + '...';
@ -249,12 +258,13 @@ function InventoriesList($scope, $rootScope, $location,
};
$scope.addInventory = function () {
$state.go('inventories.add');
};
$scope.editInventory = function (id) {
$state.go('inventories.edit', {inventory_id: id});
$scope.editInventory = function (inventory) {
if(inventory.kind && inventory.kind === 'smart') {
$state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id});
}
else {
$state.go('inventories.edit', {inventory_id: inventory.id});
}
};
$scope.manageInventory = function(id){

View File

@ -10,28 +10,25 @@ 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 hostGroups from './host-groups/main';
import { templateUrl } from '../shared/template-url/template-url.factory';
import { N_ } from '../i18n';
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';
import inventoryCompletedJobsRoute from './completed_jobs/completed_jobs.route';
import completedJobsRoute from './completed_jobs/completed_jobs.route';
import inventorySourceEditRoute from './sources/edit/sources-edit.route';
import inventorySourceAddRoute from './sources/add/sources-add.route';
import inventorySourceListRoute from './sources/list/sources-list.route';
import inventorySourceListScheduleRoute from './sources/list/schedule/sources-schedule.route';
import inventorySourceListScheduleAddRoute from './sources/list/schedule/sources-schedule-add.route';
import inventorySourceListScheduleEditRoute from './sources/list/schedule/sources-schedule-edit.route';
import inventoryAdhocCredential from './adhoc/adhoc-credential.route';
import adhocCredentialRoute from './adhoc/adhoc-credential.route';
import inventoryGroupsList from './groups/list/groups-list.route';
import inventoryGroupsAdd from './groups/add/groups-add.route';
import inventoryGroupsEdit from './groups/edit/groups-edit.route';
@ -39,6 +36,7 @@ import nestedGroups from './groups/nested-groups/nested-groups.route';
import nestedGroupsAdd from './groups/nested-groups/nested-groups-add.route';
import nestedHosts from './groups/nested-hosts/nested-hosts.route';
import inventoryHosts from './related-hosts/related-host.route';
import smartInventoryHosts from './smart-inventory/smart-inventory-hosts.route';
import inventoriesList from './inventories.route';
import inventoryHostsAdd from './related-hosts/add/host-add.route';
import inventoryHostsEdit from './related-hosts/edit/host-edit.route';
@ -50,6 +48,8 @@ import hostGroupsRoute from './host-groups/host-groups.route';
import hostGroupsAssociateRoute from './host-groups/host-groups-associate/host-groups-associate.route';
import inventorySourcesCredentialRoute from './sources/lookup/sources-lookup-credential.route';
import inventorySourcesInventoryScriptRoute from './sources/lookup/sources-lookup-inventory-script.route';
import SmartInventory from './smart-inventory/main';
import StandardInventory from './standard/main';
export default
angular.module('inventory', [
@ -59,15 +59,14 @@ angular.module('inventory', [
sources.name,
relatedHost.name,
inventoryCompletedJobs.name,
inventoryAdd.name,
inventoryEdit.name,
inventoryList.name,
ansibleFacts.name,
insights.name,
copyMove.name,
hostGroups.name
hostGroups.name,
SmartInventory.name,
StandardInventory.name
])
.factory('InventoryForm', InventoryForm)
.factory('InventoryList', InventoryList)
.service('InventoryManageService', InventoryManageService)
.config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider',
@ -79,7 +78,7 @@ angular.module('inventory', [
let basicInventoryAdd = stateDefinitions.generateTree({
name: 'inventories.add', // top-most node in the generated tree (will replace this state definition)
url: '/basic_inventory/add',
url: '/standard_inventory/add',
modes: ['add'],
form: 'InventoryForm',
controllers: {
@ -89,7 +88,7 @@ angular.module('inventory', [
let basicInventoryEdit = stateDefinitions.generateTree({
name: 'inventories.edit',
url: '/basic_inventory/:inventory_id',
url: '/standard_inventory/:inventory_id',
modes: ['edit'],
form: 'InventoryForm',
controllers: {
@ -102,9 +101,9 @@ angular.module('inventory', [
let smartInventoryAdd = stateDefinitions.generateTree({
name: 'inventories.addSmartInventory', // top-most node in the generated tree (will replace this state definition)
url: '/smart_inventory/add?hostfilter',
url: '/smart/add?hostfilter',
modes: ['add'],
form: 'SmartInventoryForm',
form: 'smartInventoryForm',
controllers: {
add: 'SmartInventoryAddController'
}
@ -112,11 +111,14 @@ angular.module('inventory', [
let smartInventoryEdit = stateDefinitions.generateTree({
name: 'inventories.editSmartInventory',
url: '/smart_inventory/:inventory_id',
url: '/smart/:smartinventory_id',
modes: ['edit'],
form: 'SmartInventoryForm',
form: 'smartInventoryForm',
controllers: {
edit: 'SmartInventoryEditController'
},
breadcrumbs: {
edit: '{{breadcrumb.inventory_name}}'
}
});
@ -169,6 +171,24 @@ angular.module('inventory', [
editSourceInventoryScript.name = 'inventories.edit.inventory_sources.edit.inventory_script';
editSourceInventoryScript.url = '/inventory_script';
let inventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute);
inventoryCompletedJobsRoute.name = 'inventories.edit.completed_jobs';
let smartInventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute);
smartInventoryCompletedJobsRoute.name = 'inventories.editSmartInventory.completed_jobs';
let inventoryAdhocRoute = _.cloneDeep(adHocRoute);
inventoryAdhocRoute.name = 'inventories.edit.adhoc';
let smartInventoryAdhocRoute = _.cloneDeep(adHocRoute);
smartInventoryAdhocRoute.name = 'inventories.editSmartInventory.adhoc';
let inventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute);
inventoryAdhocCredential.name = 'inventories.edit.adhoc.credential';
let smartInventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute);
smartInventoryAdhocCredential.name = 'inventories.editSmartInventory.adhoc.credential';
return Promise.all([
basicInventoryAdd,
basicInventoryEdit,
@ -180,8 +200,10 @@ angular.module('inventory', [
return result.concat(definition.states);
}, [
stateExtender.buildDefinition(inventoriesList),
stateExtender.buildDefinition(adHocRoute),
stateExtender.buildDefinition(inventoryAdhocRoute),
stateExtender.buildDefinition(smartInventoryAdhocRoute),
stateExtender.buildDefinition(inventoryAdhocCredential),
stateExtender.buildDefinition(smartInventoryAdhocCredential),
stateExtender.buildDefinition(inventorySourceListScheduleRoute),
stateExtender.buildDefinition(inventorySourceListScheduleAddRoute),
stateExtender.buildDefinition(inventorySourceListScheduleEditRoute),
@ -201,6 +223,7 @@ angular.module('inventory', [
stateExtender.buildDefinition(nestedHostsEdit),
stateExtender.buildDefinition(inventoryGroupsEditNestedHostsEditGroups),
stateExtender.buildDefinition(inventoryHosts),
stateExtender.buildDefinition(smartInventoryHosts),
stateExtender.buildDefinition(inventoryHostsAdd),
stateExtender.buildDefinition(inventoryHostsEdit),
stateExtender.buildDefinition(inventoryHostsEditGroups),
@ -208,6 +231,7 @@ angular.module('inventory', [
stateExtender.buildDefinition(inventorySourceAddRoute),
stateExtender.buildDefinition(inventorySourceEditRoute),
stateExtender.buildDefinition(inventoryCompletedJobsRoute),
stateExtender.buildDefinition(smartInventoryCompletedJobsRoute),
stateExtender.buildDefinition(addSourceCredential),
stateExtender.buildDefinition(addSourceInventoryScript),
stateExtender.buildDefinition(editSourceCredential),
@ -225,7 +249,6 @@ angular.module('inventory', [
list: 'HostsList',
form: 'HostsForm',
controllers: {
list: 'HostListController',
edit: 'HostEditController'
},
breadcrumbs: {
@ -245,23 +268,14 @@ angular.module('inventory', [
]
}
},
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;
},
templateUrl: templateUrl('inventories/hosts/hosts'),
controller: 'HostListController'
}
},
ncyBreadcrumb: {
label: N_('HOSTS')
}
});

View File

@ -5,8 +5,8 @@
*************************************************/
export default
['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host', '$rootScope',
function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host, $rootScope){
['$scope', '$state', '$stateParams', 'GenerateForm', 'ParseTypeChange', 'HostManageService', 'host', '$rootScope',
function($scope, $state, $stateParams, GenerateForm, ParseTypeChange, HostManageService, host, $rootScope){
$scope.parseType = 'yaml';
$scope.formCancel = function(){
$state.go('^', null, {reload: true});
@ -28,8 +28,8 @@
description: $scope.description,
enabled: $scope.host.enabled
};
DashboardHostService.putHost(host).then(function(){
$state.go('^', null, {reload: true});
HostManageService.put(host).then(function(){
$state.go('.', null, {reload: true});
});
};

View File

@ -5,14 +5,14 @@
*************************************************/
// import HostManageService from './../hosts/host.service';
export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePath',
export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostManageService', 'SetStatus',
function($scope, RelatedHostsListDefinition, $rootScope, GetBasePath,
function($scope, ListDefinition, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostManageService, SetStatus) {
let list = RelatedHostsListDefinition;
let list = ListDefinition;
init();
@ -42,22 +42,17 @@ export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePa
});
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if(toState.name === 'hosts.addSmartInventory') {
$scope.enableSmartInventoryButton = false;
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 {
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.enableSmartInventoryButton = false;
}
});
@ -142,7 +137,7 @@ export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePa
$scope.systemTracking = function(){
var hostIds = _.map($scope.hostsSelected, (host) => host.id);
$state.go('systemTracking', {
inventoryId: $state.params.inventory_id,
inventoryId: $state.params.inventory_id ? $state.params.inventory_id : $state.params.smartinventory_id,
hosts: $scope.hostsSelected,
hostIds: hostIds
});

View File

@ -109,14 +109,6 @@ export default {
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()",

View File

@ -19,8 +19,8 @@ export default {
},
views: {
'related': {
templateProvider: function(RelatedHostsListDefinition, generateList, $stateParams, GetBasePath) {
let list = _.cloneDeep(RelatedHostsListDefinition);
templateProvider: function(ListDefinition, generateList, $stateParams, GetBasePath) {
let list = _.cloneDeep(ListDefinition);
if($stateParams && $stateParams.group) {
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts';
}
@ -38,7 +38,12 @@ export default {
}
},
resolve: {
Dataset: ['RelatedHostsListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => {
let list = _.cloneDeep(RelatedHostsListDefinition);
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts';
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;
@ -58,7 +63,7 @@ export default {
// root context - provide all hosts in an inventory
InventoryManageService.rootHostsUrl($stateParams.inventory_id);
}],
hostsDataset: ['RelatedHostsListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => {
hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => {
let path = hostsUrl;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}],

View File

@ -11,7 +11,7 @@
*/
function SmartInventoryAdd($scope, $location,
GenerateForm, SmartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors,
GenerateForm, smartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
$state) {
@ -34,7 +34,7 @@ function SmartInventoryAdd($scope, $location,
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = SmartInventoryForm;
form = smartInventoryForm;
init();
@ -49,37 +49,39 @@ function SmartInventoryAdd($scope, $location,
$scope.parseType = 'yaml';
ParseTypeChange({
scope: $scope,
variable: 'variables',
variable: 'smartinventory_variables',
parse_variable: 'parseType',
field_id: 'smartinventory_variables'
field_id: 'smartinventory_smartinventory_variables'
});
$scope.dynamic_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : '';
$scope.smart_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : '';
}
// Save
$scope.formSave = function() {
Wait('start');
try {
var fld, json_data, data;
let fld, 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];
}
data[fld] = $scope[fld];
}
data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true);
let decodedHostFilter = decodeURIComponent($scope.smart_hosts.host_filter);
decodedHostFilter = decodedHostFilter.replace(/__icontains_DEFAULT/g, "__icontains");
decodedHostFilter = decodedHostFilter.replace(/__search_DEFAULT/g, "__search");
data.host_filter = decodedHostFilter;
data.kind = "smart";
Rest.setUrl(defaultUrl);
Rest.post(data)
.success(function(data) {
var inventory_id = data.id;
Wait('stop');
$location.path('/inventories/' + inventory_id);
$state.go('inventories.editSmartInventory', {smartinventory_id: inventory_id}, {reload: true});
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
@ -95,12 +97,12 @@ function SmartInventoryAdd($scope, $location,
};
$scope.formCancel = function() {
$state.go('hosts');
$state.go('inventories');
};
}
export default ['$scope', '$location',
'GenerateForm', 'SmartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert',
'GenerateForm', 'smartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange',
'Wait', 'ToJSON', '$state', SmartInventoryAdd
];

View File

@ -0,0 +1,103 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
function SmartInventoryEdit($scope, $location,
$stateParams, InventoryForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, $state, OrgAdminLookup, resourceData, $rootScope) {
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = InventoryForm,
inventory_id = $stateParams.smartinventory_id,
inventoryData = resourceData.data;
ClearScope();
init();
function init() {
ClearScope();
form.formLabelSize = null;
form.formFieldSize = null;
$scope.inventory_id = inventory_id;
$scope = angular.extend($scope, inventoryData);
$scope.smartinventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables);
$scope.organization_name = inventoryData.summary_fields.organization.name;
$scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.parseType = 'yaml';
$rootScope.$on('$stateChangeSuccess', function(event, toState) {
if(toState.name === 'inventories.editSmartInventory') {
ParseTypeChange({
scope: $scope,
variable: 'smartinventory_variables',
parse_variable: 'parseType',
field_id: 'smartinventory_smartinventory_variables'
});
}
});
OrgAdminLookup.checkForAdminAccess({organization: inventoryData.organization})
.then(function(canEditOrg){
$scope.canEditOrg = canEditOrg;
});
$scope.inventory_obj = inventoryData;
$rootScope.breadcrumb.inventory_name = inventoryData.name;
$scope.smart_hosts = {
host_filter: encodeURIComponent($scope.host_filter)
};
}
// Save
$scope.formSave = function() {
Wait('start');
let fld, data = {};
for (fld in form.fields) {
data[fld] = $scope[fld];
}
data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true);
data.host_filter = decodeURIComponent($scope.smart_hosts.host_filter);
data.kind = "smart";
Rest.setUrl(defaultUrl + inventory_id + '/');
Rest.put(data)
.success(function() {
Wait('stop');
$state.go($state.current, {}, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to update inventory. PUT returned status: ' + status
});
});
};
$scope.formCancel = function() {
$state.go('inventories');
};
}
export default [ '$scope', '$location',
'$stateParams', 'InventoryForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ToJSON', 'ParseVariableString',
'$state', 'OrgAdminLookup', 'resourceData', '$rootScope', SmartInventoryEdit
];

View File

@ -6,15 +6,15 @@
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';
import smartInventoryForm from './smart-inventory.form';
import smartInventoryHostFilter from './smart-inventory-host-filter/smart-inventory-host-filter.directive';
import hostFilterModal from './smart-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)
.factory('smartInventoryForm', smartInventoryForm)
.directive('smartInventoryHostFilter', smartInventoryHostFilter)
.directive('hostFilterModal', hostFilterModal);

View File

@ -4,7 +4,7 @@ export default ['templateUrl', function(templateUrl) {
scope: {
hostFilter: '='
},
templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal'),
templateUrl: templateUrl('inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal'),
link: function(scope, element) {
$('#host-filter-modal').on('hidden.bs.modal', function () {
@ -44,7 +44,7 @@ export default ['templateUrl', function(templateUrl) {
let hostList = _.cloneDeep(HostsList);
delete hostList.fields.toggleHost;
delete hostList.fields.active_failures;
delete hostList.fields.inventory_name;
delete hostList.fields.inventory;
let html = GenerateList.build({
list: hostList,
input_type: 'foobar',

View File

@ -0,0 +1,32 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$scope', 'QuerySet',
function($scope, qs) {
$scope.hostFilterTags = [];
$scope.$watch('hostFilter', function(){
$scope.hostFilterTags = [];
if($scope.hostFilter && $scope.hostFilter !== '') {
let hostFilterCopy = angular.copy($scope.hostFilter);
let searchParam = hostFilterCopy.host_filter.split('%20and%20');
delete hostFilterCopy.host_filter;
$.each(searchParam, function(index, param) {
let paramParts = decodeURIComponent(param).split(/=(.+)/);
paramParts[0] = paramParts[0].replace(/__icontains(_DEFAULT)?/g, "");
paramParts[0] = paramParts[0].replace(/__search(_DEFAULT)?/g, "");
let reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]);
$scope.hostFilterTags.push(reconstructedSearchString);
});
$scope.hostFilterTags = $scope.hostFilterTags.concat(qs.stripDefaultParams(hostFilterCopy));
}
});
}
];

View File

@ -4,7 +4,7 @@
* All Rights Reserved
*************************************************/
import dynamicInventoryHostFilterController from './dynamic-inventory-host-filter.controller';
import smartInventoryHostFilterController from './smart-inventory-host-filter.controller';
export default ['templateUrl', '$compile',
function(templateUrl, $compile) {
@ -13,8 +13,8 @@ export default ['templateUrl', '$compile',
hostFilter: '='
},
restrict: 'E',
templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter'),
controller: dynamicInventoryHostFilterController,
templateUrl: templateUrl('inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter'),
controller: smartInventoryHostFilterController,
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 Form-variableHeightButtonGroup">
<button type="button" class="Form-lookupButton Form-lookupButton--variableHeight btn btn-default" ng-click="openHostFilterModal()">
<i class="fa fa-search"></i>
</button>
</span>
<span class="form-control Form-textInput Form-textInput--variableHeight 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,63 @@
import { N_ } from '../../i18n';
export default {
name: "inventories.editSmartInventory.hosts",
url: "/hosts?{host_search:queryset}",
params: {
host_search: {
value: {
page_size: "20",
order_by: "name"
},
dynamic: true,
squash:""
}
},
ncyBreadcrumb: {
label: N_("HOSTS")
},
views: {
'related': {
templateProvider: function(ListDefinition, generateList) {
let list = _.cloneDeep(ListDefinition);
let html = generateList.build({
list: list,
mode: 'edit'
});
return html;
},
controller: 'RelatedHostListController'
}
},
resolve: {
ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => {
let list = _.cloneDeep(RelatedHostsListDefinition);
list.basePath = GetBasePath('inventory') + $stateParams.smartinventory_id + '/hosts';
delete list.actions.create;
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`]);
}
],
hostsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
return InventoryManageService.rootHostsUrl($stateParams.smartinventory_id);
}],
hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => {
let path = hostsUrl;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}],
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
return InventoryManageService.getInventory($stateParams.smartinventory_id).then(res => res.data);
}]
}
};

View File

@ -0,0 +1,169 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['i18n', 'InventoryCompletedJobsList', function(i18n, InventoryCompletedJobsList) {
var completed_jobs_object = {
name: 'completed_jobs',
index: false,
basePath: "unified_jobs",
title: i18n._('Completed Jobs'),
iterator: 'completed_job',
generateList: true,
skipGenerator: true,
search: {
"or__job__inventory": ''
},
ngClick: "$state.go('inventories.editSmartInventory.completed_jobs')"
};
let clone = _.clone(InventoryCompletedJobsList);
completed_jobs_object = angular.extend(clone, completed_jobs_object);
return {
addTitle: i18n._('NEW SMART INVENTORY'),
editTitle: '{{ name }}',
name: 'smartinventory',
basePath: 'inventory',
breadcrumbName: 'SMART INVENTORY',
stateTree: 'inventories',
activeEditState: 'inventories.editSmartInventory',
detailsClick: "$state.go('inventories.editSmartInventory')",
fields: {
name: {
label: i18n._('Name'),
type: 'text',
required: true,
capitalize: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
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'
},
smart_hosts: {
label: i18n._('Smart Hosts'),
type: 'custom',
control: '<smart-inventory-host-filter host-filter="smart_hosts"></smart-inventory-host-filter>',
basePath: 'hosts',
list: 'HostsList',
sourceModel: 'host',
sourceField: 'name',
required: true,
class: 'Form-formGroup--fullWidth'
},
smartinventory_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.smartinventory_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',
}
},
ngClick: "$state.go('inventories.editSmartInventory.permissions');"
},
hosts: {
name: 'hosts',
include: "RelatedHostsListDefinition",
title: i18n._('Hosts'),
iterator: 'host',
ngClick: "$state.go('inventories.editSmartInventory.hosts');",
skipGenerator: true
},
completed_jobs: completed_jobs_object
}
};
}];

View File

@ -95,7 +95,7 @@
$state.go('inventories.edit.inventory_sources.edit', {inventory_source_id: id});
};
$scope.deleteSource = function(inventory_source){
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the inventory source below from the inventory? Hosts generated by this inventory source will also be deleted.</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(inventory_source.name) + '</div>';
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the inventory source below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(inventory_source.name) + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
Wait('start');

View File

@ -30,9 +30,7 @@ function InventoriesEdit($scope, $location,
$scope = angular.extend($scope, inventoryData);
$scope.organization_name = inventoryData.summary_fields.organization.name;
$scope.inventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables);
$scope.parseType = 'yaml';
$rootScope.$on('$stateChangeSuccess', function(event, toState) {

View File

@ -31,7 +31,7 @@ function(i18n, InventoryCompletedJobsList) {
return {
addTitle: i18n._('NEW INVENTORY'),
addTitle: i18n._('NEW STANDARD INVENTORY'),
editTitle: '{{ inventory_name }}',
name: 'inventory',
basePath: 'inventory',

View File

@ -0,0 +1,16 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import inventoryAdd from './add/main';
import inventoryEdit from './edit/main';
import InventoryForm from './inventory.form';
export default
angular.module('standardInventory', [
inventoryAdd.name,
inventoryEdit.name
])
.factory('InventoryForm', InventoryForm);

View File

@ -55,6 +55,14 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
return null;
}
}
else if(key === 'inventory') {
if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') {
return '/#/inventories/smart_inventory/' + $scope.job.summary_fields.inventory.id;
}
else {
return '/#/inventories/standard_inventory/' + $scope.job.summary_fields.inventory.id;
}
}
else {
if ($scope.job.related[key]) {
return '/#/' + $scope.job.related[key]

View File

@ -163,9 +163,9 @@ export default [
Rest.setUrl(schedule.related.unified_job_template);
Rest.get().then( (res) => {
deferred.resolve({
name: 'inventoryManage.editGroup.schedules.edit',
name: 'inventories.edit.inventory_sources.edit.schedules.edit',
params: {
group_id: res.data.group,
inventory_source_id: res.data.id,
inventory_id: res.data.inventory,
schedule_id: schedule.id,
}
@ -198,23 +198,8 @@ export default [
}
function routeToScheduleForm(schedule){
buildStateMap(schedule).then( (state) =>{
$state.go(state.name, state.params).catch((err) =>{
// stateDefinition.lazyLoad'd state name matcher is not configured to match inventoryManage.* state names
// However, the stateDefinition.lazyLoad url matcher will load the correct tree.
// Expected error:
// Transition rejection error
// type: 4 // SUPERSEDED = 2, ABORTED = 3, INVALID = 4, IGNORED = 5, ERROR = 6
// detail : "Could not resolve 'inventoryManage.editGroup.schedules.edit' from state 'jobs.schedules'"
// message: "This transition is invalid"
if (err.type === 4 && err.detail.includes('inventoryManage.editGroup.schedules.edit')){
$location.path(`/inventories/${state.params.inventory_id}/manage/edit-group/${state.params.group_id}/schedules/${state.params.schedule_id}`);
}
else {
throw err;
}
});
buildStateMap(schedule).then((state) =>{
$state.go(state.name, state.params);
});
}
};

View File

@ -1464,6 +1464,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
html+= "<span class=\"Form-title--is_external_account\" "+
"ng-show='external_account'>{{external_account}}</span>";
}
if(this.form.name === "smartinventory"){
html+= "<span class=\"Form-title--is_smartinventory\" "+
">" + i18n._("Smart Inventory") + "</span>";
}
html += "</div>\n";
html += "<div class=\"Form-header--fields\">";
if(this.form.headerFields){

View File

@ -198,15 +198,13 @@ export default ['$compile', 'Attr', 'Icon',
list.searchSize = 'col-lg-7 col-md-12 col-sm-12 col-xs-12';
}
if (options.showSearch === undefined || options.showSearch === true) {
let nonstandardSearchParam = list.nonstandardSearchParam && list.nonstandardSearchParam.param ? list.nonstandardSearchParam.param : undefined;
let nonstandardSearchParamRoot = list.nonstandardSearchParam && list.nonstandardSearchParam.root ? list.nonstandardSearchParam.root : undefined;
let singleSearchParam = list.singleSearchParam && list.singleSearchParam.param ? `single-search-param="${list.singleSearchParam.param}"` : '';
html += `
<div ng-hide="${list.name}.length === 0 && (searchTags | isEmpty)">
<smart-search
django-model="${list.name}"
search-size="${list.searchSize}"
nonstandard-search-param="${nonstandardSearchParam}"
nonstandard-search-param-root="${nonstandardSearchParamRoot}"
${singleSearchParam}
base-path="${list.basePath || list.name}"
iterator="${list.iterator}"
dataset="${list.iterator}_dataset"

View File

@ -29,23 +29,6 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
return defer.promise;
},
/* @extendme
// example:
// retrieving options from a polymorphic model (unified_job)
getPolymorphicModelOptions(path, name) {
let defer = $q.defer(),
paths = {
project_update: GetBasePath('project_update'),
inventory_update: GetBasePath('inventory_update'),
job: GetBasePath('jobs'),
ad_hoc_command: GetBasePath('ad_hoc_commands'),
system_job: GetBasePath('system_jobs')
};
defer.all( // for each getCommonModelOptions() );
return defer.promise;
},
*/
// encodes ui-router params from {operand__key__comparator: value} pairs to API-consumable URL
encodeQueryset(params) {
let queryset;
@ -57,10 +40,11 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
function encodeTerm(value, key){
let root = key.split("__")[0].replace(/^-/, '');
key = key.toString().replace(/__icontains_DEFAULT/g, "__icontains");
key = key.toString().replace(/__search_DEFAULT/g, "__search");
key = key.replace(/__icontains_DEFAULT/g, "__icontains");
key = key.replace(/__search_DEFAULT/g, "__search");
value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains");
value = value.toString().replace(/__search_DEFAULT/g, "__search");
if (Array.isArray(value)){
value = _.uniq(_.flattenDeep(value));
@ -72,26 +56,10 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
concated += `${key}=${item}&`;
});
if(root === 'ansible_facts') {
return `host_filter=${encodeURIComponent(concated)}&`;
}
else {
return concated;
}
return concated;
}
else {
if(value && typeof value === 'string') {
value = decodeURIComponent(value).replace(/"|'/g, "");
}
if(root === 'ansible_facts') {
let foobar = encodeURIComponent(`${key}=${value}`);
return `host_filter=${foobar}&`;
}
else {
return `${key}=${value}&`;
}
return `${key}=${value}&`;
}
}
},
@ -144,7 +112,12 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
}
}
return {[paramString] : encodeURIComponent(valueString)};
if(params.singleSearchParam) {
return {[params.singleSearchParam]: encodeURIComponent(paramString + "=" + valueString)};
}
else {
return {[paramString] : encodeURIComponent(valueString)};
}
},
// decodes a django queryset param into a ui smart-search tag or set of tags
decodeParam(value, key){
@ -155,8 +128,8 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
return decodeURIComponent(`${searchString}`);
}
else {
key = key.replace(/__icontains_DEFAULT/g, "");
key = key.replace(/__search_DEFAULT/g, "");
key = key.toString().replace(/__icontains_DEFAULT/g, "");
key = key.toString().replace(/__search_DEFAULT/g, "");
let split = key.split('__');
let decodedParam = searchString;
let exclude = false;

View File

@ -6,31 +6,31 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
queryset,
stateChangeSuccessListener;
if($scope.defaultParams) {
defaults = $scope.defaultParams;
}
else {
// steps through the current tree of $state configurations, grabs default search params
defaults = _.find($state.$current.path, (step) => {
if(step && step.params && step.params.hasOwnProperty(`${$scope.iterator}_search`)){
return step.params.hasOwnProperty(`${$scope.iterator}_search`);
}
}).params[`${$scope.iterator}_search`].config.value;
}
if($scope.querySet) {
queryset = _.cloneDeep($scope.querySet);
}
else {
queryset = $state.params[`${$scope.iterator}_search`];
}
// build $scope.tags from $stateParams.QuerySet, build fieldset key
init();
function init() {
if($scope.defaultParams) {
defaults = $scope.defaultParams;
}
else {
// steps through the current tree of $state configurations, grabs default search params
defaults = _.find($state.$current.path, (step) => {
if(step && step.params && step.params.hasOwnProperty(`${$scope.iterator}_search`)){
return step.params.hasOwnProperty(`${$scope.iterator}_search`);
}
}).params[`${$scope.iterator}_search`].config.value;
}
if($scope.querySet) {
queryset = _.cloneDeep($scope.querySet);
}
else {
queryset = $state.params[`${$scope.iterator}_search`];
}
path = GetBasePath($scope.basePath) || $scope.basePath;
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
generateSearchTags();
qs.initFieldset(path, $scope.djangoModel).then((data) => {
$scope.models = data.models;
$scope.options = data.options.data;
@ -69,7 +69,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
});
$scope.searchTerm = null;
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
generateSearchTags();
}
}
});
@ -86,7 +86,53 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
});
}
function setDefaults(term) {
function generateSearchTags() {
$scope.searchTags = [];
let querysetCopy = angular.copy(queryset);
if($scope.singleSearchParam && querysetCopy[$scope.singleSearchParam]) {
let searchParam = querysetCopy[$scope.singleSearchParam].split('%20and%20');
delete querysetCopy[$scope.singleSearchParam];
$.each(searchParam, function(index, param) {
let paramParts = decodeURIComponent(param).split(/=(.+)/);
let reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]);
$scope.searchTags.push(reconstructedSearchString);
});
}
$scope.searchTags = $scope.searchTags.concat(qs.stripDefaultParams(querysetCopy, defaults));
}
function revertSearch(queryToBeRestored) {
queryset = queryToBeRestored;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views
// but will register new $stateParams[$scope.iterator + '_search'] terms
if(!$scope.querySet) {
$state.go('.', {
[$scope.iterator + '_search']: queryset }, {notify: false});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTerm = null;
generateSearchTags();
}
function searchWithoutKey(term) {
if($scope.singleSearchParam) {
return {
[$scope.singleSearchParam]: encodeURIComponent("search=" + term)
};
}
return {
search: encodeURIComponent(term)
};
@ -96,100 +142,8 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
$scope.showKeyPane = !$scope.showKeyPane;
};
$scope.clearAll = function(){
let cleared = _.cloneDeep(defaults);
delete cleared.page;
queryset = cleared;
if(!$scope.querySet) {
$state.go('.', {[$scope.iterator + '_search']: queryset}, {notify: false});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
};
// remove tag, merge new queryset, $state.go
$scope.remove = function(index) {
let tagToRemove = $scope.searchTags.splice(index, 1)[0],
termParts = SmartSearchService.splitTermIntoParts(tagToRemove),
removed;
let removeFromQuerySet = function(set) {
_.each(removed, (value, key) => {
if (Array.isArray(set[key])){
_.remove(set[key], (item) => item === value);
// If the array is now empty, remove that key
if(set[key].length === 0) {
delete set[key];
}
}
else {
delete set[key];
}
});
};
if (termParts.length === 1) {
removed = setDefaults(tagToRemove);
}
else {
let root = termParts[0].split(".")[0].replace(/^-/, '');
let encodeParams = {
term: tagToRemove
};
if($scope.models[$scope.list.name]) {
if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
encodeParams.relatedSearchTerm = true;
}
else {
encodeParams.searchTerm = true;
}
removed = qs.encodeParam(encodeParams);
}
else if(_.contains($scope.models[$scope.list.name].related, root)) {
encodeParams.relatedSearchTerm = true;
removed = qs.encodeParam(encodeParams);
}
else if($scope.nonstandardSearchParam && $scope.nonstandardSearchParamRoot && root === $scope.nonstandardSearchParamRoot) {
removed = qs.encodeParam(encodeParams);
}
else {
removed = setDefaults(termParts[termParts.length-1]);
}
}
else {
removed = setDefaults(termParts[termParts.length-1]);
}
}
removeFromQuerySet(queryset);
if(!$scope.querySet) {
$state.go('.', {
[$scope.iterator + '_search']: queryset }, {notify: false}).then(function(){
// ISSUE: for some reason deleting a tag from a list in a modal does not
// remove the param from $stateParams. Here we'll manually check to make sure
// that that happened and remove it if it didn't.
removeFromQuerySet($stateParams[`${$scope.iterator}_search`]);
});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
};
// add a search tag, merge new queryset, $state.go()
$scope.add = function(terms) {
$scope.addTerm = function(terms) {
let params = {},
origQueryset = _.clone(queryset);
@ -209,40 +163,53 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
}
else {
if(a) {
return [a,b];
if($scope.singleSearchParam) {
return a + "%20and%20" + b;
}
else {
return [a,b];
}
}
}
}
// if only a value is provided, search using default keys
if (termParts.length === 1) {
params = _.merge(params, setDefaults(term), combineSameSearches);
} else {
// Figure out if this is a search term
let root = termParts[0].split(".")[0].replace(/^-/, '');
if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
if($scope.singleSearchParam) {
if (termParts.length === 1) {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
}
else {
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true, singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false}), combineSameSearches);
}
}
else {
// if only a value is provided, search using default keys
if (termParts.length === 1) {
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
} else {
// Figure out if this is a search term
let root = termParts[0].split(".")[0].replace(/^-/, '');
if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches);
}
else {
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches);
}
}
// The related fields need to also be checked for related searches.
// The related fields for the search are retrieved from the API
// options endpoint, and are stored in the $scope.model. FYI, the
// Django search model is what sets the related fields on the model.
else if(_.contains($scope.models[$scope.list.name].related, root)) {
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches);
}
// Its not a search term or a related search term - treat it as a string
else {
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches);
params = _.merge(params, searchWithoutKey(term), combineSameSearches);
}
}
// The related fields need to also be checked for related searches.
// The related fields for the search are retrieved from the API
// options endpoint, and are stored in the $scope.model. FYI, the
// Django search model is what sets the related fields on the model.
else if(_.contains($scope.models[$scope.list.name].related, root)) {
params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches);
}
else if($scope.nonstandardSearchParam && $scope.nonstandardSearchParamRoot && root === $scope.nonstandardSearchParamRoot) {
params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches);
}
// Its not a search term or a related search term - treat it as a string
else {
params = _.merge(params, setDefaults(term), combineSameSearches);
}
}
}
});
@ -254,6 +221,14 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
return object[key];
}
else {
if($scope.singleSearchParam) {
if(!object[key]) {
return sourceValue;
}
else {
return object[key] + "%20and%20" + sourceValue;
}
}
// Start the array of keys
return [object[key], sourceValue];
}
@ -287,22 +262,90 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
$scope.collection = res.data.results;
})
.catch(function() {
$scope.revertSearch(origQueryset);
revertSearch(origQueryset);
});
$scope.searchTerm = null;
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
generateSearchTags();
}
};
$scope.revertSearch = function(queryToBeRestored) {
queryset = queryToBeRestored;
// https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic
// This transition will not reload controllers/resolves/views
// but will register new $stateParams[$scope.iterator + '_search'] terms
// remove tag, merge new queryset, $state.go
$scope.removeTerm = function(index) {
let tagToRemove = $scope.searchTags.splice(index, 1)[0],
termParts = SmartSearchService.splitTermIntoParts(tagToRemove),
removed;
let removeFromQuerySet = function(set) {
_.each(removed, (value, key) => {
if (Array.isArray(set[key])){
_.remove(set[key], (item) => item === value);
// If the array is now empty, remove that key
if(set[key].length === 0) {
delete set[key];
}
}
else {
if($scope.singleSearchParam && set[$scope.singleSearchParam] && set[$scope.singleSearchParam].includes("%20and%20")) {
let searchParamParts = set[$scope.singleSearchParam].split("%20and%20");
var index = searchParamParts.indexOf(value);
if (index !== -1) {
searchParamParts.splice(index, 1);
}
set[$scope.singleSearchParam] = searchParamParts.join("%20and%20");
}
else {
delete set[key];
}
}
});
};
if (termParts.length === 1) {
removed = searchWithoutKey(tagToRemove);
}
else {
let root = termParts[0].split(".")[0].replace(/^-/, '');
let encodeParams = {
term: tagToRemove,
singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false
};
if($scope.models[$scope.list.name]) {
if($scope.singleSearchParam) {
removed = qs.encodeParam(encodeParams);
}
else if(_.has($scope.models[$scope.list.name].base, root)) {
if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') {
encodeParams.relatedSearchTerm = true;
}
else {
encodeParams.searchTerm = true;
}
removed = qs.encodeParam(encodeParams);
}
else if(_.contains($scope.models[$scope.list.name].related, root)) {
encodeParams.relatedSearchTerm = true;
removed = qs.encodeParam(encodeParams);
}
else {
removed = searchWithoutKey(termParts[termParts.length-1]);
}
}
else {
removed = searchWithoutKey(termParts[termParts.length-1]);
}
}
removeFromQuerySet(queryset);
if(!$scope.querySet) {
$state.go('.', {
[$scope.iterator + '_search']: queryset }, {notify: false});
[$scope.iterator + '_search']: queryset }, {notify: false}).then(function(){
// ISSUE: for some reason deleting a tag from a list in a modal does not
// remove the param from $stateParams. Here we'll manually check to make sure
// that that happened and remove it if it didn't.
removeFromQuerySet($stateParams[`${$scope.iterator}_search`]);
});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
@ -312,7 +355,23 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
$scope.collection = res.data.results;
});
$scope.searchTerm = null;
generateSearchTags();
};
$scope.clearAllTerms = function(){
let cleared = _.cloneDeep(defaults);
delete cleared.page;
queryset = cleared;
if(!$scope.querySet) {
$state.go('.', {[$scope.iterator + '_search']: queryset}, {notify: false});
}
qs.search(path, queryset).then((res) => {
if($scope.querySet) {
$scope.querySet = queryset;
}
$scope.dataset = res.data;
$scope.collection = res.data.results;
});
$scope.searchTags = qs.stripDefaultParams(queryset, defaults);
};
}

View File

@ -18,8 +18,7 @@ export default ['templateUrl',
disableSearch: '=',
defaultParams: '=',
querySet: '=',
nonstandardSearchParam: '@',
nonstandardSearchParamRoot: '@'
singleSearchParam: '@'
},
controller: 'SmartSearchController',
templateUrl: templateUrl('shared/smart-search/smart-search')

View File

@ -3,11 +3,11 @@
<div class="SmartSearch-bar">
<div class="SmartSearch-searchTermContainer">
<!-- string search input -->
<form name="smartSearch" class="SmartSearch-form" aw-enter-key="add(searchTerm)" novalidate>
<form name="smartSearch" class="SmartSearch-form" aw-enter-key="addTerm(searchTerm)" novalidate>
<input class="SmartSearch-input" ng-model="searchTerm" placeholder="{{searchPlaceholder}}"
ng-disabled="disableSearch">
</form>
<div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="add(searchTerm)">
<div type="submit" class="SmartSearch-searchButton" ng-disabled="!searchTerm" ng-click="addTerm(searchTerm)">
<i class="fa fa-search"></i>
</div>
</div>
@ -20,14 +20,14 @@
<div class="SmartSearch-tagSection">
<div class="SmartSearch-flexContainer">
<div class="SmartSearch-tagContainer" ng-repeat="tag in searchTags track by $index">
<div class="SmartSearch-deleteContainer" ng-click="remove($index)">
<div class="SmartSearch-deleteContainer" ng-click="removeTerm($index)">
<i class="fa fa-times SmartSearch-tagDelete"></i>
</div>
<div class="SmartSearch-tag SmartSearch-tag--deletable">
<span class="SmartSearch-name">{{tag}}</span>
</div>
</div>
<a href class="SmartSearch-clearAll" ng-click="clearAll()" ng-show="!(searchTags | isEmpty)" translate>CLEAR ALL</a>
<a href class="SmartSearch-clearAll" ng-click="clearAllTerms()" ng-show="!(searchTags | isEmpty)" translate>CLEAR ALL</a>
</div>
</div>
</div>

View File

@ -54,7 +54,17 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
$scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';
$scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : '';
$scope.job_template_url = '/#/templates/' + data.unified_job_template;
$scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : '';
if($scope.inventory_name && data.inventory && data.summary_fields.inventory && data.summary_fields.inventory.kind) {
if(data.summary_fields.inventory.kind === '') {
$scope.inventory_url = '/#/inventories/standard_inventory' + data.inventory;
}
else if(data.summary_fields.inventory.kind === 'smart') {
$scope.inventory_url = '/#/inventories/smart_inventory' + data.inventory;
}
}
else {
$scope.inventory_url = '';
}
$scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : '';
$scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : '';
$scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : '';

View File

@ -8,7 +8,7 @@
}
.LabelList-tagContainer,
.LabelList-tagContainer,
.LabelList-seeMoreLess {
display: flex;
max-width: 200px;
@ -23,6 +23,7 @@
background-color: @default-list-header-bg;
margin-right: 5px;
max-width: 100%;
display: inline-block;
}
.LabelList-seeMoreLess {
@ -89,4 +90,4 @@
.List-tableCell.col-md-4 {
padding-left: 40px;
}
}
}

View File

@ -10,9 +10,15 @@ describe('Controller: jobResultsController', () => {
statusSocket = function() {
var fn = function() {};
return fn;
}
};
jobData = {
related: {}
related: {},
summary_fields: {
inventory: {
id: null,
kind: ''
}
}
};
jobDataOptions = {
actions: {
@ -85,7 +91,7 @@ describe('Controller: jobResultsController', () => {
$provide.value('ParseVariableString', ParseVariableString);
$provide.value('jobResultsService', jobResultsService);
$provide.value('eventQueue', eventQueue);
$provide.value('Dataset', Dataset)
$provide.value('Dataset', Dataset);
$provide.value('Rest', Rest);
$provide.value('$state', $state);
$provide.value('QuerySet', QuerySet);
@ -140,7 +146,7 @@ describe('Controller: jobResultsController', () => {
eventQueue.populate.and
.returnValue(populateResolve);
jobResultsService.getJobData = function(blah) {
jobResultsService.getJobData = function() {
var deferred = $q.defer();
deferred.resolve({});
return deferred.promise;
@ -200,12 +206,17 @@ describe('Controller: jobResultsController', () => {
"network_credential": "api/v1/credentials/14",
};
jobData.summary_fields.inventory = {
id: 12,
kind: ''
};
bootstrapTest();
});
it('should transform related links and set to scope var', () => {
expect($scope.created_by_link).toBe('/#/users/12');
expect($scope.inventory_link).toBe('/#/inventories/12');
expect($scope.inventory_link).toBe('/#/inventories/standard_inventory/12');
expect($scope.project_link).toBe('/#/projects/12');
expect($scope.machine_credential_link).toBe('/#/credentials/12');
expect($scope.cloud_credential_link).toBe('/#/credentials/13');