Merge branch 'release_3.0.2' into devel

* release_3.0.2: (126 commits)
  Disable permissions tab in Credential > Edit form if Credential is private (#3288)
  Tweaked the popover text for job and skip tags on JT add/edit
  Workaround a cascade setnull polymorphic issue
  flake8
  Fixed old test expectations
  Made it so the credential organization field can't be changed
  Skip some unit tests
  Fixed org auditor visibility of team credentials
  Fix sosreport
  fix credential kind options for list
  interpret any code below 300 as success
  bail when status code is over 300
  Make CloudForms inventory_script work
  Use no_log when handling passwords
  Prevent ignored task from being displayed as failing.
  making ec2 cred optional on group->edit
  making ec2 credential optional for ec2 inventory
  Revert "Fix to ensure org auditors can see team credentials"
  Fixed team credential list to work with corrected permissions
  Making the username and password fields optional
  ...
This commit is contained in:
Matthew Jones
2016-08-18 22:52:55 -04:00
96 changed files with 1151 additions and 570 deletions

View File

@@ -80,12 +80,6 @@ a.red-txt:active {
text-overflow: ellipsis;
}
.name-column {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
blockquote {
font-size: 14px;
}

View File

@@ -44,9 +44,11 @@
color: @list-header-txt;
font-size: 14px;
font-weight: bold;
white-space: nowrap;
padding-bottom: 25px;
min-height: 45px;
word-break: break-all;
max-width: 90%;
word-wrap: break-word;
}
.Form-secondaryTitle{
@@ -55,7 +57,10 @@
min-height: 40px;
}
.Form-title--is_superuser, .Form-title--is_system_auditor, .Form-title--is_ldap_user{
.Form-title--is_superuser,
.Form-title--is_system_auditor,
.Form-title--is_ldap_user,
.Form-title--is_external_account{
height:15px;
color: @default-interface-txt;
background-color: @default-list-header-bg;
@@ -353,6 +358,11 @@
border-color: transparent transparent @field-dropdown-icon transparent!important;
}
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.select2-dropdown{
border:1px solid @field-border;

View File

@@ -78,6 +78,7 @@ table, tbody {
padding-left: 15px;
padding-right: 15px;
border-top:0px!important;
word-wrap: break-word;
}
.List-tableCell.description-column {
@@ -383,6 +384,7 @@ table, tbody {
.List-action--showTooltipOnDisabled {
display: inline-block;
cursor: not-allowed;
}
.List-action--showTooltipOnDisabled .btn[disabled] {

View File

@@ -51,23 +51,6 @@ export default
PaginateInit({ scope: scope,
list: list, url: url, pageSize: 5 });
if (scope.removePostRefresh) {
scope.removePostRefresh();
}
scope.removePostRefresh = scope.$on('PostRefresh', function () {
if(scope.allSelected && scope.allSelected.length > 0) {
// We need to check to see if any of the selected items are now in our list!
for(var i=0; i<scope.allSelected.length; i++) {
for(var j=0; j<scope[set].length; j++) {
if(scope.allSelected[i].id === scope[set][j].id && scope.allSelected[i].type === scope[set][j].type) {
// If so, let's go ahead and mark it as selected so that select-list-item knows to check the box
scope[set][j].isSelected = true;
}
}
}
}
});
scope.search(list.iterator);
});
}

View File

@@ -21,6 +21,13 @@
key: true,
label: 'name'
},
organization: {
label: 'organization',
ngBind: 'team.summary_fields.organization.name',
sourceModel: 'organization',
sourceField: 'name',
searchable: true
}
}
};

View File

@@ -66,6 +66,12 @@
display: inline-block;
color: @default-interface-txt;
text-transform: uppercase;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: bottom;
}
.BreadCrumb-item + .BreadCrumb-item:before {

View File

@@ -46,13 +46,13 @@ export function CredentialsList($scope, $rootScope, $location, $log,
Wait('stop');
$('#prompt-modal').modal('hide');
list.fields.kind.searchOptions = $scope.credential_kind_options;
list.fields.kind.searchOptions = $scope.credential_kind_options_list;
// Translate the kind value
for (i = 0; i < $scope.credentials.length; i++) {
for (j = 0; j < $scope.credential_kind_options.length; j++) {
if ($scope.credential_kind_options[j].value === $scope.credentials[i].kind) {
$scope.credentials[i].kind = $scope.credential_kind_options[j].label;
for (j = 0; j < $scope.credential_kind_options_list.length; j++) {
if ($scope.credential_kind_options_list[j].value === $scope.credentials[i].kind) {
$scope.credentials[i].kind = $scope.credential_kind_options_list[j].label;
break;
}
}
@@ -82,7 +82,7 @@ export function CredentialsList($scope, $rootScope, $location, $log,
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
variable: 'credential_kind_options_list',
callback: 'choicesReadyCredential'
});
@@ -148,7 +148,7 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
url;
$scope.keyEntered = false;
$scope.permissionsTooltip = 'Please save before assigning permissions';
generator.inject(form, { mode: 'add', related: false, scope: $scope });
generator.reset();
@@ -391,6 +391,12 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
$scope.removeCredentialLoaded();
}
$scope.removeCredentialLoaded = $scope.$on('credentialLoaded', function () {
// if the credential is assigned to an organization, allow permission delegation
// do NOT use $scope.organization in a view directive to determine if a credential is associated with an org
$scope.disablePermissionAssignment = typeof($scope.organization) === 'number' ? false : true;
if ($scope.disablePermissionAssignment){
$scope.permissionsTooltip = 'Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.';
}
var set;
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);

View File

@@ -20,7 +20,8 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
var jobs_scope, scheduled_scope,
choicesCount = 0,
listCount = 0,
api_complete = false;
api_complete = false,
scheduledJobsList = _.cloneDeep(ScheduledJobsList);
$scope.jobsSelected = true;
@@ -66,22 +67,23 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
scope: jobs_scope,
list: AllJobsList,
id: 'active-jobs',
pageSize: 20,
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished',
pageSize: 20,
searchParams: search_params,
spinner: false
});
scheduled_scope = $scope.$new(true);
scheduledJobsList.basePath = GetBasePath('schedules') + '?next_run__isnull=false';
LoadSchedulesScope({
parent_scope: $scope,
scope: scheduled_scope,
list: ScheduledJobsList,
list: scheduledJobsList,
pageSize: 20,
id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: GetBasePath('schedules') + '?next_run__isnull=false'
url: scheduledJobsList.basePath
});
$scope.refreshJobs = function() {

View File

@@ -358,11 +358,8 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
$scope.editSchedules = function(id) {
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (project.scm_type === "Manual" || Empty(project.scm_type)) {
// Nothing to do
}
else {
$location.path('/projects/' + id + '/schedules');
if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) {
$state.go('projectSchedules', {id: id});
}
};
}

View File

@@ -162,6 +162,7 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log,
$scope.not_ldap_user = !$scope.ldap_user;
$scope.ldap_dn = null;
$scope.socialAuthUser = false;
$scope.external_account = null;
generator.reset();
@@ -334,6 +335,7 @@ export function UsersEdit($scope, $rootScope, $location,
$scope.not_ldap_user = !$scope.ldap_user;
master.ldap_user = $scope.ldap_user;
$scope.socialAuthUser = (data.auth.length > 0) ? true : false;
$scope.external_account = data.external_account;
$scope.user_type = $scope.user_type_options[0];
$scope.is_system_auditor = false;

View File

@@ -6,8 +6,8 @@
export default
['$scope', '$state', '$stateParams', 'PageRangeSetup', 'GetBasePath', 'DashboardHostsList',
'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts', '$rootScope',
function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts, $rootScope){
'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts', '$rootScope', 'SearchInit',
function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts, $rootScope, SearchInit){
var setJobStatus = function(){
_.forEach($scope.hosts, function(value){
SetStatus({
@@ -59,6 +59,12 @@ export default
$scope.hosts = hosts.results;
setJobStatus();
generator.inject(list, {mode: 'edit', scope: $scope});
SearchInit({
scope: $scope,
set: 'hosts',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
@@ -77,6 +83,7 @@ export default
$scope.rowBeingEdited = $state.params.id;
$scope.listBeingEdited = "hosts";
}
$scope.search(list.iterator);
};
init();
}];

View File

@@ -31,6 +31,7 @@ export default function(){
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'
},

View File

@@ -101,10 +101,8 @@
.DashboardList-nameCell {
padding-left: 15px;
text-overflow: ellipsis;
overflow:hidden;
white-space: nowrap;
width: 100%;
word-wrap: break-word;
}
.DashboardList-nameContainer {

View File

@@ -404,7 +404,9 @@ export default
related: {
permissions: {
awToolTip: 'Please save before assigning permissions',
disabled: 'disablePermissionAssignment',
awToolTip: '{{permissionsTooltip}}',
dataTipWatch: 'permissionsTooltip',
dataPlacement: 'top',
basePath: 'credentials/:id/access_list/',
type: 'collection',
@@ -452,7 +454,7 @@ export default
return {
permissions: {
iterator: 'permission',
url: urls.access_list
url: urls.access_list,
}
};
}

View File

@@ -81,7 +81,7 @@ export default
permissions: {
awToolTip: 'Please save before assigning permissions',
dataPlacement: 'top',
basePath: 'projects/:id/access_list/',
basePath: 'inventories/:id/access_list/',
type: 'collection',
title: 'Permissions',
iterator: 'permission',

View File

@@ -228,10 +228,7 @@ export default
column: 2,
awPopOver: "<p>Provide a comma separated list of tags.</p>\n" +
"<p>Tags are useful when you have a large playbook, and you want to run a specific part of a play or task.</p>" +
"<p>For example, you might have a task consisting of a long list of actions. Tag values can be assigned to each action. " +
"Suppose the actions have been assigned tag values of &quot;configuration&quot;, &quot;packages&quot; and &quot;install&quot;.</p>" +
"<p>If you just want to run the &quot;configuration&quot; and &quot;packages&quot; actions, you would enter the following here " +
"in the Job Tags field:</p>\n<blockquote>configuration,packages</blockquote>\n",
"<p>Consult the Ansible documentation for further details on the usage of tags.</p>",
dataTitle: "Job Tags",
dataPlacement: "right",
dataContainer: "body",
@@ -240,6 +237,25 @@ export default
text: 'Prompt on launch'
}
},
skip_tags: {
label: 'Skip Tags',
type: 'textarea',
rows: 5,
addRequired: false,
editRequired: false,
'elementClass': 'Form-textInput',
column: 2,
awPopOver: "<p>Provide a comma separated list of tags.</p>\n" +
"<p>Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task.</p>" +
"<p>Consult the Ansible documentation for further details on the usage of tags.</p>",
dataTitle: "Skip Tags",
dataPlacement: "right",
dataContainer: "body",
subCheckbox: {
variable: 'ask_skip_tags_on_launch',
text: 'Prompt on launch'
}
},
checkbox_group: {
label: 'Options',
type: 'checkbox_group',
@@ -281,7 +297,7 @@ export default
column: 2,
awPopOver: "callback_help",
awPopOverWatch: "callback_help",
dataPlacement: 'right',
dataPlacement: 'top',
dataTitle: 'Provisioning Callback URL',
dataContainer: "body"
},

View File

@@ -46,7 +46,7 @@ export default
label: 'Username',
type: 'text',
awRequiredWhen: {
reqExpression: "not_ldap_user",
reqExpression: "not_ldap_user && external_account === null",
init: true
},
autocomplete: false
@@ -69,7 +69,7 @@ export default
label: 'Password',
type: 'sensitive',
hasShowInputButton: true,
ngShow: 'ldap_user == false && socialAuthUser === false',
ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null',
addRequired: true,
editRequired: false,
ngChange: "clearPWConfirm('password_confirm')",
@@ -80,7 +80,7 @@ export default
label: 'Confirm Password',
type: 'sensitive',
hasShowInputButton: true,
ngShow: 'ldap_user == false && socialAuthUser === false',
ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null',
addRequired: true,
editRequired: false,
awPassMatch: true,

View File

@@ -55,9 +55,9 @@ export default
// Submit request to run an adhoc comamand
.factory('AdhocRun', ['$location','$stateParams', 'LaunchJob',
'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors',
'Wait', 'Empty', 'CreateLaunchDialog',
'Wait', 'Empty', 'CreateLaunchDialog', '$state',
function ($location, $stateParams, LaunchJob, PromptForPasswords,
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog) {
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog, $state) {
return function (params) {
var id = params.project_id,
scope = params.scope.$new(),
@@ -87,25 +87,31 @@ export default
});
});
if (scope.removeAdhocLaunchFinished) {
scope.removeAdhocLaunchFinished();
}
scope.removeAdhocLaunchFinished = scope.$on('AdhocLaunchFinished',
function(e, data) {
$location.path('/ad_hoc_commands/' + data.id);
});
if (scope.removeStartAdhocRun) {
scope.removeStartAdhocRun();
}
scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() {
LaunchJob({
scope: scope,
url: url,
callback: 'AdhocLaunchFinished' // send to the adhoc
// standard out page
});
var password,
postData={};
for (password in scope.passwords) {
postData[scope.passwords[password]] = scope[
scope.passwords[password]
];
}
// Re-launch the adhoc job
Rest.setUrl(url);
Rest.post(postData)
.success(function (data) {
Wait('stop');
$state.go('adHocJobStdout', {id: data.id});
})
.error(function (data, status) {
ProcessErrors(scope, data, status, {
hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status });
});
});
// start routine only if passwords need to be prompted

View File

@@ -161,7 +161,7 @@ angular.module('CredentialsHelper', ['Utilities'])
break;
case 'net':
scope.username_required = true;
scope.password_required = true;
scope.password_required = false;
scope.passwordLabel = 'Password';
scope.sshKeyDataLabel = 'SSH Key';
break;

View File

@@ -165,6 +165,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
has_inventory_sources = params.has_inventory_sources,
launch_class = '',
launch_tip = 'Start sync process',
schedule_tip = 'Schedule future inventory syncs',
stat, stat_class, status_tip;
stat = status;
@@ -225,7 +226,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
"tooltip": status_tip,
"status": stat,
"launch_class": launch_class,
"launch_tip": launch_tip
"launch_tip": launch_tip,
"schedule_tip": schedule_tip
};
};
}

View File

@@ -132,6 +132,9 @@ angular.module('JobTemplatesHelper', ['Utilities'])
scope.ask_tags_on_launch = (data.ask_tags_on_launch) ? true : false;
master.ask_tags_on_launch = scope.ask_tags_on_launch;
scope.ask_skip_tags_on_launch = (data.ask_skip_tags_on_launch) ? true : false;
master.ask_skip_tags_on_launch = scope.ask_skip_tags_on_launch;
scope.ask_job_type_on_launch = (data.ask_job_type_on_launch) ? true : false;
master.ask_job_type_on_launch = scope.ask_job_type_on_launch;

View File

@@ -34,7 +34,8 @@ export default
fld = (params.variable) ? params.variable : 'variables',
pfld = (params.parse_variable) ? params.parse_variable : 'parseType',
onReady = params.onReady,
onChange = params.onChange;
onChange = params.onChange,
readOnly = params.readOnly;
function removeField(fld) {
//set our model to the last change in CodeMirror and then destroy CodeMirror

View File

@@ -75,6 +75,9 @@ export default
case 'missing':
result = 'Missing. Click for details';
break;
case 'canceled':
result = 'Canceled. Click for details';
break;
}
return result;
};

View File

@@ -50,16 +50,9 @@ function InventoriesList($scope, $rootScope, $location, $log,
"aw-pop-over": html,
"data-popover-title": title,
"data-placement": "right" });
elem.removeAttr('ng-click');
$compile(elem)($scope);
elem.on('shown.bs.popover', function() {
$('.popover').each(function() {
$compile($(this))($scope); //make nested directives work!
});
$('.popover-content, .popover-title').click(function() {
elem.popover('hide');
});
});
elem.popover('show');
$scope.triggerPopover(event);
}
view.inject(InventoryList, { mode: mode, scope: $scope });
@@ -250,44 +243,62 @@ function InventoriesList($scope, $rootScope, $location, $log,
});
$scope.showGroupSummary = function(event, id) {
var inventory;
if (!Empty(id)) {
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
if (inventory.syncStatus !== 'na') {
Wait('start');
Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5');
Rest.get()
.success(function(data) {
$scope.$emit('GroupSummaryReady', event, inventory, data);
})
.error(function(data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status
try{
var elem = $(event.target).parent();
// if the popover is visible already, then exit the function here
if(elem.data()['bs.popover'].tip().hasClass('in')){
return;
}
}
catch(err){
var inventory;
if (!Empty(id)) {
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
if (inventory.syncStatus !== 'na') {
Wait('start');
Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5');
Rest.get()
.success(function(data) {
$scope.$emit('GroupSummaryReady', event, inventory, data);
})
.error(function(data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status
});
});
});
}
}
}
};
$scope.showHostSummary = function(event, id) {
var url, inventory;
if (!Empty(id)) {
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
if (inventory.total_hosts > 0) {
Wait('start');
url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed=";
url += (inventory.has_active_failures) ? 'true' : "false";
url += "&order_by=-finished&page_size=5";
Rest.setUrl(url);
Rest.get()
.success( function(data) {
$scope.$emit('HostSummaryReady', event, data);
})
.error( function(data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status
try{
var elem = $(event.target).parent();
// if the popover is visible already, then exit the function here
if(elem.data()['bs.popover'].tip().hasClass('in')){
return;
}
}
catch(err){
var url, inventory;
if (!Empty(id)) {
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
if (inventory.total_hosts > 0) {
Wait('start');
url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed=";
url += (inventory.has_active_failures) ? 'true' : "false";
url += "&order_by=-finished&page_size=5";
Rest.setUrl(url);
Rest.get()
.success( function(data) {
$scope.$emit('HostSummaryReady', event, data);
})
.error( function(data, status) {
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. GET returned: ' + status
});
});
});
}
}
}
};

View File

@@ -242,7 +242,7 @@ function adhocController($q, $scope, $location, $stateParams,
Rest.post(data)
.success(function (data) {
Wait('stop');
$location.path("/ad_hoc_commands/" + data.id);
$state.go('adHocJobStdout', {id: data.id});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, adhocForm, {

View File

@@ -19,18 +19,4 @@
<li class="BreadCrumb-item" ng-if="currentState !== 'inventoryManage'"></li>
<div class="InventoryManageBreadCrumb-ncy" ng-if="!licenseMissing" ncy-breadcrumb></div>
</ol>
<div class="BreadCrumb-menuLink"
id="bread_crumb_activity_stream"
aw-tool-tip="View Activity Stream"
data-placement="left"
data-trigger="hover"
data-container="body"
ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}"
ng-if="showActivityStreamButton"
ng-hide= "licenseMissing"
ng-click="openActivityStream()">
<i class="BreadCrumb-menuLinkImage icon-activity-stream"
alt="Activity Stream">
</i>
</div>
</div>

View File

@@ -100,6 +100,7 @@
// equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack'
else{
var credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source));
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
CredentialList.basePath = credentialBasePath;
LookUpInit({
scope: $scope,
@@ -122,7 +123,7 @@
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
$scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions'];
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' ? true : false;
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
$scope.group_by = null;
$scope.source_regions = null;
$scope.credential = null;

View File

@@ -100,6 +100,7 @@
else{
var credentialBasePath = (source.value === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source.value === '' ? '' : '?kind=' + (source.value));
CredentialList.basePath = credentialBasePath;
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
LookUpInit({
scope: $scope,
url: credentialBasePath,
@@ -122,7 +123,7 @@
// reset fields
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
$scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions'];
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' ? true : false;
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
$scope.group_by = null;
$scope.source_regions = null;
$scope.credential = null;

View File

@@ -151,6 +151,7 @@
{status_tooltip: group_status.tooltip},
{launch_tooltip: group_status.launch_tip},
{launch_class: group_status.launch_class},
{group_schedule_tooltip: group_status.schedule_tip},
{hosts_status_tip: hosts_status.tooltip},
{hosts_status_class: hosts_status.class},
{source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null},

View File

@@ -13,11 +13,6 @@ import GroupsListController from './groups/groups-list.controller';
export default {
name: 'inventoryManage',
url: '/inventories/:inventory_id/manage?{group:int}{failed}',
data: {
activityStream: true,
activityStreamTarget: 'inventory',
activityStreamId: 'inventory_id'
},
params:{
group:{
array: true

View File

@@ -38,11 +38,13 @@
break;
case 'ok':
params.event = 'runner_on_ok';
params.changed = 'false';
break;
case 'failed':
params.failed = true;
params.event = 'runner_on_failed';
break;
case 'changed':
params.event = 'runner_on_ok';
params.changed = true;
break;
default:

View File

@@ -641,7 +641,7 @@ export default
return true;
});
//scope.setSearchAll('host');
ParseTypeChange({ scope: scope, field_id: 'pre-formatted-variables' });
ParseTypeChange({ scope: scope, field_id: 'pre-formatted-variables', readOnly: true });
scope.$emit('LoadPlays', data.related.job_events);
})
.error(function(data, status) {

View File

@@ -154,6 +154,11 @@
<div class="JobDetail-resultRowText">{{ job.job_tags }}</div>
</div>
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.skip_tags">
<label class="JobDetail-resultRowLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Skip Tags</label>
<div class="JobDetail-resultRowText">{{ job.skip_tags }}</div>
</div>
<div class="form-group JobDetail-resultRow JobDetail-resultRow--variables toggle-show" ng-show="variables">
<label class="JobDetail-resultRowLabel JobDetail-extraVarsLabel col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Extra Variables</label>
<textarea rows="6" ng-model="variables" name="variables" class="JobDetail-extraVars" id="pre-formatted-variables"></textarea>

View File

@@ -42,10 +42,14 @@ export default
}
if(scope.ask_tags_on_launch && scope.other_prompt_data && scope.other_prompt_data.job_tags){
if(scope.ask_tags_on_launch && scope.other_prompt_data && typeof scope.other_prompt_data.job_tags === 'string'){
job_launch_data.job_tags = scope.other_prompt_data.job_tags;
}
if(scope.ask_skip_tags_on_launch && scope.other_prompt_data && typeof scope.other_prompt_data.skip_tags === 'string'){
job_launch_data.skip_tags = scope.other_prompt_data.skip_tags;
}
if(scope.ask_limit_on_launch && scope.other_prompt_data && scope.other_prompt_data.limit){
job_launch_data.limit = scope.other_prompt_data.limit;
}

View File

@@ -153,7 +153,7 @@ export default
// General catch-all for "other prompts" - used in this link function and to hide the Other Prompts tab when
// it should be hidden
$scope.has_other_prompts = (data.ask_job_type_on_launch || data.ask_limit_on_launch || data.ask_tags_on_launch || data.ask_variables_on_launch) ? true : false;
$scope.has_other_prompts = (data.ask_job_type_on_launch || data.ask_limit_on_launch || data.ask_tags_on_launch || data.ask_skip_tags_on_launch || data.ask_variables_on_launch) ? true : false;
$scope.password_needed = data.passwords_needed_to_start && data.passwords_needed_to_start.length > 0;
$scope.has_default_inventory = data.defaults && data.defaults.inventory && data.defaults.inventory.id;
$scope.has_default_credential = data.defaults && data.defaults.credential && data.defaults.credential.id;
@@ -172,6 +172,10 @@ export default
$scope.other_prompt_data.job_tags = (data.defaults && data.defaults.job_tags) ? data.defaults.job_tags : "";
}
if($scope.ask_skip_tags_on_launch) {
$scope.other_prompt_data.skip_tags = (data.defaults && data.defaults.skip_tags) ? data.defaults.skip_tags : "";
}
if($scope.ask_variables_on_launch) {
$scope.jobLaunchVariables = (data.defaults && data.defaults.extra_vars) ? data.defaults.extra_vars : "---";
$scope.other_prompt_data.parseType = 'yaml';

View File

@@ -148,7 +148,15 @@
<span class="Form-inputLabel">Job Tags</span>
</label>
<div>
<textarea rows="1" ng-model="other_prompt_data.job_tags" name="tags" class="form-control Form-textArea Form-textInput"></textarea>
<textarea rows="5" ng-model="other_prompt_data.job_tags" name="tags" class="form-control Form-textArea Form-textInput"></textarea>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ask_skip_tags_on_launch">
<label for="skip_tags">
<span class="Form-inputLabel">Skip Tags</span>
</label>
<div>
<textarea rows="5" ng-model="other_prompt_data.skip_tags" name="skip_tags" class="form-control Form-textArea Form-textInput"></textarea>
</div>
</div>
</form>

View File

@@ -518,6 +518,7 @@
}
data.ask_tags_on_launch = $scope.ask_tags_on_launch ? $scope.ask_tags_on_launch : false;
data.ask_skip_tags_on_launch = $scope.ask_skip_tags_on_launch ? $scope.ask_skip_tags_on_launch : false;
data.ask_limit_on_launch = $scope.ask_limit_on_launch ? $scope.ask_limit_on_launch : false;
data.ask_job_type_on_launch = $scope.ask_job_type_on_launch ? $scope.ask_job_type_on_launch : false;
data.ask_inventory_on_launch = $scope.ask_inventory_on_launch ? $scope.ask_inventory_on_launch : false;

View File

@@ -640,6 +640,7 @@ export default
}
data.ask_tags_on_launch = $scope.ask_tags_on_launch ? $scope.ask_tags_on_launch : false;
data.ask_skip_tags_on_launch = $scope.ask_skip_tags_on_launch ? $scope.ask_skip_tags_on_launch : false;
data.ask_limit_on_launch = $scope.ask_limit_on_launch ? $scope.ask_limit_on_launch : false;
data.ask_job_type_on_launch = $scope.ask_job_type_on_launch ? $scope.ask_job_type_on_launch : false;
data.ask_inventory_on_launch = $scope.ask_inventory_on_launch ? $scope.ask_inventory_on_launch : false;

View File

@@ -8,7 +8,7 @@
}
.LabelList-tagContainer,
.LabelList-seeMore {
.LabelList-seeMoreLess {
display: flex;
max-width: 100%;
}
@@ -27,7 +27,7 @@
overflow: hidden;
}
.LabelList-seeMore {
.LabelList-seeMoreLess {
color: @default-link;
margin: 4px 0px;
text-transform: uppercase;
@@ -37,7 +37,7 @@
font-size: 11px;
}
.LabelList-seeMore:hover {
.LabelList-seeMoreLess:hover {
color: @default-link-hov;
}

View File

@@ -47,6 +47,13 @@ export default
});
};
scope.seeLess = function() {
// Trim the labels array back down to 10 items
scope.labels = scope.labels.slice(0, 10);
// Re-set the seeMoreInteractive flag so that the "See More" will be displayed
scope.seeMoreInactive = true;
};
scope.deleteLabel = function(templateId, templateName, labelId, labelName) {
var action = function () {
$('#prompt-modal').modal('hide');
@@ -56,13 +63,13 @@ export default
Rest.setUrl(url);
Rest.post({"disassociate": true, "id": labelId})
.success(function () {
scope.search("job_template");
scope.search("job_template", scope.$parent.job_template_page);
Wait('stop');
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Could not disacssociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status });
msg: 'Could not disassociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status });
});
};
@@ -86,6 +93,7 @@ export default
scope.count = null;
}
});
}
};
}

View File

@@ -8,5 +8,7 @@
<span class="LabelList-name">{{ label.name }}</span>
</div>
</div>
<div class="LabelList-seeMore" ng-show="count > 10 && seeMoreInactive"
<div class="LabelList-seeMoreLess" ng-show="count > 10 && seeMoreInactive"
ng-click="seeMore()">View More</div>
<div class="LabelList-seeMoreLess" ng-show="count > 10 && !seeMoreInactive"
ng-click="seeLess()">View Less</div>

View File

@@ -314,7 +314,7 @@ export default
ngClick: 'submitQuestion($event)',
ngDisabled: true,
'class': 'btn btn-sm Form-saveButton',
label: '{{editQuestionIndex === null ? "ADD" : "UPDATE"}}'
label: '{{editQuestionIndex === null ? "+ ADD" : "UPDATE"}}'
}
}

View File

@@ -48,9 +48,7 @@ export default
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-6',
ngClick: "viewJobDetails(all_job)",
defaultSearchField: true,
awToolTip: "{{ all_job.name | sanitize }}",
dataTipWatch: 'all_job.name',
dataPlacement: 'top'
searchDefault: true,
},
type: {
label: 'Type',

View File

@@ -35,6 +35,7 @@ export default
ngClick:"viewJobDetails(completed_job)",
searchable: true,
searchType: 'select',
defaultSearchField: true,
nosort: true,
searchOptions: [
{ label: "Success", value: "successful" },
@@ -54,8 +55,8 @@ export default
name: {
label: 'Name',
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
searchable: false,
ngClick: "viewJobDetails(completed_job)",
defaultSearchField: true,
awToolTip: "{{ completed_job.name | sanitize }}",
dataPlacement: 'top'
},
@@ -64,7 +65,7 @@ export default
ngBind: 'completed_job.type_label',
link: false,
columnClass: "col-lg-2 col-md-2 hidden-sm hidden-xs",
searchable: true,
searchable: false,
searchType: 'select',
searchOptions: [] // populated via GetChoices() in controller
},

View File

@@ -19,6 +19,7 @@ export default
hover: true,
'class': 'table-no-border',
multiSelect: true,
trackBy: 'group.id',
fields: {
sync_status: {

View File

@@ -20,6 +20,7 @@ export default
hasChildren: true,
'class': 'table-no-border',
multiSelect: true,
trackBy: 'host.id',
fields: {
active_failures: {

View File

@@ -22,7 +22,8 @@ export default
key: true,
label: 'Name',
columnClass: 'col-lg-5 col-md-5 col-sm-9 col-xs-8',
linkTo: '/#/job_templates/{{job_template.id}}'
linkTo: '/#/job_templates/{{job_template.id}}',
searchDefault: true
},
description: {
label: 'Description',

View File

@@ -35,7 +35,8 @@ export default
label: 'Name',
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6 List-staticColumnAdjacent',
defaultSearchField: true,
linkTo: '/#/jobs/{{job.id}}'
linkTo: '/#/jobs/{{job.id}}',
searchDefault: true
},
finished: {
label: 'Finished',

View File

@@ -37,6 +37,7 @@ export default
},
name: {
key: true,
searchDefault: true,
label: 'Name',
columnClass: "col-lg-4 col-md-4 col-sm-5 col-xs-7 List-staticColumnAdjacent",
modalColumnClass: 'col-md-8'

View File

@@ -149,6 +149,12 @@ export default
if(field.type === 'number'){
$scope[i] = Number($scope[i]);
}
if(field.name === "username" && $scope.notification_type.value === "email" && value === null){
$scope[i] = "";
}
if(field.type === 'sensitive' && value === null){
$scope[i] = "";
}
return $scope[i];
}

View File

@@ -223,6 +223,12 @@ export default
if(field.type === 'number'){
$scope[i] = Number($scope[i]);
}
if(field.name === "username" && $scope.notification_type.value === "email" && value === null){
$scope[i] = "";
}
if(field.type === 'sensitive' && value === null){
$scope[i] = "";
}
return $scope[i];
}

View File

@@ -28,6 +28,7 @@ export default
Wait('stop');
if (scope.notification_templates) {
scope.notification_templates.forEach(function(notification_template, i) {
setStatus(notification_template);
scope.notification_type_options.forEach(function(type) {
if (type.value === notification_template.notification_type) {
scope.notification_templates[i].notification_type = type.label;
@@ -74,78 +75,33 @@ export default
callback: 'choicesReadyNotifierList'
});
function attachElem(event, html, title) {
var elem = $(event.target).parent();
try {
elem.tooltip('hide');
elem.popover('destroy');
}
catch(err) {
//ignore
}
function setStatus(notification_template) {
var html, recent_notifications = notification_template.summary_fields.recent_notifications;
if (recent_notifications.length > 0) {
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>";
html += "<th>Status</th>";
html += "<th>Time</th>";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
$('.popover').each(function() {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$('.tooltip').each( function() {
// close any lingering tool tipss
$(this).hide();
});
elem.attr({
"aw-pop-over": html,
"data-popover-title": title,
"data-placement": "right" });
$compile(elem)(scope);
elem.on('shown.bs.popover', function() {
$('.popover').each(function() {
$compile($(this))(scope); //make nested directives work!
recent_notifications.forEach(function(row) {
html += "<tr>\n";
html += `<td><i class=\"SmartStatus-tooltip--${row.status} fa icon-job-${row.status}"></i></td>`;
html += "<td>" + ($filter('longDate')(row.created)).replace(/ /,'<br />') + "</td>\n";
html += "</tr>\n";
});
$('.popover-content, .popover-title').click(function() {
elem.popover('hide');
});
});
elem.popover('show');
html += "</tbody>\n";
html += "</table>\n";
}
else {
html = "<p>No recent notifications.</p>\n";
}
notification_template.template_status_html = html;
}
scope.showSummary = function(event, id) {
setTimeout(function(){
if (!Empty(id)) {
var recent_notifications,
html, title = "Recent Notifications";
scope.notification_templates.forEach(function(notification_template){
if(notification_template.id === id){
recent_notifications = notification_template.summary_fields.recent_notifications;
}
});
if (recent_notifications.length > 0) {
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
html += "<thead>\n";
html += "<tr>";
html += "<th>Status</th>";
html += "<th>Time</th>";
html += "</tr>\n";
html += "</thead>\n";
html += "<tbody>\n";
recent_notifications.forEach(function(row) {
html += "<tr>\n";
html += `<td><i class=\"SmartStatus-tooltip--${row.status} fa icon-job-${row.status}"></i></td>`;
html += "<td>" + ($filter('longDate')(row.created)).replace(/ /,'<br />') + "</td>\n";
html += "</tr>\n";
});
html += "</tbody>\n";
html += "</table>\n";
}
else {
html = "<p>No recent notifications.</p>\n";
}
attachElem(event, html, title);
}
}, 100);
};
scope.testNotification = function(){
var name = $filter('sanitize')(this.notification_template.name),
pending_retries = 10;

View File

@@ -59,10 +59,6 @@ export default function() {
username: {
label: 'Username',
type: 'text',
awRequiredWhen: {
reqExpression: "email_required",
init: "false"
},
ngShow: "notification_type.value == 'email' ",
subForm: 'typeSubForm'
},

View File

@@ -19,17 +19,14 @@ export default function(){
fields: {
status: {
label: '',
columnClass: 'List-staticColumn--smallStatus',
iconOnly: true,
searchable: false,
nosort: true,
ngClick: "null",
iconOnly: true,
excludeModal: true,
icons: [{
icon: "{{ 'icon-job-' + notification_template.status }}",
ngClick: "showSummary($event, notification_template.id)",
ngClass: ""
}]
icon: 'icon-job-{{ notification_template.status }}',
awPopOver: '{{ notification_template.template_status_html }}',
dataTitle: "Recent Notifications",
dataPlacement: 'right',
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus'
},
name: {
key: true,

View File

@@ -36,6 +36,8 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest',
disassociate: 1
};
}
// Show the working spinner
Wait('start');
Rest.setUrl(url);
Rest.post(params)
.success( function(data) {
@@ -43,9 +45,8 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest',
scope.$emit(callback, data.id);
notifier[column] = !notifier[column];
}
else {
Wait('stop');
}
// Hide the working spinner
Wait('stop');
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',

View File

@@ -28,7 +28,7 @@ function () {
obj.passwordLabel = ' Password';
obj.email_required = true;
obj.port_required = true;
obj.password_required = true;
obj.password_required = false;
break;
case 'slack':
obj.tokenLabel =' Token';

View File

@@ -334,11 +334,8 @@ export default ['$scope', '$rootScope', '$location', '$log',
$scope.editSchedules = function(id) {
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (project.scm_type === "Manual" || Empty(project.scm_type)) {
// Nothing to do
}
else {
$location.path('/projects/' + id + '/schedules');
if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) {
$state.go('projectSchedules', {id: id});
}
};

View File

@@ -34,6 +34,10 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
type = 'text';
}
if (field.searchDefault) {
obj.default = true;
}
obj.id = id;
obj.value = value;
obj.label = label;
@@ -76,10 +80,13 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu
passThrough = partitionedOptions[1];
var joinOptions = function() {
return _.sortBy(_
var options = _.sortBy(_
.flatten([needsRequest, passThrough]), function(opt) {
return opt.id;
});
// put default first
return _.flatten(_.partition(options, opt => opt.default));
};
if (needsRequest.length) {

View File

@@ -390,46 +390,57 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper'])
// lookup Validate lookup value against API
//
.directive('awlookup', ['Rest', function(Rest) {
.directive('awlookup', ['Rest', '$timeout', function(Rest, $timeout) {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var restTimeout;
ctrl.$parsers.unshift( function(viewValue) {
if (viewValue !== '' && viewValue !== null) {
var url = elm.attr('data-url');
url = url.replace(/\:value/, encodeURI(viewValue));
scope[elm.attr('data-source')] = null;
Rest.setUrl(url);
Rest.get().then( function(data) {
var results = data.data.results;
if (results.length > 0) {
scope[elm.attr('data-source')] = results[0].id;
if(restTimeout) {
$timeout.cancel(restTimeout);
}
restTimeout = $timeout( function(){
Rest.setUrl(url);
Rest.get().then( function(data) {
var results = data.data.results;
if (results.length > 0) {
scope[elm.attr('data-source')] = results[0].id;
// For user lookups the API endpoint doesn't
// have a `name` property, so this is `undefined`
// which causes the input to clear after typing
// a valid value O_o
//
// Only assign if there is a value, so that we avoid
// this situation.
//
// TODO: Evaluate if assigning name on the scope is
// even necessary at all.
//
if (!_.isEmpty(results[0].name)) {
scope[elm.attr('name')] = results[0].name;
// For user lookups the API endpoint doesn't
// have a `name` property, so this is `undefined`
// which causes the input to clear after typing
// a valid value O_o
//
// Only assign if there is a value, so that we avoid
// this situation.
//
// TODO: Evaluate if assigning name on the scope is
// even necessary at all.
//
if (!_.isEmpty(results[0].name)) {
scope[elm.attr('name')] = results[0].name;
}
ctrl.$setValidity('required', true);
ctrl.$setValidity('awlookup', true);
return viewValue;
}
ctrl.$setValidity('required', true);
ctrl.$setValidity('awlookup', true);
return viewValue;
}
ctrl.$setValidity('required', true);
ctrl.$setValidity('awlookup', false);
return undefined;
});
ctrl.$setValidity('awlookup', false);
return undefined;
});
}, 750);
}
else {
if(restTimeout) {
$timeout.cancel(restTimeout);
}
ctrl.$setValidity('awlookup', true);
scope[elm.attr('data-source')] = null;
}
@@ -477,7 +488,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper'])
return {
link: function(scope, element, attrs) {
var delay = (attrs.delay !== undefined && attrs.delay !== null) ? attrs.delay : ($AnsibleConfig) ? $AnsibleConfig.tooltip_delay : {show: 500, hide: 100},
placement;
placement,
stateChangeWatcher;
if (attrs.awTipPlacement) {
placement = attrs.awTipPlacement;
}
@@ -493,6 +505,22 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper'])
template = '<div class="tooltip Tooltip" role="tooltip"><div class="tooltip-arrow Tooltip-arrow"></div><div class="tooltip-inner Tooltip-inner"></div></div>';
}
// This block helps clean up tooltips that may get orphaned by a click event
$(element).on('mouseenter', function() {
if(stateChangeWatcher) {
// Un-bind - we don't want a bunch of listeners firing
stateChangeWatcher();
}
stateChangeWatcher = scope.$on('$stateChangeStart', function() {
// Go ahead and force the tooltip setTimeout to expire (if it hasn't already fired)
$(element).tooltip('hide');
// Clean up any existing tooltips including this one
$('.tooltip').each(function() {
$(this).remove();
});
});
});
$(element).on('hidden.bs.tooltip', function( ) {
// TB3RC1 is leaving behind tooltip <div> elements. This will remove them
// after a tooltip fades away. If not, they lay overtop of other elements and
@@ -548,6 +576,10 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper'])
template = '<div id="' + element[0].id + '_popover_container" class="popover" role="tooltip"><div class="arrow"></div><h3 id="' + element[0].id + '_popover_title" class="popover-title"></h3><div id="' + element[0].id + '_popover_content" class="popover-content"></div></div>';
}
scope.triggerPopover = function(e){
showPopover(e);
};
if (attrs.awPopOverWatch) {
$(element).popover({
placement: placement,

View File

@@ -1487,6 +1487,8 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
"ng-show='is_system_auditor'>Auditor</span>";
html+= "<span class=\"Form-title--is_ldap_user\" "+
"ng-show='ldap_user'>LDAP</span>";
html+= "<span class=\"Form-title--is_external_account\" "+
"ng-show='external_account'>{{external_account}}</span>";
}
html += "</div>\n";
html += "<div class=\"Form-header--fields\">";
@@ -1524,8 +1526,13 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
collection = this.form.related[itm];
html += `<div id="${itm}_tab"`+
`class="Form-tab"`+
`ng-click="${this.form.related[itm].disabled} || toggleFormTabs($event)"` +
`ng-class="{'is-selected': ${itm}Selected ` ;
`ng-click="${this.form.related[itm].disabled} || toggleFormTabs($event)"`;
if (collection.awToolTip){
html += `aw-tool-tip="${collection.awToolTip}"` +
`aw-tip-placement="${collection.dataPlacement}"` +
`data-tip-watch="${collection.dataTipWatch}"`;
}
html += `ng-class="{'is-selected': ${itm}Selected ` ;
if(this.form.related[itm].disabled){
html += `, 'Form-tab--disabled' : ${this.form.related[itm].disabled }`;
}

View File

@@ -371,7 +371,7 @@ angular.module('GeneratorHelpers', [systemStatus.name])
} else if (field.link || (field.key && (field.link === undefined || field.link))) {
html += "<a href=\"#/" + base + "/{{" + list.iterator + ".id }}\" ";
} else {
html += "<a href=\"\">";
html += "<a href=\"\"";
}
if (field.awDroppable) {
html += Attr(field, 'awDroppable');
@@ -394,7 +394,7 @@ angular.module('GeneratorHelpers', [systemStatus.name])
if (field.awPopOver) {
html += "aw-pop-over=\"" + field.awPopOver + "\" ";
html += (field.dataPlacement) ? "data-placement=\"" + field.dataPlacement + "\" " : "";
html += (field.dataTitle) ? "data-title=\"" + field.dataTitle + "\" " : "";
html += (field.dataTitle) ? "over-title=\"" + field.dataTitle + "\" " : "";
}
html += (field.ngClass) ? Attr(field, 'ngClass') : '';
html += (field.ngEllipsis) ? "data-ng-bind=\"" + list.iterator + "." + fld + "\" data-ellipsis " : "";

View File

@@ -466,7 +466,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
innerTable += "ng-class-odd=\"'List-tableRow--oddRow'\" ";
innerTable += "ng-class-even=\"'List-tableRow--evenRow'\" ";
innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name;
innerTable += (list.trackBy) ? " track by " + list.trackBy : " track by $index";
innerTable += (list.trackBy) ? " track by " + list.trackBy : "";
innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : "";
innerTable += (list.filterBy) ? " | filter: " + list.filterBy : "";
innerTable += "\">\n";

View File

@@ -63,9 +63,16 @@ export default ['$scope',
* {@link multiSelectList.controller:multiSelectList#decorateItem `decorateItem`}
*/
this.registerItem = function(item) {
var decoratedItem = this.decorateItem(item);
$scope.items = $scope.items.concat(decoratedItem);
return decoratedItem;
var foundItem = _.find($scope.items, function(existingItem) { return existingItem.id === item.id; });
if(foundItem) {
return foundItem;
}
else {
var decoratedItem = this.decorateItem(item);
$scope.items = $scope.items.concat(decoratedItem);
return decoratedItem;
}
};
/**
@@ -99,6 +106,7 @@ export default ['$scope',
this.decorateItem = function(item) {
return {
isSelected: false,
id: item.id,
value: item
};
};
@@ -129,11 +137,11 @@ export default ['$scope',
* Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`}
*/
this.deselectAll = function() {
$scope.items.forEach(function(item) {
item.isSelected = false;
});
$scope.selection.isExtended = false;
rebuildSelections();
$scope.items.forEach(function(item) {
item.isSelected = false;
});
$scope.selection.isExtended = false;
rebuildSelections();
};

View File

@@ -33,11 +33,8 @@ export default
template: '<input type="checkbox" data-multi-select-list-item ng-model="isSelected" ng-change="userInteractionSelect()">',
link: function(scope, element, attrs, multiSelectList) {
var initializeItem = function() {
scope.decoratedItem = multiSelectList.registerItem(scope.item);
scope.isSelected = scope.item.isSelected ? true : false;
scope.decoratedItem.isSelected = scope.item.isSelected ? true : false;
};
scope.decoratedItem = multiSelectList.registerItem(scope.item);
scope.isSelected = scope.decoratedItem.isSelected ? true : false;
scope.$watch('isSelected', function(value) {
if (value === true) {
@@ -47,23 +44,10 @@ export default
}
});
scope.$watch('item', function() {
// This is necessary for page changes where $scope.item gets updated via ng-repeat
// but this link function never gets triggered (and scope.decoratedItem) never
// gets updated.
initializeItem();
});
scope.$on('$destroy', function() {
multiSelectList.deregisterItem(scope.decoratedItem);
});
scope.userInteractionSelect = function() {
scope.$emit("selectedOrDeselected", scope.decoratedItem);
};
initializeItem();
}
};
}];

View File

@@ -74,6 +74,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
$scope.limit = data.limit;
$scope.verbosity = data.verbosity;
$scope.job_tags = data.job_tags;
$scope.job.module_name = data.module_name;
if (data.extra_vars) {
$scope.variables = ParseVariableString(data.extra_vars);
}

View File

@@ -37,7 +37,7 @@
<body data-user-agent="{{userAgent}}">
<main-menu></main-menu>
<bread-crumb ng-if="!includesCurrentState('inventoryManage')"></bread-crumb><div ui-view="groupBreadcrumbs" ng-if="includesCurrentState('inventoryManage')"></div>
<bread-crumb ng-show="!includesCurrentState('inventoryManage')"></bread-crumb><div ui-view="groupBreadcrumbs" ng-show="includesCurrentState('inventoryManage')"></div>
<toast></toast>
<div class="container-fluid" id="content-container">