Merge branch 'master' into reintroduce-zeromq-unstable

This commit is contained in:
Luke Sneeringer 2014-11-25 09:01:59 -06:00
commit f8cbdd65a5
18 changed files with 333 additions and 162 deletions

1
.gitignore vendored
View File

@ -14,7 +14,6 @@ awx/tower_warnings.log
tower/tower_warnings.log
celerybeat-schedule
awx/ui/static/docs
tools
# Python & setuptools
__pycache__

View File

@ -312,6 +312,7 @@ rpm-build/$(SDIST_TAR_FILE): dist/$(SDIST_TAR_FILE)
mkdir -p rpm-build
cp packaging/rpm/$(NAME).spec rpm-build/
cp packaging/rpm/$(NAME).te rpm-build/
cp packaging/rpm/$(NAME).sysconfig rpm-build/
cp packaging/remove_tower_source.py rpm-build/
if [ "$(OFFICIAL)" != "yes" ] ; then \
(cd dist/ && tar zxf $(SDIST_TAR_FILE)) ; \

View File

@ -277,12 +277,6 @@ class OrderByBackend(BaseFilterBackend):
if field not in ('type', '-type'):
new_order_by.append(field)
queryset = queryset.order_by(*new_order_by)
# Fetch the first result to run the query, otherwise we don't
# always catch the FieldError for invalid field names.
try:
queryset[0]
except IndexError:
pass
return queryset
except FieldError, e:
# Return a 400 for invalid field names.

View File

@ -266,6 +266,13 @@ class BaseSerializer(serializers.ModelSerializer):
summary_fields = SortedDict()
for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items():
try:
# A few special cases where we don't want to access the field
# because it results in additional queries.
if fk == 'job' and isinstance(obj, UnifiedJob):
continue
if fk == 'project' and isinstance(obj, InventorySource):
continue
fkval = getattr(obj, fk, None)
if fkval is None:
continue
@ -777,7 +784,7 @@ class HostSerializer(BaseSerializerWithVariables):
else "",
'status': j.job.status,
'finished': j.job.finished,
} for j in obj.job_host_summaries.filter(job__active=True).order_by('-created')[:5]]})
} for j in obj.job_host_summaries.filter(job__active=True).select_related('job__job_template').order_by('-created')[:5]]})
return d
def _get_host_port_from_name(self, name):
@ -1452,9 +1459,7 @@ class ScheduleSerializer(BaseSerializer):
unified_jobs = reverse('api:schedule_unified_jobs_list', args=(obj.pk,)),
))
if obj.unified_job_template and obj.unified_job_template.active:
#TODO: Figure out why we have to do this
ujt = UnifiedJobTemplate.objects.get(id=obj.unified_job_template.id)
res['unified_job_template'] = ujt.get_absolute_url() #obj.unified_job_template.get_absolute_url()
res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
return res
def validate_unified_job_template(self, attrs, source):

View File

@ -15,6 +15,7 @@ import sys
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.core.exceptions import FieldError
from django.db.models import Q, Count, Sum
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404
@ -62,6 +63,8 @@ def api_exception_handler(exc):
'''
if isinstance(exc, IntegrityError):
exc = ParseError(exc.args[0])
if isinstance(exc, FieldError):
exc = ParseError(exc.args[0])
return exception_handler(exc)
@ -1879,7 +1882,7 @@ class JobRelaunch(GenericAPIView):
obj = self.get_object()
data = {}
data['passwords_needed_to_start'] = obj.passwords_needed_to_start
data['ask_variables_on_launch'] = obj.ask_variables_on_launch
#data['ask_variables_on_launch'] = obj.ask_variables_on_launch
return Response(data)
def post(self, request, *args, **kwargs):

View File

@ -223,7 +223,7 @@ class OrganizationAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by')
qs = qs.select_related('created_by', 'modified_by')
if self.user.is_superuser:
return qs
return qs.filter(Q(admins__in=[self.user]) | Q(users__in=[self.user]))
@ -256,7 +256,7 @@ class InventoryAccess(BaseAccess):
def get_queryset(self, allowed=None):
allowed = allowed or PERMISSION_TYPES_ALLOWING_INVENTORY_READ
qs = Inventory.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'organization')
qs = qs.select_related('created_by', 'modified_by', 'organization')
if self.user.is_superuser:
return qs
qs = qs.filter(organization__active=True)
@ -331,12 +331,12 @@ class HostAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'inventory',
qs = qs.select_related('created_by', 'modified_by', 'inventory',
'last_job__job_template',
'last_job_host_summary')
'last_job_host_summary__job')
qs = qs.prefetch_related('groups')
inventories_qs = self.user.get_queryset(Inventory)
return qs.filter(inventory__in=inventories_qs)
inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(inventory_id__in=inventory_ids)
def can_read(self, obj):
return obj and self.user.can_access(Inventory, 'read', obj.inventory)
@ -404,10 +404,10 @@ class GroupAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'inventory')
qs = qs.select_related('created_by', 'modified_by', 'inventory')
qs = qs.prefetch_related('parents', 'children', 'inventory_source')
inventories_qs = self.user.get_queryset(Inventory)
return qs.filter(inventory__in=inventories_qs)
inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(inventory_id__in=inventory_ids)
def can_read(self, obj):
return obj and self.user.can_access(Inventory, 'read', obj.inventory)
@ -464,10 +464,10 @@ class InventorySourceAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'group', 'inventory')
inventories_qs = self.user.get_queryset(Inventory)
return qs.filter(Q(inventory__in=inventories_qs) |
Q(group__inventory__in=inventories_qs))
qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory')
inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(Q(inventory_id__in=inventory_ids) |
Q(group__inventory_id__in=inventory_ids))
def can_read(self, obj):
if obj and obj.group:
@ -504,7 +504,7 @@ class InventoryUpdateAccess(BaseAccess):
def get_queryset(self):
qs = InventoryUpdate.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'inventory_source__group',
qs = qs.select_related('created_by', 'modified_by', 'inventory_source__group',
'inventory_source__inventory')
inventory_sources_qs = self.user.get_queryset(InventorySource)
return qs.filter(inventory_source__in=inventory_sources_qs)
@ -531,16 +531,24 @@ class CredentialAccess(BaseAccess):
model = Credential
def get_queryset(self):
"""Return the queryset for credentials, based on what the user is
permitted to see.
"""
# Create a base queryset.
# If the user is a superuser, and therefore can see everything, this
# is also sufficient, and we are done.
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'user', 'team')
qs = qs.select_related('created_by', 'modified_by', 'user', 'team')
if self.user.is_superuser:
return qs
orgs_as_admin = self.user.admin_of_organizations.filter(active=True)
# Get the list of organizations for which the user is an admin
orgs_as_admin_ids = set(self.user.admin_of_organizations.filter(active=True).values_list('id', flat=True))
return qs.filter(
Q(user=self.user) |
Q(user__organizations__in=orgs_as_admin) |
Q(user__admin_of_organizations__in=orgs_as_admin) |
Q(team__organization__in=orgs_as_admin, team__active=True) |
Q(user__organizations__id__in=orgs_as_admin_ids) |
Q(user__admin_of_organizations__id__in=orgs_as_admin_ids) |
Q(team__organization__id__in=orgs_as_admin_ids, team__active=True) |
Q(team__users__in=[self.user], team__active=True)
)
@ -598,7 +606,7 @@ class TeamAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'organization')
qs = qs.select_related('created_by', 'modified_by', 'organization')
if self.user.is_superuser:
return qs
return qs.filter(
@ -650,7 +658,7 @@ class ProjectAccess(BaseAccess):
def get_queryset(self):
qs = Project.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'current_update', 'last_update')
qs = qs.select_related('created_by', 'modified_by', 'credential', 'current_update', 'last_update')
if self.user.is_superuser:
return qs
allowed = [PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
@ -696,9 +704,9 @@ class ProjectUpdateAccess(BaseAccess):
def get_queryset(self):
qs = ProjectUpdate.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'project')
projects_qs = self.user.get_queryset(Project)
return qs.filter(project__in=projects_qs)
qs = qs.select_related('created_by', 'modified_by', 'project')
project_ids = set(self.user.get_queryset(Project).values_list('id', flat=True))
return qs.filter(project_id__in=project_ids)
def can_cancel(self, obj):
return self.can_change(obj, {}) and obj.can_cancel
@ -721,15 +729,15 @@ class PermissionAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'user', 'team', 'inventory',
qs = qs.select_related('created_by', 'modified_by', 'user', 'team', 'inventory',
'project')
if self.user.is_superuser:
return qs
orgs_as_admin = self.user.admin_of_organizations.filter(active=True)
orgs_as_admin_ids = set(self.user.admin_of_organizations.filter(active=True).values_list('id', flat=True))
return qs.filter(
Q(user__organizations__in=orgs_as_admin) |
Q(user__admin_of_organizations__in=orgs_as_admin) |
Q(team__organization__in=orgs_as_admin, team__active=True) |
Q(user__organizations__in=orgs_as_admin_ids) |
Q(user__admin_of_organizations__in=orgs_as_admin_ids) |
Q(team__organization__in=orgs_as_admin_ids, team__active=True) |
Q(user=self.user) |
Q(team__users__in=[self.user], team__active=True)
)
@ -813,14 +821,14 @@ class JobTemplateAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'inventory', 'project',
'credential')
qs = qs.select_related('created_by', 'modified_by', 'inventory', 'project',
'credential', 'cloud_credential', 'next_schedule')
if self.user.is_superuser:
return qs
credential_qs = self.user.get_queryset(Credential)
credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True))
base_qs = qs.filter(
Q(credential__in=credential_qs) | Q(credential__isnull=True),
Q(cloud_credential__in=credential_qs) | Q(cloud_credential__isnull=True),
Q(credential_id__in=credential_ids) | Q(credential__isnull=True),
Q(cloud_credential_id__in=credential_ids) | Q(cloud_credential__isnull=True),
)
# FIXME: Check active status on related objects!
org_admin_qs = base_qs.filter(
@ -830,35 +838,30 @@ class JobTemplateAccess(BaseAccess):
allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY]
allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
# perm_qs = base_qs.filter(
# Q(inventory__permissions__user=self.user) | Q(inventory__permissions__team__users__in=[self.user]),
# Q(project__permissions__user=self.user) | Q(project__permissions__team__users__in=[self.user]),
# inventory__permissions__permission_type__in=allowed,
# project__permissions__permission_type__in=allowed,
# inventory__permissions__active=True,
# project__permissions__active=True,
# inventory__permissions__pk=F('project__permissions__pk'),
# )
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
deploy_permissions_ids = set(Permission.objects.filter(
Q(user=self.user) | Q(team_id__in=team_ids),
active=True,
permission_type__in=allowed_deploy,
).values_list('id', flat=True))
check_permissions_ids = set(Permission.objects.filter(
Q(user=self.user) | Q(team_id__in=team_ids),
active=True,
permission_type__in=allowed_check,
).values_list('id', flat=True))
perm_deploy_qs = base_qs.filter(
Q(inventory__permissions__user=self.user) | Q(inventory__permissions__team__users__in=[self.user]),
Q(project__permissions__user=self.user) | Q(project__permissions__team__users__in=[self.user]),
job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__permission_type__in=allowed_deploy,
project__permissions__permission_type__in=allowed_deploy,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__in=deploy_permissions_ids,
project__permissions__in=deploy_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'),
)
perm_check_qs = base_qs.filter(
Q(inventory__permissions__user=self.user) | Q(inventory__permissions__team__users__in=[self.user]),
Q(project__permissions__user=self.user) | Q(project__permissions__team__users__in=[self.user]),
job_type=PERM_INVENTORY_CHECK,
inventory__permissions__permission_type__in=allowed_check,
project__permissions__permission_type__in=allowed_check,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__in=check_permissions_ids,
project__permissions__in=check_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'),
)
@ -1010,13 +1013,14 @@ class JobAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'job_template', 'inventory',
'project', 'credential')
qs = qs.select_related('created_by', 'modified_by', 'job_template', 'inventory',
'project', 'credential', 'cloud_credential', 'job_template')
qs = qs.prefetch_related('unified_job_template')
if self.user.is_superuser:
return qs
credential_qs = self.user.get_queryset(Credential)
credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True))
base_qs = qs.filter(
credential__in=credential_qs,
credential_id__in=credential_ids,
)
org_admin_qs = base_qs.filter(
project__organizations__admins__in=[self.user]
@ -1036,28 +1040,33 @@ class JobAccess(BaseAccess):
# inventory__permissions__pk=F('project__permissions__pk'),
# )
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
deploy_permissions_ids = set(Permission.objects.filter(
Q(user=self.user) | Q(team__in=team_ids),
active=True,
permission_type__in=allowed_deploy,
).values_list('id', flat=True))
check_permissions_ids = set(Permission.objects.filter(
Q(user=self.user) | Q(team__in=team_ids),
active=True,
permission_type__in=allowed_check,
).values_list('id', flat=True))
perm_deploy_qs = base_qs.filter(
Q(inventory__permissions__user=self.user) | Q(inventory__permissions__team__users__in=[self.user]),
Q(project__permissions__user=self.user) | Q(project__permissions__team__users__in=[self.user]),
job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__permission_type__in=allowed_deploy,
project__permissions__permission_type__in=allowed_deploy,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__in=deploy_permissions_ids,
project__permissions__in=deploy_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'),
)
perm_check_qs = base_qs.filter(
Q(inventory__permissions__user=self.user) | Q(inventory__permissions__team__users__in=[self.user]),
Q(project__permissions__user=self.user) | Q(project__permissions__team__users__in=[self.user]),
job_type=PERM_INVENTORY_CHECK,
inventory__permissions__permission_type__in=allowed_check,
project__permissions__permission_type__in=allowed_check,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__in=check_permissions_ids,
project__permissions__in=check_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'),
)
# FIXME: I *think* this should work... needs more testing.
return org_admin_qs | perm_deploy_qs | perm_check_qs
@ -1158,7 +1167,7 @@ class JobHostSummaryAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.distinct()
qs = qs.select_related('created_by', 'job', 'job__job_template',
qs = qs.select_related('created_by', 'modified_by', 'job', 'job__job_template',
'host')
if self.user.is_superuser:
return qs
@ -1184,7 +1193,7 @@ class JobEventAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.distinct()
qs = qs.select_related('created_by', 'job', 'job__job_template',
qs = qs.select_related('created_by', 'modified_by', 'job', 'job__job_template',
'host', 'parent')
qs = qs.prefetch_related('hosts', 'children')
@ -1228,7 +1237,17 @@ class UnifiedJobTemplateAccess(BaseAccess):
qs = qs.filter(Q(Project___in=project_qs) |
Q(InventorySource___in=inventory_source_qs) |
Q(JobTemplate___in=job_template_qs))
# FIXME: select/prefetch to optimize!
qs = qs.select_related(
'created_by',
'modified_by',
'project',
'inventory',
'credential',
'cloud_credential',
'next_schedule',
'last_job',
'current_job',
)
return qs
class UnifiedJobAccess(BaseAccess):
@ -1249,7 +1268,21 @@ class UnifiedJobAccess(BaseAccess):
Q(InventoryUpdate___in=inventory_update_qs) |
Q(Job___in=job_qs) |
Q(SystemJob___in=system_job_qs))
# FIXME: select/prefetch to optimize!
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',
)
qs = qs.prefetch_related('unified_job_template')
return qs
class ScheduleAccess(BaseAccess):
@ -1261,7 +1294,8 @@ class ScheduleAccess(BaseAccess):
def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('unified_job_template')
qs = qs.select_related('created_by', 'modified_by')
qs = qs.prefetch_related('unified_job_template')
if self.user.is_superuser:
return qs
job_template_qs = self.user.get_queryset(JobTemplate)

View File

@ -137,6 +137,13 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique):
editable=False,
)
def get_absolute_url(self):
real_instance = self.get_real_instance()
if real_instance != self:
return real_instance.get_absolute_url()
else:
return ''
def unique_error_message(self, model_class, unique_check):
# If polymorphic_ctype is part of a unique check, return a list of the
# remaining fields instead of the error message.
@ -438,6 +445,13 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
editable=False,
)
def get_absolute_url(self):
real_instance = self.get_real_instance()
if real_instance != self:
return real_instance.get_absolute_url()
else:
return ''
@classmethod
def _get_task_class(cls):
raise NotImplementedError # Implement in subclasses.

View File

@ -35,6 +35,7 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
list = PortalJobTemplateList,
view= GenerateList,
defaultUrl = GetBasePath('job_templates'),
max_rows,
buttons = {
refresh: {
mode: 'all',
@ -79,6 +80,7 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
searchSize: 'col-lg-6 col-md-6'
});
$scope.job_templatePageSize = $scope.getMaxRows();
SearchInit({
scope: $scope,
@ -89,16 +91,10 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
url: defaultUrl,
pageSize: $scope.job_templatePageSize
});
// Called from Inventories tab, host failed events link:
if ($routeParams.name) {
$scope[list.iterator + 'SearchField'] = 'name';
$scope[list.iterator + 'SearchValue'] = $routeParams.name;
$scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
}
$scope.search(list.iterator);
PortalJobsWidget({
@ -126,22 +122,53 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
jobs_scope.search('portal_job'); //processEvent(event);
});
$scope.getMaxRows = function(){
var docw = $(window).width(),
box_height, available_height, search_row, page_row, height, header, row_height;
available_height = Math.floor($(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#refresh-row').outerHeight() - 35);
$('.portal-job-template-container').height(available_height);
$('.portal-container').height(available_height);
search_row = Math.max($('.search-row:eq(0)').outerHeight(), 50);
page_row = Math.max($('.page-row:eq(0)').outerHeight(), 33);
header = 0; //Math.max($('#completed_jobs_table thead').height(), 41);
height = Math.floor(available_height) - header - page_row - search_row ;
if (docw < 765 && docw >= 493) {
row_height = 27;
}
else if (docw < 493) {
row_height = 47;
}
else if (docw < 865) {
row_height = 87;
}
else if (docw < 925) {
row_height = 67;
}
else if (docw < 1415) {
row_height = 47;
}
else {
row_height = 35;
}
max_rows = Math.floor(height / row_height);
if (max_rows < 5){
box_height = header+page_row + search_row + 40 + (5 * row_height);
if (docw < 1140) {
box_height += 40;
}
// $('.portal-job-template-container').height(box_height);
max_rows = 5;
}
return max_rows;
};
$scope.submitJob = function (id) {
PlaybookRun({ scope: $scope, id: id });
};
$scope.refresh = function () {
$scope.$emit('LoadPortal');
// Wait('start');
// loadedCount = 0;
// Rest.setUrl(GetBasePath('dashboard'));
// Rest.get()
// .success(function (data) {
// $scope.$emit('dashboardReady', data);
// })
// .error(function (data, status) {
// ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status });
// });
};
$scope.refresh();

View File

@ -75,7 +75,7 @@ angular.module('SourceFormDefinition', [])
dataContainer: 'body'
},
group_by: {
label: 'Group By',
label: 'Only Group By',
type: 'text',
ngShow: "source && source.value == 'ec2'",
addRequired: false,
@ -83,40 +83,58 @@ angular.module('SourceFormDefinition', [])
awMultiselect: 'group_by_choices',
dataTitle: 'Group By',
dataPlacement: 'right',
awPopOver: "<p>FIXME: Create these automatic groups by default.</p>",
awPopOver: "<p>FIXME: Create these automatic groups by default. give examples</p>",
dataContainer: 'body'
},
group_tag_filters: {
label: 'Tag Filters',
type: 'text',
ngShow: "source && source.value == 'ec2' && group_by.value.indexOf('tag_keys') >= 0", // FIXME: Not sure what's needed to make the last expression work.
addRequired: false,
editRequired: false,
dataTitle: 'Tag Filters',
dataPlacement: 'right',
awPopOver: "<p>FIXME: When grouping by tags, specify which tag keys become groups.</p>",
dataContainer: 'body'
},
custom_script: {
// group_tag_filters: {
// label: 'Tag Filters',
// type: 'text',
// ngShow: "source && source.value == 'ec2' && group_by.value.indexOf('tag_keys') >= 0", // FIXME: Not sure what's needed to make the last expression work.
// addRequired: false,
// editRequired: false,
// dataTitle: 'Tag Filters',
// dataPlacement: 'right',
// awPopOver: "<p>FIXME: When grouping by tags, specify which tag keys become groups.</p>",
// dataContainer: 'body'
// },
source_script: {
label : "Custom Inventory Scripts",
type: 'lookup',
ngShow: "source && source.value !== '' && source.value === 'custom'",
sourceModel: 'custom_script',
sourceModel: 'source_script',
sourceField: 'name',
ngClick: 'lookUpCustomScript()',
ngClick: 'lookUpCustom_inventory()',
addRequired: false,
editRequired: false
},
source_vars: {
label: 'Source Variables',
ngShow: "source && (source.value == 'file' || source.value == 'ec2' || source.value == 'custom')",
extra_vars: {
label: 'Environment Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value=='custom')",
type: 'textarea',
addRequired: false,
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: 'Source Variables',
dataTitle: "Environment Variables", //'<p ng-show=source.value=="ec2">Source Variables<p>',
dataPlacement: 'right',
awPopOver: "<p>Provide key/value pairs using either YAML or JSON.</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />\"somevar\": \"somevalue\",<br />\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n",
dataContainer: 'body'
},
source_vars: {
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'file' || source.value == 'ec2')",
type: 'textarea',
addRequired: false,
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
dataTitle: "Source Variables",
dataPlacement: 'right',
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
"<a href=\"https://github.com/ansible/ansible/blob/devel/plugins/inventory/ec2.ini\" target=\"_blank\">" +

View File

@ -63,7 +63,7 @@ angular.module('CreateCustomInventoryHelper', [ 'Utilities', 'RestServices', 'Sc
SearchInit({
scope: scope,
set: 'custum_inventories',
set: 'custom_inventories',
list: list,
url: defaultUrl
});
@ -187,12 +187,10 @@ function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInvento
view = GenerateList,
list = CustomInventoryList,
url = GetBasePath('inventory_scripts');
// base = $location.path().replace(/^\//, '').split('/')[0];
generator.inject(form, { id:'custom-script-dialog', mode: 'add' , scope:scope, related: false, breadCrumbs: false});
generator.reset();
// Save
scope.formSave = function () {
generator.clearApiErrors();
@ -211,7 +209,7 @@ function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInvento
SearchInit({
scope: scope,
set: 'custum_inventories',
set: 'custom_inventories',
list: list,
url: url
});
@ -252,7 +250,6 @@ function($compile, CustomInventoryList, Rest, Wait, GenerateList, CustomInventor
list = CustomInventoryList,
master = {},
url = GetBasePath('inventory_scripts');
// base = $location.path().replace(/^\//, '').split('/')[0];
generator.inject(form, {
id:'custom-script-dialog',

View File

@ -207,13 +207,13 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
])
.factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange',
function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange) {
.factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', 'CustomInventoryList' ,
function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList) {
return function (params) {
var scope = params.scope,
form = params.form,
kind, url, callback;
kind, url, callback, invUrl;
if (!Empty(scope.source)) {
if (scope.source.value === 'file') {
@ -234,7 +234,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
$('#source_form').removeClass('squeeze');
} else if (scope.source.value === 'ec2') {
scope.source_region_choices = scope.ec2_regions;
//$('#s2id_group_source_regions').select2('data', []);
$('#s2id_source_source_regions').select2('data', [{
id: 'all',
text: 'All'
@ -273,6 +272,21 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}]);
$('#source_form').addClass('squeeze');
}
if(scope.source.value==="custom"){
invUrl = GetBasePath('inventory_scripts');
LookUpInit({
url: invUrl,
scope: scope,
form: form,
// current_item: null,
list: CustomInventoryList,
field: 'source_script',
input_type: 'radio'
});
scope.extra_vars = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'extra_vars', parse_variable: form.fields.extra_vars.parseTypeName,
field_id: 'source_extra_vars', onReady: callback });
}
if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure' || scope.source.value === 'vmware') {
kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : (scope.source.value === 'vmware') ? 'vmware' : 'aws' ;
url = GetBasePath('credentials') + '?cloud=true&kind=' + kind;
@ -850,6 +864,8 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
Wait('start');
ParseTypeChange({ scope: sources_scope, variable: 'source_vars', parse_variable: SourceForm.fields.source_vars.parseTypeName,
field_id: 'source_source_vars', onReady: waitStop });
ParseTypeChange({ scope: sources_scope, variable: 'extra_vars', parse_variable: SourceForm.fields.extra_vars.parseTypeName,
field_id: 'source_extra_vars', onReady: waitStop });
}
}
else if ($(e.target).text() === 'Schedule') {
@ -946,7 +962,12 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
// Parse source_vars, converting to YAML.
sources_scope.source_vars = ParseVariableString(data.source_vars);
master.source_vars = sources_scope.variables;
} else if (data[fld] !== undefined) {
}
// else if(fld === "source_script"){
// sources_scope[fld] = data
// }
else if (data[fld] !== undefined) {
sources_scope[fld] = data[fld];
master[fld] = sources_scope[fld];
}
@ -1144,6 +1165,7 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
credential: sources_scope.credential,
overwrite: sources_scope.overwrite,
overwrite_vars: sources_scope.overwrite_vars,
source_script: sources_scope.source_script,
update_on_launch: sources_scope.update_on_launch,
update_cache_timeout: (sources_scope.update_cache_timeout || 0)
};
@ -1156,11 +1178,19 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
}
data.source_regions = r.join();
if (sources_scope.source && (sources_scope.source.value === 'ec2' || sources_scope.source.value === 'custom')) {
if (sources_scope.source && (sources_scope.source.value === 'ec2')) {
// for ec2, validate variable data
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.source_vars, true);
}
if (sources_scope.source && (sources_scope.source.value === 'custom')) {
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.extra_vars, true);
}
if(sources_scope.source.value === 'custom'){
delete(data.credential);
}
if (!parseError) {
Rest.setUrl(sources_scope.source_url);
Rest.put(data)

View File

@ -43,8 +43,10 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
}
}
}
delete(job_launch_data.extra_vars);
if(!Empty(scope.credential)){
job_launch_data.credential = scope.credential;
}
Rest.setUrl(url);
Rest.post(job_launch_data)
.success(function(data) {
@ -386,6 +388,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
function buildHtml(extra_vars){
html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope });
html = html.replace("</form>", "");
scope.helpContainer = "<div style=\"display:inline-block; font-size: 12px; margin-top: 6px;\" class=\"help-container pull-right\">\n" +
"<a href=\"\" id=\"awp-promote\" href=\"\" aw-pop-over=\"{{ helpText }}\" aw-tool-tip=\"Click for help\" aw-pop-over-watch=\"helpText\" " +
"aw-tip-placement=\"top\" data-placement=\"bottom\" data-container=\"body\" data-title=\"Help\" class=\"help-link\"><i class=\"fa fa-question-circle\">" +
@ -444,10 +447,9 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
question.index = index;
requiredAsterisk = (question.required===true) ? "prepend-asterisk" : "";
requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : "";
html+='<div id="taker_'+question.index+'" class="form-group '+requiredAsterisk+' ">';
requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : "";
html += '<label for="'+question.variable+'">'+question.question_name+'</label>\n';
if(!Empty(question.question_description)){
@ -458,7 +460,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(question.type === 'text' ){
html+='<input type="text" id="'+question.variable+'" ng-model="'+question.variable+'" '+
'name="'+question.variable+'" '+
'class="form-control ng-pristine ng-invalid-required ng-invalid" required="" >'+
'class="form-control" ng-required='+question.required+'>'+
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
'job_launch_form.'+question.variable+'.$error.required\">A value is required!</div>'+
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
@ -467,7 +469,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(question.type === "textarea"){
scope[question.variable] = question.default || question.default_textarea;
html+='<textarea id="'+question.variable+'" name="'+question.variable+'" ng-model="'+question.variable+'" '+
'class="form-control ng-pristine ng-invalid-required ng-invalid final" required="" rows="3"></textarea>'+
'class="form-control final" ng-required="'+question.required+'" rows="3"></textarea>'+
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
'job_launch_form.'+question.variable+'.$error.required\">A value is required!</div>'+
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
@ -480,7 +482,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
html+='<div class="survey_taker_input" > ';
for( j = 0; j<choices.length; j++){
checked = (!Empty(question.default) && question.default.indexOf(choices[j])!==-1) ? "checked" : "";
html+= '<input type="'+element+'" class="mc" ng-model="'+question.variable+'" ng-required="!'+question.variable+'" name="'+question.variable+ ' " id="'+question.variable+'" value=" '+choices[j]+' " '+checked+' >' +
html+= '<input type="'+element+'" class="mc" ng-model="'+question.variable+'" ng-required="'+question.required+'" name="'+question.variable+ ' " id="'+question.variable+'" value=" '+choices[j]+' " '+checked+' >' +
'<span>'+choices[j] +'</span><br>' ;
}
html+= '<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
@ -515,18 +517,23 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(question.type === 'integer'){
min = (!Empty(question.min)) ? Number(question.min) : "";
max = (!Empty(question.max)) ? Number(question.max) : "" ;
html+='<input type="number" id="'+question.variable+'" class=" form-control" name="'+question.variable+'" ng-min="'+min+'" ng-max="'+max+'" ng-model="'+question.variable+' " integer>'+
html+='<input type="number" id="'+question.variable+'" ng-model="'+question.variable+'" class="form-control" name="'+question.variable+'" ng-min="'+min+'" ng-max="'+max+'" integer>'+
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
'job_launch_form.'+question.variable+'.$error.required\">A value is required!</div>'+
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>'+
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.number || job_launch_form.'+question.variable+'.$error.integer">This is not valid integer!</div>'+
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.ngMin || job_launch_form.'+question.variable+'.$error.ngMax"> The value must be in range {{'+min+'}} to {{'+max+'}}!</div>';
}
if(question.type === "float"){
min = (!Empty(question.min)) ? question.min : "";
max = (!Empty(question.max)) ? question.max : "" ;
defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ;
html+='<input type="number" id="'+question.variable+'" class=" form-control" name="'+question.variable+'" ng-min="'+min+'" ng-max="'+max+'" ng-model="'+question.variable+'" smart-float>'+
html+='<input type="number" id="'+question.variable+'" ng-model="'+question.variable+'" class=" form-control" name="'+question.variable+'" ng-min="'+min+'" ng-max="'+max+'" smart-float>'+
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.number || job_launch_form.'+question.variable+'.$error.float">This is not valid float!</div>'+
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.ngMin || job_launch_form.'+question.variable+'.$error.ngMax"> The value must be in range {{'+min+'}} to {{'+max+'}}!</div>';
// '<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$dirty || job_launch_form.'+question.variable+'.$error.required"> A value is required!</div>';
}
html+='</div>';
if(question.index === scope.survey_questions.length-1){
@ -739,6 +746,12 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(passwords.length>0){
scope.$emit('PromptForPasswords', passwords, html, url);
}
else if (scope.ask_variables_on_launch){
scope.$emit('PromptForVars', html, url);
}
else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) {
scope.$emit('PromptForSurvey', html, url);
}
else scope.$emit('StartPlaybookRun', url);
})
.error(function (data, status) {
@ -758,8 +771,8 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
launch_url = url;//data.related.start;
scope.passwords_needed_to_start = data.passwords_needed_to_start;
scope.prompt_for_vars = data.ask_variables_on_launch;
// scope.extra_vars = data.variables_needed_to_start;
scope.survey_enabled = data.survey_enabled;
scope.ask_variables_on_launch = data.ask_variables_on_launch;
html = '<form class="ng-valid ng-valid-required" name="job_launch_form" id="job_launch_form" autocomplete="off" nonvalidate>';

View File

@ -138,7 +138,7 @@ angular.module('PaginationHelpers', ['Utilities', 'RefreshHelper', 'RefreshRelat
connect = (/\/$/.test(new_url)) ? '?' : '&';
new_url += connect + 'page=' + page;
new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] +
'&page_size=' + scope[iterator + '_page_size'] : 'page_size=' + scope[iterator + 'PageSize'];
'&page_size=' + scope[iterator + '_page_size'] : '&page_size=' + scope[iterator + 'PageSize'];
Wait('start');
Refresh({ scope: scope, set: set, iterator: iterator, url: new_url });
};

View File

@ -12,7 +12,7 @@
angular.module('CustomInventoryListDefinition', [])
.value('CustomInventoryList', {
name: 'custum_inventories',
name: 'custom_inventories',
iterator: 'custom_inventory',
selectTitle: 'Add custom inventory',
editTitle: 'Custom Inventories',

View File

@ -11,8 +11,8 @@
'use strict';
angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
.factory('PortalJobsWidget', ['$rootScope', '$compile', 'LoadSchedulesScope', 'LoadJobsScope', 'PortalJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath',
function ($rootScope, $compile, LoadSchedulesScope, LoadJobsScope, PortalJobsList, ScheduledJobsList, GetChoices, GetBasePath) {
.factory('PortalJobsWidget', ['$rootScope', '$compile', 'LoadSchedulesScope', 'LoadJobsScope', 'PortalJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'PortalJobTemplateList',
function ($rootScope, $compile, LoadSchedulesScope, LoadJobsScope, PortalJobsList, ScheduledJobsList, GetChoices, GetBasePath, PortalJobTemplateList) {
return function (params) {
var scope = params.scope,
target = params.target,
@ -70,6 +70,7 @@ angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
});
$(window).resize(_.debounce(function() {
resizePortalJobsWidget();
}, 500));
@ -150,8 +151,9 @@ angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
setPortalJobsHeight();
jobs_scope[PortalJobsList.iterator + '_page_size'] = max_rows;
jobs_scope.changePageSize(PortalJobsList.name, PortalJobsList.iterator, false);
// scheduled_scope[ScheduledJobsList.iterator + '_page_size'] = max_rows;
// scheduled_scope.changePageSize(ScheduledJobsList.name, ScheduledJobsList.iterator, false);
scope[PortalJobTemplateList.iterator + '_page_size'] = max_rows;
scope[PortalJobTemplateList.iterator + 'PageSize'] = max_rows;
scope.changePageSize(PortalJobTemplateList.name, PortalJobTemplateList.iterator, false);
}

View File

@ -1,4 +1,5 @@
<div id="configure-jobs" >
<div id="configure-jobs" ><div>
<div class="tab-pane" id="configure-schedules-tab">
<div id="configure-schedules-overlay"></div>
<div id="configure-schedules-list"></div>

View File

@ -205,7 +205,7 @@
<a id="mobile_munin" target="_blank" ng-show="user_is_superuser" href="/munin" ng-hide="portalMode===true">Monitor Tower</a></li>
<a href="#portal" id="mobile_portal_link" ng-hide="portalMode===true">Portal Mode</a></li>
<a href="" id="mobile_view_license" ng-click="viewLicense()" ng-hide="portalMode===true">View License</a></li>
<a href="" id="mobile_view_leave_portal" ng-click="leavePortal()" ng-show="portalMode===true">Exit Portal Mode</a></li>
<a href="" id="mobile_view_leave_portal" ng-click="leavePortal()" ng-show="portalMode===true">Exit Portal</a></li>
<a href="#/logout" id="mobile_logout">Logout</a>
</nav>
@ -398,7 +398,37 @@
<div id="license-modal-dialog" style="display: none;"></div>
<div id="about-modal-dialog" style="display: none;" ng-include=" 'static/partials/cowsay-about.html ' "></div>
<div id="custom-script-dialog" style="display:none;" > </div>
<div id='configure-tower-dialog' style="display:none" ng-include = " ' static/partials/configure_tower.html' "></div>
<div id='configure-tower-dialog' style="display:none" >
<div id="configure-jobs" ></div>
<div class="tab-pane" id="configure-schedules-tab">
<div id="configure-schedules-overlay"></div>
<div id="configure-schedules-list"></div>
<div id="configure-schedules-form-container">
<div id="configure-schedules-title">
<h4 ng-bind="schedulesTitle"></h4>
<button type="button" class="close pull-right" ng-click="cancelScheduleForm()">x</button>
</div>
<div id="configure-schedules-form-container-body">
<div id="configure-schedules-form"></div>
<div id="configure-schedules-detail"></div>
</div>
<div id="configure-schedules-buttons" >
<a id="configure-schedules-flip-link" ng-show="formShowing" ng-click="showScheduleDetail()" href=""><i class="fa fa-search-plus"></i> View Details</a>
<a id="configure-schedules-flip-link" ng-show="!formShowing" ng-click="showScheduleDetail()" href=""><i class="fa fa-arrow-circle-left"></i> Back to options</a>
<button type="button" class="btn btn-default btn-sm" id="configure-schedule-delete-button" ng-show="mode==='edit'" ng-click="deleteSystemSchedule($event)"><i class="fa fa-trash-o"></i> Delete</button>
<button type="button" class="btn btn-default btn-sm" id="configure-reset-button" ng-click="cancelScheduleForm()"><i class="fa fa-times"></i> Cancel</button>
<button type="button" class="btn btn-primary btn-sm" id="configure-save-button" ng-click="saveScheduleForm()"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<div id="prompt-for-days" style="display:none">
<form name="prompt_for_days_form" id="prompt_for_days_form">
How many days of data would you like to <b>keep</b>? <br>
<input type="number" min="1" id="days_to_keep" name="days_to_keep" value="30" ng-required="true" class="form-control ng-pristine ng-invalid-required ng-invalid" style="margin-top:10px;">
<div class="error" ng-show="prompt_for_days_form.days_to_keep.$dirty && copy_form.new_copy_name.$error.required">A value is required!</div></input>
</form>
</div>

View File

@ -16,15 +16,18 @@
# Description: Ansible Tower provides an easy-to-use UI and dashboard, role-based access control and more for your Ansible initiative
### END INIT INFO
if [ -e /etc/debian_version ]
then
SERVICES=(postgresql redis-server apache2 supervisor)
# Default configured services
if [ -e /etc/debian_version ]; then
TOWER_CONFIG="/etc/default/ansible-tower"
else
SERVICES=(postgresql redis httpd supervisord)
TOWER_CONFIG="/etc/sysconfig/ansible-tower"
fi
# Load default configuration
[ -e "${TOWER_CONFIG}" ] && . "${TOWER_CONFIG}"
service_action() {
for svc in ${SERVICES[@]}; do
for svc in ${TOWER_SERVICES}; do
service ${svc} $1
this_return=$?
if [ $this_return -gt $worst_return ]; then