Merge branch 'devel' of github.com:ansible/ansible-tower into rbac-devel-integration

This commit is contained in:
Akita Noek 2016-02-09 09:47:29 -05:00
commit 29f8d6b778
12 changed files with 259 additions and 30 deletions

View File

@ -1,5 +1,5 @@
recursive-include awx *.py
recursive-include awx/static *.ico
recursive-include awx/static *
recursive-include awx/templates *.html
recursive-include awx/api/templates *.md *.html
recursive-include awx/ui/templates *.html

View File

@ -128,9 +128,10 @@ class Metadata(metadata.SimpleMetadata):
metadata['added_in_version'] = added_in_version
# Add type(s) handled by this view/serializer.
serializer = view.get_serializer()
if hasattr(serializer, 'get_types'):
metadata['types'] = serializer.get_types()
if hasattr(view, 'get_serializer'):
serializer = view.get_serializer()
if hasattr(serializer, 'get_types'):
metadata['types'] = serializer.get_types()
# Add search fields if available from the view.
if getattr(view, 'search_fields', None):

View File

@ -772,13 +772,16 @@ class OrganizationSerializer(BaseSerializer):
class ProjectOptionsSerializer(BaseSerializer):
scm_clean = serializers.NullBooleanField(default=False)
scm_delete_on_update = serializers.NullBooleanField(default=False)
class Meta:
fields = ('*', 'local_path', 'scm_type', 'scm_url', 'scm_branch',
'scm_clean', 'scm_delete_on_update', 'credential')
extra_kwargs = {
'scm_type': {
'allow_null': True
}
'allow_null': True,
},
}
def get_related(self, obj):
@ -791,6 +794,12 @@ class ProjectOptionsSerializer(BaseSerializer):
def validate_scm_type(self, value):
return value or u''
def validate_scm_clean(self, value):
return bool(value)
def validate_scm_delete_on_update(self, value):
return bool(value)
def validate(self, attrs):
errors = {}
@ -822,18 +831,20 @@ class ProjectOptionsSerializer(BaseSerializer):
class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
playbooks = serializers.ReadOnlyField(help_text='Array of playbooks available within this project.')
scm_delete_on_next_update = serializers.BooleanField(read_only=True)
scm_update_on_launch = serializers.NullBooleanField(default=False)
status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True, required=False)
last_update_failed = serializers.BooleanField(read_only=True)
last_updated = serializers.DateTimeField(read_only=True)
class Meta:
model = Project
fields = ('*', 'playbooks', 'scm_delete_on_next_update', 'scm_update_on_launch',
fields = ('*', 'scm_delete_on_next_update', 'scm_update_on_launch',
'scm_update_cache_timeout') + \
('last_update_failed', 'last_updated') # Backwards compatibility
def clean_scm_update_on_launch(self, value):
return bool(value)
def get_related(self, obj):
res = super(ProjectSerializer, self).get_related(obj)
@ -858,6 +869,8 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
class ProjectPlaybooksSerializer(ProjectSerializer):
playbooks = serializers.ReadOnlyField(help_text='Array of playbooks available within this project.')
class Meta:
model = Project
fields = ('playbooks',)
@ -1736,6 +1749,12 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
},
}
def get_field_names(self, declared_fields, info):
field_names = super(AdHocCommandSerializer, self).get_field_names(declared_fields, info)
# Meta inheritance and -field_name options don't seem to be taking
# effect above, so remove the undesired fields here.
return tuple(x for x in field_names if x not in ('unified_job_template', 'description'))
def build_standard_field(self, field_name, model_field):
field_class, field_kwargs = super(AdHocCommandSerializer, self).build_standard_field(field_name, model_field)
# Load module name choices dynamically from DB settings.

View File

@ -1379,14 +1379,15 @@ class UnifiedJobTemplateAccess(BaseAccess):
qs = qs.select_related(
'created_by',
'modified_by',
'project',
'inventory',
'credential',
'cloud_credential',
#'project',
#'inventory',
#'credential',
#'cloud_credential',
'next_schedule',
'last_job',
'current_job',
)
# FIXME: Figure out how to do select/prefetch on related project/inventory/credential/cloud_credential.
return qs
class UnifiedJobAccess(BaseAccess):
@ -1412,18 +1413,19 @@ class UnifiedJobAccess(BaseAccess):
qs = qs.select_related(
'created_by',
'modified_by',
'project',
'inventory',
'credential',
'project___credential',
'inventory_source___credential',
'inventory_source___inventory',
'job_template___inventory',
'job_template___project',
'job_template___credential',
'job_template___cloud_credential',
#'project',
#'inventory',
#'credential',
#'project___credential',
#'inventory_source___credential',
#'inventory_source___inventory',
#'job_template___inventory',
#'job_template___project',
#'job_template___credential',
#'job_template___cloud_credential',
)
qs = qs.prefetch_related('unified_job_template')
# FIXME: Figure out how to do select/prefetch on related project/inventory/credential/cloud_credential.
return qs
class ScheduleAccess(BaseAccess):

View File

@ -9,13 +9,21 @@
* @name controllers.function:Activity Stream
* @description This controller controls the activity stream.
*/
function activityStreamController($scope, Stream) {
function activityStreamController($scope, $state, subTitle, Stream, GetTargetTitle) {
// subTitle is passed in via a resolve on the route. If there is no subtitle
// generated in the resolve then we go get the targets generic title.
// Get the streams sub-title based on the target. This scope variable is leveraged
// when we define the activity stream list. Specifically it is included in the list
// title.
$scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target);
// Open the stream
Stream({
scope: $scope
});
}
export default ['$scope', 'Stream', activityStreamController];
export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle', activityStreamController];

View File

@ -14,4 +14,39 @@ export default {
ncyBreadcrumb: {
label: "ACTIVITY STREAM"
},
resolve: {
subTitle:
[ '$stateParams',
'Rest',
'ModelToPlural',
'GetBasePath',
'ProcessErrors',
function($stateParams, rest, ModelToPlural, getBasePath, ProcessErrors) {
// If we have a target and an ID then we want to go grab the name of the object
// that we're examining with the activity stream. This name will be used in the
// subtitle.
if ($stateParams.target && $stateParams.id) {
var target = $stateParams.target;
var id = $stateParams.id;
var url = getBasePath(ModelToPlural(target)) + id + '/';
rest.setUrl(url);
return rest.get()
.then(function(data) {
// Return the name or the username depending on which is available.
return (data.data.name || data.data.username);
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get title info. GET returned status: ' +
response.status
});
});
}
else {
return null;
}
}
]
}
};

View File

@ -185,7 +185,9 @@ var tower = angular.module('Tower', [
'pendolytics',
'ui.router',
'ncy-angular-breadcrumb',
'scheduler'
'scheduler',
'ApiModelHelper',
'ActivityStreamHelper'
])
.constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/')

View File

@ -41,6 +41,8 @@ import RelatedSearch from "./helpers/related-search";
import Search from "./helpers/search";
import Teams from "./helpers/teams";
import AdhocHelper from "./helpers/Adhoc";
import ApiModelHelper from "./helpers/ApiModel";
import ActivityStreamHelper from "./helpers/ActivityStream";
export
{ AboutAnsible,
@ -76,5 +78,7 @@ export
RelatedSearch,
Search,
Teams,
AdhocHelper
AdhocHelper,
ApiModelHelper,
ActivityStreamHelper
};

View File

@ -0,0 +1,58 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ActivityStream
* @description Helper functions for the activity stream
*/
export default
angular.module('ActivityStreamHelper', ['Utilities'])
.factory('GetTargetTitle', [
function () {
return function (target) {
var rtnTitle = 'DASHBOARD';
switch(target) {
case 'project':
rtnTitle = 'PROJECTS';
break;
case 'inventory':
rtnTitle = 'INVENTORIES';
break;
case 'job_template':
rtnTitle = 'JOB TEMPLATES';
break;
case 'credential':
rtnTitle = 'CREDENTIALS';
break;
case 'user':
rtnTitle = 'USERS';
break;
case 'team':
rtnTitle = 'TEAMS';
break;
case 'organization':
rtnTitle = 'ORGANIZATIONS';
break;
case 'management_job':
rtnTitle = 'MANAGEMENT JOBS';
break;
case 'inventory_script':
rtnTitle = 'INVENTORY SCRIPTS';
break;
case 'schedule':
rtnTitle = 'SCHEDULES';
break;
}
return rtnTitle;
};
}
]);

View File

@ -0,0 +1,100 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:ApiModel
* @description Helper functions to convert singular/plural versions of our models to the opposite
*/
export default
angular.module('ApiModelHelper', ['Utilities'])
.factory('ModelToSingular', [
function () {
return function (model) {
// This function takes in the plural model string and spits out the singular
// version.
var singularModel;
switch(model) {
case 'projects':
singularModel = 'project';
break;
case 'inventories':
singularModel = 'inventory';
break;
case 'job_templates':
singularModel = 'job_template';
break;
case 'credentials':
singularModel = 'credential';
break;
case 'users':
singularModel = 'user';
break;
case 'teams':
singularModel = 'team';
break;
case 'organizations':
singularModel = 'organization';
break;
case 'management_jobs':
singularModel = 'management_job';
break;
case 'inventory_scripts':
singularModel = 'inventory_script';
break;
}
return singularModel;
};
}
])
.factory('ModelToPlural', [
function () {
return function (model) {
// This function takes in the singular model string and spits out the plural
// version.
var pluralModel;
switch(model) {
case 'project':
pluralModel = 'projects';
break;
case 'inventory':
pluralModel = 'inventories';
break;
case 'job_template':
pluralModel = 'job_templates';
break;
case 'credential':
pluralModel = 'credentials';
break;
case 'user':
pluralModel = 'users';
break;
case 'team':
pluralModel = 'teams';
break;
case 'organization':
pluralModel = 'organizations';
break;
case 'management_job':
pluralModel = 'management_jobs';
break;
case 'inventory_script':
pluralModel = 'inventory_scripts';
break;
}
return pluralModel;
};
}
]);

View File

@ -12,7 +12,7 @@ export default
name: 'activities',
iterator: 'activity',
editTitle: 'Activity Stream',
listTitle: 'Activity Stream',
listTitle: 'Activity Stream<span ng-show="streamSubTitle"><div class="List-titleLockup"></div>{{streamSubTitle}}<span>',
listTitleBadge: false,
selectInstructions: '',
index: false,

View File

@ -119,7 +119,7 @@ angular.module('PromptDialog', ['Utilities', 'sanitizeFilter'])
$('#prompt-modal').off('hidden.bs.modal');
$('#prompt-modal').modal({
backdrop: 'local_backdrop',
backdrop: 'static',
keyboard: true,
show: true
});