Merge remote-tracking branch 'tower/release_3.2.2' into devel

This commit is contained in:
Matthew Jones
2017-12-13 12:25:47 -05:00
144 changed files with 9292 additions and 9012 deletions

View File

@@ -458,7 +458,6 @@
align-items: center;
justify-content: center;
border:1px solid @field-border;
border-right: 0px;
}
.Form-lookupButton:hover {
@@ -470,7 +469,6 @@
.Form-lookupButton:active,
.Form-lookupButton:focus {
border: 1px solid @field-border;
border-right: 0px;
}
.CodeMirror {

View File

@@ -149,7 +149,7 @@ table.ui-datepicker-calendar {
.ui-widget-content {
background-image: none;
background-color: @default-secondary-bg;
background-color: @default-bg;
a,
a:visited,

View File

@@ -248,8 +248,7 @@ table, tbody {
}
.List-buttonDefault[disabled] {
color: @d7grey;
border-color: @d7grey;
opacity: 0.65;
}
.List-searchDropdown {

View File

@@ -72,7 +72,7 @@ function AtFormController (eventService, strings) {
const data = vm.components
.filter(component => component.category === 'input')
.reduce((values, component) => {
if (!component.state._value) {
if (component.state._value === undefined) {
return values;
}

View File

@@ -84,6 +84,7 @@ function AtInputLookupController (baseInputController, $q, $state) {
scope.state._touched = true;
if (scope.state._displayValue === '' && !scope.state._required) {
scope.state._value = null;
return vm.check({ isValid: true });
}

View File

@@ -62,13 +62,20 @@ function requestWithCache (config) {
* @yields {boolean} - Indicating a match has been found. If so, the results
* are set on the model.
*/
function search (params, config) {
function search (params = {}, config = {}) {
const req = {
method: 'GET',
url: this.path,
params
url: this.path
};
if (typeof params === 'string') {
req.url = `?params`;
} else if (Array.isArray(params)) {
req.url += `?${params.join('&')}`;
} else {
req.params = params;
}
return $http(req)
.then(({ data }) => {
if (!data.count) {

View File

@@ -205,7 +205,8 @@ export default [
scope: $scope.$parent,
variable: name,
parse_variable: 'parseType',
field_id: form.formDef.name + '_' + name
field_id: form.formDef.name + '_' + name,
readOnly: $scope.$parent.configDataResolve[name] && $scope.$parent.configDataResolve[name].disabled ? true : false
});
$scope.parseTypeChange('parseType', name);
}

View File

@@ -37,8 +37,14 @@ export default ['Rest', 'Wait',
callback: 'loadCredentialKindOptions'
});
$scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs");
$scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector");
const docs_url = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/credential_types.html#getting-started-with-credential-types';
const docs_help_text = `<br><br><a href=${docs_url}>Getting Started with Credential Types</a>`;
const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs.");
const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector.");
$scope.inputs_help_text = api_inputs_help_text + docs_help_text;
$scope.injectors_help_text = api_injectors_help_text + docs_help_text;
if (!options.actions.POST) {
$state.go("^");

View File

@@ -36,8 +36,14 @@ export default ['Rest', 'Wait',
callback: 'choicesReadyCredentialTypes'
});
$scope.inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs");
$scope.injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector");
const docs_url = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/credential_types.html#getting-started-with-credential-types';
const docs_help_text = `<br><br><a href=${docs_url}>Getting Started with Credential Types</a>`;
const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs.");
const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector.");
$scope.inputs_help_text = api_inputs_help_text + docs_help_text;
$scope.injectors_help_text = api_injectors_help_text + docs_help_text;
});
}

View File

@@ -11,7 +11,7 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', '
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset,
credentialType, i18n, Credential, CredentialsStrings) {
let credential = new Credential();
const credential = new Credential();
var list = CredentialList,
defaultUrl = GetBasePath('credentials');
@@ -48,9 +48,25 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', '
return;
}
$scope[list.name].forEach(credential => {
credential.kind = credentialType.match('id', credential.credential_type).name;
});
const params = $scope[list.name]
.reduce((accumulator, credential) => {
accumulator.push(credential.credential_type);
return accumulator;
}, [])
.filter((id, i, array) => array.indexOf(id) === i)
.map(id => `or__id=${id}`);
credentialType.search(params)
.then(found => {
if (!found) {
return;
}
$scope[list.name].forEach(credential => {
credential.kind = credentialType.match('id', credential.credential_type).name;
});
});
}
// iterate over the list and add fields like type label, after the

View File

@@ -9,11 +9,12 @@
'adjustGraphSize',
'templateUrl',
'i18n',
'moment',
'jobStatusGraphData',
JobStatusGraph
];
function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataService) {
function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, moment, graphDataService) {
return {
restrict: 'E',
scope: {
@@ -72,11 +73,11 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe
}
});
if(period==="day") {
timeFormat="%H:%M";
if(period === "day") {
timeFormat="H:M";
}
else {
timeFormat = '%m/%d';
timeFormat = "MMM D";
}
graphData.map(function(series) {
series.values = series.values.map(function(d) {
@@ -93,7 +94,8 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
.showLegend(false) //Show the legend, allowing users to turn on/off line series.
.showYAxis(true) //Show the y-axis
.showXAxis(true); //Show the x-axis
.showXAxis(true) //Show the x-axis
.margin({ right: 32 });
job_status_chart.interactiveLayer.tooltip.fixedTop(-10); //distance from the top of the chart to tooltip
job_status_chart.interactiveLayer.tooltip.distance(-1); //distance from interactive line to tooltip
@@ -101,8 +103,15 @@ function JobStatusGraph($window, adjustGraphSize, templateUrl, i18n, graphDataSe
job_status_chart.xAxis
.axisLabel(i18n._("TIME"))//.showMaxMin(true)
.tickFormat(function(d) {
var dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : '';
const dx = graphData[0].values[d] && graphData[0].values[d].x || 0;
if (!dx) {
return '';
}
const tickDate = new Date(Number(dx + '000'));
return moment(tickDate).format(timeFormat);
});
job_status_chart.yAxis //Chart y-axis settings

View File

@@ -104,12 +104,14 @@ export default ['i18n', function(i18n) {
smart_inventory: {
mode: 'all',
ngClick: "smartInventory()",
awToolTip: i18n._("Create a new Smart Inventory from search results."),
awToolTip: "{{ smartInventoryButtonTooltip }}",
dataTipWatch: 'smartInventoryButtonTooltip',
actionClass: 'btn List-buttonDefault',
buttonContent: i18n._('SMART INVENTORY'),
ngShow: 'canAdd && (hosts.length > 0 || !(searchTags | isEmpty))',
dataPlacement: "top",
ngDisabled: '!enableSmartInventoryButton'
ngDisabled: '!enableSmartInventoryButton',
showTipWhenDisabled: true
}
}
};

View File

@@ -7,7 +7,7 @@
function HostsList($scope, HostsList, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostsService, SetStatus, canAdd, $transitions) {
HostsService, SetStatus, canAdd, $transitions, InventoryHostsStrings, HostsList) {
let list = HostsList;
@@ -16,6 +16,7 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
function init(){
$scope.canAdd = canAdd;
$scope.enableSmartInventoryButton = false;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
// Search init
$scope.list = list;
@@ -37,14 +38,16 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
if(trans.params('to') && trans.params('to').host_search) {
let hasMoreThanDefaultKeys = false;
angular.forEach(trans.params('to').host_search, function(value, key) {
if(key !== 'order_by' && key !== 'page_size') {
if(key !== 'order_by' && key !== 'page_size' && key !== 'page') {
hasMoreThanDefaultKeys = true;
}
});
$scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false;
$scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
else {
$scope.enableSmartInventoryButton = false;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
});
@@ -83,20 +86,7 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
};
$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)});
$state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify({"host_filter":`${$state.params.host_search.host_filter}`})});
};
$scope.editInventory = function(host) {
@@ -114,5 +104,5 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath,
export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostsService', 'SetStatus', 'canAdd', '$transitions', HostsList
'HostsService', 'SetStatus', 'canAdd', '$transitions', 'InventoryHostsStrings', HostsList
];

View File

@@ -141,9 +141,11 @@
} else {
$state.go($state.current, reloadListStateParams, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
setTimeout(function(){
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
}, 1000);
});
break;
default:
@@ -153,9 +155,11 @@
} else {
$state.go($state.current, reloadListStateParams, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
setTimeout(function(){
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
}, 1000);
});
}
};

View File

@@ -5,7 +5,7 @@
<div class="Modal-title ng-binding">
<span translate>Delete Group</span>
<a href="" id="awp-promote" href=""
aw-pop-over="<dl><dt>Delete</dt><dd>Deletes groups and hosts associated with the group being deleted. If a group or host is associated with other groups, it will still exist within those groups. Otherwise, the associated groups and hosts will no longer appear in the inventory.</dd>\n<dt style='margin-top: 5px;'>Promote</dt><dd>Groups and hosts associated with the group being removed will be promoted root level. Note: groups already associated with other groups cannot be promoted.</dd></dl>\n" aw-tool-tip="Click for help"
aw-pop-over="<dl><dt>Delete</dt><dd>Deletes groups and hosts associated with the group being deleted. If a group or host is associated with other groups, it will still exist within those groups. Otherwise, the associated groups and hosts will no longer appear in the inventory.</dd><dt style='margin-top: 5px;'>Promote</dt><dd>Groups and hosts associated with the group being removed will be promoted root level. Note: groups already associated with other groups cannot be promoted.</dd></dl>" aw-tool-tip="Click for help"
data-placement="right"
data-container="body"
data-title="Delete Group"

View File

@@ -3,13 +3,13 @@
<div class="modal-content Modal-content">
<div class="Modal-header">
<div class="Modal-title ng-binding">
<translate>Disassociate Group</translate>
<translate>Disassociate Group From Group</translate>
<a href="" id="awp-promote" href=""
aw-pop-over="<dl><dt>Disassociate</dt><dd>Disassociates this group from the currently targeted parent group.</dd></dl>"
aw-tool-tip="Click for help"
data-placement="right"
data-container="body"
data-title="Disassociate Group"
data-title="Disassociate Group From Group"
class="help-link">
<i class="fa fa-question-circle"></i>
</a>

View File

@@ -3,13 +3,13 @@
<div class="modal-content Modal-content">
<div class="Modal-header">
<div class="Modal-title ng-binding">
<translate>Disassociate Host</translate>
<translate>Disassociate Host From Group</translate>
<a href="" id="awp-promote" href=""
aw-pop-over="<dl><dt>Disassociate</dt><dd>Disassociates this host from the currently targeted parent group.</dd></dl>"
aw-tool-tip="Click for help"
data-placement="right"
data-container="body"
data-title="Disassociate Host"
data-title="Disassociate Host From Group"
class="help-link">
<i class="fa fa-question-circle"></i>
</a>

View File

@@ -6,11 +6,11 @@
export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostsService', 'SetStatus', 'canAdd', 'GroupsService', 'ProcessErrors', 'groupData', 'inventoryData',
'HostsService', 'SetStatus', 'canAdd', 'GroupsService', 'ProcessErrors', 'groupData', 'inventoryData', 'InventoryHostsStrings',
'$transitions',
function($scope, NestedHostsListDefinition, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostsService, SetStatus, canAdd, GroupsService, ProcessErrors, groupData, inventoryData,
HostsService, SetStatus, canAdd, GroupsService, ProcessErrors, groupData, inventoryData, InventoryHostsStrings,
$transitions) {
let list = NestedHostsListDefinition;
@@ -21,6 +21,7 @@ export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePat
$scope.canAdd = canAdd;
$scope.enableSmartInventoryButton = false;
$scope.disassociateFrom = groupData;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
// Search init
$scope.list = list;
@@ -52,14 +53,16 @@ export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePat
if(trans.params('to') && trans.params('to').host_search) {
let hasMoreThanDefaultKeys = false;
angular.forEach(trans.params('to').host_search, function(value, key) {
if(key !== 'order_by' && key !== 'page_size') {
if(key !== 'order_by' && key !== 'page_size' && key !== 'page') {
hasMoreThanDefaultKeys = true;
}
});
$scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false;
$scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
else {
$scope.enableSmartInventoryButton = false;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
});

View File

@@ -7,10 +7,10 @@
// import HostsService from './../hosts/host.service';
export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait',
'HostsService', 'SetStatus', 'canAdd', 'i18n', '$transitions',
'HostsService', 'SetStatus', 'canAdd', 'i18n', 'InventoryHostsStrings', '$transitions',
function($scope, ListDefinition, $rootScope, GetBasePath,
rbacUiControlService, Dataset, $state, $filter, Prompt, Wait,
HostsService, SetStatus, canAdd, i18n, $transitions) {
HostsService, SetStatus, canAdd, i18n, InventoryHostsStrings, $transitions) {
let list = ListDefinition;
@@ -19,6 +19,7 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
function init(){
$scope.canAdd = canAdd;
$scope.enableSmartInventoryButton = false;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
// Search init
$scope.list = list;
@@ -45,14 +46,16 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
if(trans.params('to') && trans.params('to').host_search) {
let hasMoreThanDefaultKeys = false;
angular.forEach(trans.params('to').host_search, function(value, key) {
if(key !== 'order_by' && key !== 'page_size') {
if(key !== 'order_by' && key !== 'page_size' && key !== 'page') {
hasMoreThanDefaultKeys = true;
}
});
$scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false;
$scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
else {
$scope.enableSmartInventoryButton = false;
$scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS');
}
});
@@ -89,7 +92,12 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath',
$state.go('inventories.edit.hosts.add');
};
$scope.editHost = function(host){
$state.go('.edit', {inventory_id: host.inventory_id, host_id: host.id});
if($state.includes('inventories.edit.hosts')) {
$state.go('inventories.edit.hosts.edit', {host_id: host.id});
}
else if($state.includes('inventories.editSmartInventory.hosts')) {
$state.go('inventories.editSmartInventory.hosts.edit', {host_id: host.id});
}
};
$scope.goToInsights = function(host){
$state.go('inventories.edit.hosts.edit.insights', {inventory_id: host.inventory_id, host_id:host.id});

View File

@@ -3,13 +3,13 @@
<div class="modal-content Modal-content">
<div class="Modal-header">
<div class="Modal-title ng-binding">
<translate>Disassociate Group</translate>
<translate>Disassociate Host From Group</translate>
<a href="" id="awp-promote" href=""
aw-pop-over="<dl><dt>Disassociate</dt><dd>Disassociates this group from the currently targeted parent group.</dd></dl>"
aw-pop-over="<dl><dt>Disassociate</dt><dd>Disassociates this host from the currently targeted parent group.</dd></dl>"
aw-tool-tip="Click for help"
data-placement="right"
data-container="body"
data-title="Disassociate Group"
data-title="Disassociate Host From Group"
class="help-link">
<i class="fa fa-question-circle"></i>
</a>

View File

@@ -198,7 +198,10 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition',
$scope.group_by = $scope.group_by_choices;
$scope.groupByPopOver = i18n._("Specify which groups to create automatically. Group names will be created similar to the options selected. If blank, all groups above are created. Refer to Ansible Tower documentation for more detail.");
$scope.instanceFilterPopOver = i18n._("Provide a comma-separated list of filter expressions. Hosts are imported when all of the filters match. Refer to Ansible Tower documentation for more detail.");
}
}
if( _.get($scope, 'source') === 'tower' || _.get($scope.source, 'value') === 'tower') {
$scope.instanceFilterPopOver = i18n._("Provide the named URL encoded name or id of the remote Tower inventory to be imported.");
}
CreateSelect2({
element: '#inventory_source_group_by',
multiple: true,

View File

@@ -275,7 +275,7 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString',
i18n._("for a complete list of supported filters.") + "</p>";
}
if( _.get($scope, 'source') === 'vmware' || _.get($scope.source, 'value') === 'vmware') {
if( _.get($scope, 'source') === 'vmware' || _.get($scope.source, 'value') === 'vmware') {
add_new = true;
$scope.group_by_choices = (inventorySourceData.group_by) ? inventorySourceData.group_by.split(',')
.map((i) => ({name: i, label: i, value: i})) : [];
@@ -283,6 +283,9 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString',
$scope.groupByPopOver = i18n._(`Specify which groups to create automatically. Group names will be created similar to the options selected. If blank, all groups above are created. Refer to Ansible Tower documentation for more detail.`);
$scope.instanceFilterPopOver = i18n._(`Provide a comma-separated list of filter expressions. Hosts are imported when all of the filters match. Refer to Ansible Tower documentation for more detail.`);
}
if( _.get($scope, 'source') === 'tower' || _.get($scope.source, 'value') === 'tower') {
$scope.instanceFilterPopOver = i18n._(`Provide the named URL encoded name or id of the remote Tower inventory to be imported.`);
}
CreateSelect2({
element: '#inventory_source_group_by',
multiple: true,

View File

@@ -9,13 +9,13 @@
'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
'GetSyncStatusMsg', 'Dataset', 'Find', 'QuerySet',
'inventoryData', '$filter', 'Prompt', 'Wait', 'SourcesService', 'inventorySourceOptions',
'canAdd', 'hasSyncableSources', 'i18n', 'InventoryHostsStrings', 'InventorySourceModel',
'canAdd', 'hasSyncableSources', 'i18n', 'InventoryHostsStrings', 'InventorySourceModel', 'ProcessErrors',
function($scope, $rootScope, $state, $stateParams, SourcesListDefinition,
InventoryUpdate, CancelSourceUpdate,
ViewUpdateStatus, rbacUiControlService, GetBasePath, GetSyncStatusMsg,
Dataset, Find, qs, inventoryData, $filter, Prompt,
Wait, SourcesService, inventorySourceOptions, canAdd, hasSyncableSources, i18n,
InventoryHostsStrings, InventorySource){
InventoryHostsStrings, InventorySource, ProcessErrors){
let inventorySource = new InventorySource();
@@ -120,24 +120,58 @@
$state.go('inventories.edit.inventory_sources.edit', {inventory_source_id: id});
};
$scope.deleteSource = function(inventory_source){
var body = '<div class=\"Prompt-bodyQuery\">' + i18n._('Confirm that you want to permanently delete the inventory source below from the inventory. Deleting this inventory source also deletes its associated groups and hosts.') + '</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(inventory_source.name) + '</div>';
var action = function(){
delete $rootScope.promptActionBtnClass;
$rootScope.promptActionBtnClass = "Modal-errorButton--sourcesDelete";
Wait('start');
SourcesService.delete(inventory_source.id).then(() => {
let hostDelete = SourcesService.deleteHosts(inventory_source.id).catch(({data, status}) => {
$('#prompt-modal').modal('hide');
let reloadListStateParams = null;
if($scope.inventory_sources.length === 1 && $state.params.inventory_source_search && !_.isEmpty($state.params.inventory_source_search.page) && $state.params.inventory_source_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.inventory_source_search.page = (parseInt(reloadListStateParams.inventory_source_search.page)-1).toString();
}
if (parseInt($state.params.inventory_source_id) === inventory_source.id) {
$state.go('^', reloadListStateParams, {reload: true});
} else {
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
ProcessErrors($scope, data, status, null,
{
hdr: i18n._('Error!'),
msg: i18n._('There was an error deleting inventory source hosts. Returned status: ') +
status
});
});
let groupDelete = SourcesService.deleteGroups(inventory_source.id).catch(({data, status}) => {
$('#prompt-modal').modal('hide');
Wait('stop');
ProcessErrors($scope, data, status, null,
{
hdr: i18n._('Error!'),
msg: i18n._('There was an error deleting inventory source groups. Returned status: ') +
status
});
});
Promise.all([hostDelete, groupDelete]).then(() => {
SourcesService.delete(inventory_source.id).then(() => {
$('#prompt-modal').modal('hide');
delete $rootScope.promptActionBtnClass;
let reloadListStateParams = null;
if($scope.inventory_sources.length === 1 && $state.params.inventory_source_search && !_.isEmpty($state.params.inventory_source_search.page) && $state.params.inventory_source_search.page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
reloadListStateParams.inventory_source_search.page = (parseInt(reloadListStateParams.inventory_source_search.page)-1).toString();
}
if (parseInt($state.params.inventory_source_id) === inventory_source.id) {
$state.go('^', reloadListStateParams, {reload: true});
} else {
$state.go('.', reloadListStateParams, {reload: true});
}
Wait('stop');
})
.catch(({data, status}) => {
$('#prompt-modal').modal('hide');
Wait('stop');
ProcessErrors($scope, data, status, null,
{
hdr: i18n._('Error!'),
msg: i18n._('There was an error deleting inventory source. Returned status: ') +
status
});
});
});
};
inventorySource.getDependentResourceCounts(inventory_source.id)

View File

@@ -144,7 +144,7 @@ return {
instance_filters: {
label: i18n._("Instance Filters"),
type: 'text',
ngShow: "source && (source.value == 'ec2' || source.value == 'vmware')",
ngShow: "source && (source.value == 'ec2' || source.value == 'vmware' || source.value == 'tower')",
dataTitle: i18n._('Instance Filters'),
dataPlacement: 'right',
awPopOverWatch: 'instanceFilterPopOver',

View File

@@ -120,6 +120,34 @@ export default
source = source && source.value ? source.value : '';
if(source === 'ec2'){
return _.map(group_by, 'value').join(',');
}
if(source === 'vmware'){
group_by = _.map(group_by, (i) => {return i.value;});
$("#inventory_source_group_by").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){
group_by.push(option.title);
});
group_by = (Array.isArray(group_by)) ? _.uniq(group_by).join() : "";
return group_by;
}
else {
return;
}
},
deleteHosts(id) {
this.url = GetBasePath('inventory_sources') + id + '/hosts/';
Rest.setUrl(this.url);
return Rest.destroy()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally();
},
deleteGroups(id) {
this.url = GetBasePath('inventory_sources') + id + '/groups/';
Rest.setUrl(this.url);
return Rest.destroy()
.success(this.success.bind(this))
.error(this.error.bind(this))
.finally();
}
if(source === 'vmware'){
group_by = _.map(group_by, (i) => {return i.value;});
@@ -134,4 +162,4 @@ export default
}
}
};
}];
}];

View File

@@ -28,6 +28,11 @@ function InventoryHostsStrings (BaseString) {
MISSING_PERMISSIONS: t.s('You do not have sufficient permissions to edit the host filter.')
}
};
ns.smartinventorybutton = {
DISABLED_INSTRUCTIONS: "Please enter at least one search term to create a new Smart Inventory.",
ENABLED_INSTRUCTIONS: "Create a new Smart Inventory from search results."
};
}
InventoryHostsStrings.$inject = ['BaseStringService'];

View File

@@ -165,7 +165,7 @@
color: @default-interface-txt;
display: inline-block;
white-space: pre-wrap;
word-break: break-word;
word-break: break-all;
width:100%;
background-color: @default-secondary-bg;
}

View File

@@ -93,7 +93,7 @@
}
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });

View File

@@ -27,7 +27,6 @@ export default
// scope.$emit('CancelJob');
// scope.$destroy();
},
icon: "fa-times",
"class": "btn btn-default",
"id": "password-cancel-button"
},{
@@ -36,7 +35,6 @@ export default
scope.$emit(callback);
$('#password-modal').dialog('close');
},
icon: "fa-check",
"class": "btn btn-primary",
"id": "password-accept-button"
}];
@@ -46,7 +44,7 @@ export default
scope: scope,
buttons: buttons,
width: 620,
height: 700, //(scope.passwords.length > 1) ? 700 : 500,
height: "auto",
minWidth: 500,
title: 'Launch Configuration',
callback: 'DialogReady',

View File

@@ -13,12 +13,10 @@
border-radius: 5px;
border: 1px solid @default-link;
background-color: @default-link;
width: 42px;
margin-top: 2px;
cursor: pointer;
display: flex;
height: 18px;
overflow: hidden;
display: inline-block;
min-width: 42px;
&.ScheduleToggle--disabled {
cursor: not-allowed;
@@ -42,10 +40,11 @@
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
padding-top: 0px;
margin-top: 0px;
border-top: 0px;
border-bottom: 0px;
border-right: 0px;
display: inline-block;
height: 22px;
}
.ScheduleToggle.is-on {

View File

@@ -94,7 +94,7 @@ angular.module('ModalDialog', ['Utilities'])
ww = $(document).width();
wh = $(document).height();
x = (width > ww) ? ww - 10 : width;
y = (height > wh) ? wh - 10 : height;
y = height === "auto" ? "auto" : ((height > wh) ? wh - 10 : height);
// Create the modal
$('#' + id).dialog({

View File

@@ -636,6 +636,22 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
if (!multiple) {
config.minimumResultsForSearch = 1;
config.matcher = function(params, data) {
// If there are no search terms, return all of the data
if ($.trim(params.term) === '' &&
data.text.indexOf("Choose an") === -1) {
return data;
}
// Do not display the item if there is no 'text' property
if (typeof data.text === 'undefined' ||
data.text.indexOf("Choose an") > -1) {
return null;
}
// Return `null` if the term should not be displayed
return null;
};
}
}

View File

@@ -19,15 +19,35 @@ export default ['templateUrl', function(templateUrl) {
element.find('.modal-body').append(clone);
scope.init();
scope.init(element);
});
$('#form-modal').modal('show');
},
controller: ['$scope', '$state', function($scope, $state) {
controller: ['$scope', '$state', 'EventService', function($scope, $state, eventService) {
let listeners, modal;
$scope.init = function() {
function clickIsOutsideModal(e) {
const m = modal.getBoundingClientRect();
const cx = e.clientX;
const cy = e.clientY;
if (cx < m.left || cx > m.right || cy > m.bottom || cy < m.top) {
return true;
}
return false;
}
function clickToHide(event) {
if (clickIsOutsideModal(event)) {
$scope.cancelForm();
}
}
$scope.init = function(el) {
let list = $scope.list;
[modal] = el.find('.modal-dialog');
if($state.params.selected) {
let selection = $scope[list.name].find(({id}) => id === parseInt($state.params.selected));
$scope.currentSelection = _.pick(selection, 'id', 'name');
@@ -37,6 +57,10 @@ export default ['templateUrl', function(templateUrl) {
});
$scope.modalTitle = list.iterator.replace(/_/g, ' ');
listeners = eventService.addListeners([
[window, 'click', clickToHide]
]);
};
function selectRowIfPresent(){
@@ -51,6 +75,7 @@ export default ['templateUrl', function(templateUrl) {
}
$scope.saveForm = function() {
eventService.remove(listeners);
let list = $scope.list;
if($scope.currentSelection.name !== null) {
$scope.$parent[`${list.iterator}_name`] = $scope.currentSelection.name;
@@ -60,6 +85,7 @@ export default ['templateUrl', function(templateUrl) {
};
$scope.cancelForm = function() {
eventService.remove(listeners);
$state.go('^');
};

View File

@@ -97,6 +97,10 @@
color: @btn-txt-sel;
}
.Modal-errorButton--sourcesDelete:hover{
cursor: not-allowed;
}
.Modal-primaryButton {
background-color: @default-link;
color: @default-bg;

View File

@@ -29,43 +29,48 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
return defer.promise;
},
replaceDefaultFlags (value) {
value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains");
value = value.toString().replace(/__search_DEFAULT/g, "__search");
return value;
},
replaceEncodedTokens(value) {
return decodeURIComponent(value).replace(/"|'/g, "");
},
encodeTerms (values, key) {
key = this.replaceDefaultFlags(key);
if (!Array.isArray(values)) {
values = this.replaceEncodedTokens(values);
return `${key}=${values}`;
}
return values
.map(value => {
value = this.replaceDefaultFlags(value);
value = this.replaceEncodedTokens(value);
return `${key}=${value}`;
})
.join('&');
},
// encodes ui-router params from {operand__key__comparator: value} pairs to API-consumable URL
encodeQueryset(params) {
let queryset;
queryset = _.reduce(params, (result, value, key) => {
return result + encodeTerm(value, key);
}, '');
queryset = queryset.substring(0, queryset.length - 1);
return angular.isObject(params) ? `?${queryset}` : '';
function encodeTerm(value, key){
key = key.toString().replace(/__icontains_DEFAULT/g, "__icontains");
key = key.toString().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));
let concated = '';
angular.forEach(value, function(item){
if(item && typeof item === 'string') {
item = decodeURIComponent(item).replace(/"|'/g, "");
}
concated += `${key}=${item}&`;
});
return concated;
}
else {
if(value && typeof value === 'string') {
value = decodeURIComponent(value).replace(/"|'/g, "");
}
return `${key}=${value}&`;
}
if (typeof params !== 'object') {
return '';
}
return _.reduce(params, (result, value, key) => {
if (result !== '?') {
result += '&';
}
return result += this.encodeTerms(value, key);
}, '?');
},
// encodes a ui smart-search param to a django-friendly param
// operand:key:comparator:value => {operand__key__comparator: value}
@@ -127,7 +132,7 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
}
if(params.singleSearchParam) {
return {[params.singleSearchParam]: encodeURIComponent(paramString + "=" + valueString)};
return {[params.singleSearchParam]: paramString + "=" + valueString};
}
else {
return {[paramString] : encodeURIComponent(valueString)};

View File

@@ -140,7 +140,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
function searchWithoutKey(term) {
if($scope.singleSearchParam) {
return {
[$scope.singleSearchParam]: encodeURIComponent("search=" + term)
[$scope.singleSearchParam]: "search=" + encodeURIComponent(term)
};
}
return {
@@ -328,6 +328,10 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
else {
if($scope.singleSearchParam && set[$scope.singleSearchParam] && set[$scope.singleSearchParam].includes("%20and%20")) {
let searchParamParts = set[$scope.singleSearchParam].split("%20and%20");
// The value side of each paramPart might have been encoded in SmartSearch.splitFilterIntoTerms
_.each(searchParamParts, (paramPart, paramPartIndex) => {
searchParamParts[paramPartIndex] = decodeURIComponent(paramPart);
});
var index = searchParamParts.indexOf(value);
if (index !== -1) {
searchParamParts.splice(index, 1);

View File

@@ -341,7 +341,6 @@
Rest.post(data)
.then(({data}) => {
Wait('stop');
if (data.related && data.related.callback) {
Alert('Callback URL',
`Host callbacks are enabled for this template. The callback URL is:

View File

@@ -501,7 +501,7 @@ export default
$scope.removeTemplateSaveSuccess();
}
$scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
Wait('stop');
if (data.related &&
data.related.callback) {
Alert('Callback URL',
@@ -610,6 +610,7 @@ export default
}
$q.all(defers)
.then(function() {
Wait('stop');
saveCompleted();
});
});

View File

@@ -157,13 +157,13 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
view_survey: {
ngClick: 'editSurvey()',
awFeature: 'surveys',
ngShow: '($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\')) && survey_exists && !(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
ngShow: '($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')) && survey_exists && !(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
label: i18n._('View Survey'),
class: 'Form-primaryButton'
},
add_survey: {
ngClick: 'addSurvey()',
ngShow: '!survey_exists && ($state.includes(\'templates.addWorkflowJobTemplate\') || $state.includes(\'templates.editWorkflowJobTemplate\'))',
ngShow: '!survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))',
awFeature: 'surveys',
awToolTip: '{{surveyTooltip}}',
dataPlacement: 'top',
@@ -173,7 +173,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
edit_survey: {
ngClick: 'editSurvey()',
awFeature: 'surveys',
ngShow: 'survey_exists && ($state.includes(\'templates.addWorkflowJobTemplate\') || $state.includes(\'templates.editWorkflowJobTemplate\'))',
ngShow: 'survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))',
label: i18n._('Edit Survey'),
class: 'Form-primaryButton',
awToolTip: '{{surveyTooltip}}',
@@ -181,7 +181,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
},
workflow_editor: {
ngClick: 'openWorkflowMaker()',
ngShow: '$state.includes(\'templates.addWorkflowJobTemplate\') || $state.includes(\'templates.editWorkflowJobTemplate\')',
ngShow: '$state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')',
awToolTip: '{{workflowEditorTooltip}}',
dataPlacement: 'top',
label: i18n._('Workflow Editor'),

View File

@@ -21,7 +21,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
restrict: 'E',
link: function(scope, element) {
let margin = {top: 20, right: 20, bottom: 20, left: 20},
let marginLeft = 20,
i = 0,
nodeW = 180,
nodeH = 60,
@@ -36,7 +36,8 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
line,
zoomObj,
baseSvg,
svgGroup;
svgGroup,
graphLoaded;
scope.dimensionsSet = false;
@@ -75,7 +76,8 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
);
svgGroup = baseSvg.append("g")
.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")");
.attr("id", "aw-workflow-chart-g")
.attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")");
}
function calcAvailableScreenSpace() {
@@ -158,7 +160,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
let scale = d3.event.scale,
translation = d3.event.translate;
translation = [translation[0] + (margin.left*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)];
translation = [translation[0] + (marginLeft*scale), translation[1] + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)];
svgGroup.attr("transform", "translate(" + translation + ")scale(" + scale + ")");
@@ -177,7 +179,7 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
translateX = unscaledOffsetX*scale - ((scale*windowWidth)-windowWidth)/2,
translateY = unscaledOffsetY*scale - ((scale*windowHeight)-windowHeight)/2;
svgGroup.attr("transform", "translate(" + [translateX + (margin.left*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")");
svgGroup.attr("transform", "translate(" + [translateX + (marginLeft*scale), translateY + ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scale)] + ")scale(" + scale + ")");
zoomObj.scale(scale);
zoomObj.translate([translateX, translateY]);
}
@@ -200,12 +202,36 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
}
function resetZoomAndPan() {
svgGroup.attr("transform", "translate(" + margin.left + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")");
svgGroup.attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - rootH/2 - startNodeOffsetY) + ")scale(" + 1 + ")");
// Update the zoomObj
zoomObj.scale(1);
zoomObj.translate([0,0]);
}
function zoomToFitChart() {
let graphDimensions = d3.select('#aw-workflow-chart-g')[0][0].getBoundingClientRect(),
startNodeDimensions = d3.select('.WorkflowChart-rootNode')[0][0].getBoundingClientRect(),
availableScreenSpace = calcAvailableScreenSpace(),
currentZoomValue = zoomObj.scale(),
unscaledH = graphDimensions.height/currentZoomValue,
unscaledW = graphDimensions.width/currentZoomValue,
scaleNeededForMaxHeight = (availableScreenSpace.height)/unscaledH,
scaleNeededForMaxWidth = (availableScreenSpace.width - marginLeft)/unscaledW,
lowerScale = Math.min(scaleNeededForMaxHeight, scaleNeededForMaxWidth),
scaleToFit = lowerScale < 0.5 ? 0.5 : (lowerScale > 2 ? 2 : Math.floor(lowerScale * 10)/10),
startNodeOffsetFromGraphCenter = Math.round((((rootH/2) + (startNodeDimensions.top/currentZoomValue)) - ((graphDimensions.top/currentZoomValue) + (unscaledH/2)))*scaleToFit);
manualZoom(scaleToFit*100);
scope.workflowZoomed({
zoom: scaleToFit
});
svgGroup.attr("transform", "translate(" + marginLeft + "," + (windowHeight/2 - (nodeH*scaleToFit/2) + startNodeOffsetFromGraphCenter) + ")scale(" + scaleToFit + ")");
zoomObj.translate([marginLeft - scaleToFit*marginLeft, windowHeight/2 - (nodeH*scaleToFit/2) + startNodeOffsetFromGraphCenter - ((windowHeight/2 - rootH/2 - startNodeOffsetY)*scaleToFit)]);
}
function update() {
let userCanAddEdit = (scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate;
if(scope.dimensionsSet) {
@@ -243,7 +269,6 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
thisNode.append("rect")
.attr("width", rootW)
.attr("height", rootH)
//.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH)
.attr("y", 10)
.attr("rx", 5)
.attr("ry", 5)
@@ -252,7 +277,6 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
.call(add_node);
thisNode.append("text")
.attr("x", 13)
//.attr("y", (windowHeight-margin.top-margin.bottom)/2 - rootH + rootH/2)
.attr("y", 30)
.attr("dy", ".35em")
.attr("class", "WorkflowChart-startText")
@@ -544,6 +568,12 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
node.exit().remove();
if(nodes && nodes.length > 1 && !graphLoaded) {
zoomToFitChart();
}
graphLoaded = true;
let link = svgGroup.selectAll("g.link")
.data(links, function(d) {
return d.source.id + "-" + d.target.id;
@@ -927,6 +957,10 @@ export default ['$state','moment', '$timeout', '$window', '$filter', 'Rest', 'Ge
manualZoom(params.zoom);
});
scope.$on('zoomToFitChart', function() {
zoomToFitChart();
});
let clearWatchTreeData = scope.$watch('treeData', function(newVal) {
if(newVal) {
update();

View File

@@ -7,7 +7,7 @@
flex: 1 0 auto;
}
.WorkflowControls-Pan {
flex: 0 0 85px;
flex: 0 0 87px;
}
.WorkflowControls-Pan--button {
color: @default-icon;
@@ -49,17 +49,10 @@
.WorkflowControls-Zoom--button:hover {
color: @default-link-hov;
}
.WorkflowControls-Zoom--minus {
margin-left: 20px;
padding-right: 8px;
}
.WorkflowControls-Zoom--plus {
padding-left: 8px;
}
.WorkflowControls-zoomSlider {
width: 150px;
padding-left: 10px;
padding-right: 10px;
padding-left: 8px;
padding-right: 8px;
}
.WorkflowControls-zoomPercentage {
text-align: center;
@@ -67,6 +60,16 @@
height: 24px;
line-height: 24px;
}
.WorkflowControls-ZoomToFit {
display: flex;
flex: 0 0 62px;
align-items: center;
justify-content: center;
color: @default-icon;
}
.WorkflowControls-ZoomToFit:hover {
color: @default-link-hov;
}
.ui-slider-handle.ui-state-default.ui-corner-all {
border-radius: 50%;

View File

@@ -10,7 +10,8 @@ export default ['templateUrl',
scope: {
panChart: '&',
resetChart: '&',
zoomChart: '&'
zoomChart: '&',
zoomToFitChart: '&'
},
templateUrl: templateUrl('templates/workflows/workflow-controls/workflow-controls'),
restrict: 'E',
@@ -60,6 +61,10 @@ export default ['templateUrl',
});
};
scope.zoomToFit = function() {
scope.zoomToFitChart();
};
scope.$on('workflowZoomed', function(evt, params) {
scope.zoom = Math.round(params.zoom * 10) * 10;
$("#slider").slider('value',scope.zoom);

View File

@@ -1,3 +1,6 @@
<div class="WorkflowControls-ZoomToFit">
<i class="fa fa-desktop" ng-click="zoomToFit()"></i>
</div>
<div class="WorkflowControls-Zoom">
<div class="WorkflowControls-Zoom--button WorkflowControls-Zoom--minus" ng-click="zoomOut()">
<i class="fa fa-minus" aria-hidden="true"></i>

View File

@@ -168,7 +168,7 @@
flex: 1 0 auto;
}
.WorkflowLegend-maker--right {
flex: 0 0 215px;
flex: 0 0 217px;
text-align: right;
padding-right: 20px;
position: relative;
@@ -246,9 +246,9 @@
}
.WorkflowMaker-manualControls {
position: absolute;
left: -77px;
left: -106px;
height: 60px;
width: 293px;
width: 322px;
background-color: @default-bg;
display: flex;
border: 1px solid @b7grey;
@@ -259,10 +259,10 @@
}
.WorkflowLegend-manualControls {
position: absolute;
left: -239px;
left: -272px;
top: 38px;
height: 60px;
width: 290px;
width: 322px;
background-color: @default-bg;
display: flex;
border: 1px solid @d7grey;

View File

@@ -31,9 +31,9 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
showTypeOptions: false
};
$scope.editRequests = [];
$scope.associateRequests = [];
$scope.disassociateRequests = [];
$scope.editRequests = [];
$scope.associateRequests = [];
$scope.disassociateRequests = [];
$scope.showKey = false;
$scope.toggleKey = () => $scope.showKey = !$scope.showKey;
@@ -940,9 +940,9 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
});
};
$scope.$on('WorkflowDialogReady', function(){
$scope.modalOpen = true;
});
$scope.zoomToFitChart = function() {
$scope.$broadcast('zoomToFitChart');
};
init();

View File

@@ -57,6 +57,8 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window',
scope.removeWorkflowDialogReady = scope.$on('WorkflowDialogReady', function() {
$('#workflow-modal-dialog').dialog('open');
scope.modalOpen = true;
scope.$broadcast("refreshWorkflowChart");
});

View File

@@ -73,7 +73,7 @@
<span class="badge List-titleBadge" ng-bind="treeData.data.totalNodes"></span>
<i ng-class="{'WorkflowMaker-manualControlsIcon--active': showManualControls}" class="fa fa-cog WorkflowMaker-manualControlsIcon" aria-hidden="true" alt="Controls" ng-click="toggleManualControls()"></i>
<div ng-show="showManualControls" class="WorkflowMaker-manualControls noselect">
<workflow-controls class="WorkflowControls" pan-chart="panChart(direction)" zoom-chart="zoomChart(zoom)" reset-chart="resetChart()"></workflow-controls>
<workflow-controls class="WorkflowControls" pan-chart="panChart(direction)" zoom-chart="zoomChart(zoom)" reset-chart="resetChart()" zoom-to-fit-chart="zoomToFitChart()"></workflow-controls>
</div>
</div>
</div>

View File

@@ -165,6 +165,10 @@ export default ['workflowData', 'workflowResultsService', 'workflowDataOptions',
$scope.$broadcast('resetWorkflowChart');
};
$scope.zoomToFitChart = function() {
$scope.$broadcast('zoomToFitChart');
};
$scope.workflowZoomed = function(zoom) {
$scope.$broadcast('workflowZoomed', {
zoom: zoom

View File

@@ -289,7 +289,7 @@
<div class="WorkflowLegend-details--right">
<i ng-class="{'WorkflowMaker-manualControlsIcon--active': showManualControls}" class="fa fa-cog WorkflowMaker-manualControlsIcon" aria-hidden="true" alt="Controls" ng-click="toggleManualControls()"></i>
<div ng-show="showManualControls" class="WorkflowLegend-manualControls noselect">
<workflow-controls class="WorkflowControls" pan-chart="panChart(direction)" zoom-chart="zoomChart(zoom)" reset-chart="resetChart()"></workflow-controls>
<workflow-controls class="WorkflowControls" pan-chart="panChart(direction)" zoom-chart="zoomChart(zoom)" reset-chart="resetChart()" zoom-to-fit-chart="zoomToFitChart()"></workflow-controls>
</div>
</div>
</div>

View File

@@ -52,3 +52,14 @@ register(
category_slug='ui',
feature_required='rebranding',
)
register(
'MAX_UI_JOB_EVENTS',
field_class=fields.IntegerField,
min_value=100,
label=_('Max Job Events Retreived by UI'),
help_text=_('Maximum number of job events for the UI to retreive within a '
'single request.'),
category=_('UI'),
category_slug='ui',
)

View File

@@ -30,6 +30,16 @@
"dev": "webpack --config build/webpack.development.js --progress",
"watch": "webpack-dev-server --config build/webpack.watch.js --progress",
"production": "webpack --config build/webpack.production.js"
"component-test": "./node_modules/.bin/karma start client/test/karma.conf.js",
"lint-dev": "./node_modules/.bin/nodemon --exec \"./node_modules/.bin/eslint -c .eslintrc.js .\" --watch \"client/components/**/*.js\"",
"component-dev": "./node_modules/.bin/karma start client/test/karma.conf.js --auto-watch --no-single-run"
},
"optionalDependencies": {
"fsevents": "*",
"browser-sync": "^2.14.0",
"grunt-browser-sync": "^2.2.0",
"grunt-contrib-watch": "^1.0.0",
"webpack-dev-server": "^1.14.1"
},
"devDependencies": {
"angular-mocks": "~1.6.6",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff