Merge branch 'master' into reintroduce-zeromq-unstable

This commit is contained in:
Luke Sneeringer
2014-11-25 09:01:59 -06:00
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 tower/tower_warnings.log
celerybeat-schedule celerybeat-schedule
awx/ui/static/docs awx/ui/static/docs
tools
# Python & setuptools # Python & setuptools
__pycache__ __pycache__

View File

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

View File

@@ -277,12 +277,6 @@ class OrderByBackend(BaseFilterBackend):
if field not in ('type', '-type'): if field not in ('type', '-type'):
new_order_by.append(field) new_order_by.append(field)
queryset = queryset.order_by(*new_order_by) 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 return queryset
except FieldError, e: except FieldError, e:
# Return a 400 for invalid field names. # Return a 400 for invalid field names.

View File

@@ -266,6 +266,13 @@ class BaseSerializer(serializers.ModelSerializer):
summary_fields = SortedDict() summary_fields = SortedDict()
for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items(): for fk, related_fields in SUMMARIZABLE_FK_FIELDS.items():
try: 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) fkval = getattr(obj, fk, None)
if fkval is None: if fkval is None:
continue continue
@@ -777,7 +784,7 @@ class HostSerializer(BaseSerializerWithVariables):
else "", else "",
'status': j.job.status, 'status': j.job.status,
'finished': j.job.finished, '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 return d
def _get_host_port_from_name(self, name): 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,)), unified_jobs = reverse('api:schedule_unified_jobs_list', args=(obj.pk,)),
)) ))
if obj.unified_job_template and obj.unified_job_template.active: if obj.unified_job_template and obj.unified_job_template.active:
#TODO: Figure out why we have to do this res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
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()
return res return res
def validate_unified_job_template(self, attrs, source): def validate_unified_job_template(self, attrs, source):

View File

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

View File

@@ -223,7 +223,7 @@ class OrganizationAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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: if self.user.is_superuser:
return qs return qs
return qs.filter(Q(admins__in=[self.user]) | Q(users__in=[self.user])) 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): def get_queryset(self, allowed=None):
allowed = allowed or PERMISSION_TYPES_ALLOWING_INVENTORY_READ allowed = allowed or PERMISSION_TYPES_ALLOWING_INVENTORY_READ
qs = Inventory.objects.filter(active=True).distinct() 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: if self.user.is_superuser:
return qs return qs
qs = qs.filter(organization__active=True) qs = qs.filter(organization__active=True)
@@ -331,12 +331,12 @@ class HostAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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__job_template',
'last_job_host_summary') 'last_job_host_summary__job')
qs = qs.prefetch_related('groups') qs = qs.prefetch_related('groups')
inventories_qs = self.user.get_queryset(Inventory) inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(inventory__in=inventories_qs) return qs.filter(inventory_id__in=inventory_ids)
def can_read(self, obj): def can_read(self, obj):
return obj and self.user.can_access(Inventory, 'read', obj.inventory) return obj and self.user.can_access(Inventory, 'read', obj.inventory)
@@ -404,10 +404,10 @@ class GroupAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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') qs = qs.prefetch_related('parents', 'children', 'inventory_source')
inventories_qs = self.user.get_queryset(Inventory) inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(inventory__in=inventories_qs) return qs.filter(inventory_id__in=inventory_ids)
def can_read(self, obj): def can_read(self, obj):
return obj and self.user.can_access(Inventory, 'read', obj.inventory) return obj and self.user.can_access(Inventory, 'read', obj.inventory)
@@ -464,10 +464,10 @@ class InventorySourceAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'group', 'inventory') qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory')
inventories_qs = self.user.get_queryset(Inventory) inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
return qs.filter(Q(inventory__in=inventories_qs) | return qs.filter(Q(inventory_id__in=inventory_ids) |
Q(group__inventory__in=inventories_qs)) Q(group__inventory_id__in=inventory_ids))
def can_read(self, obj): def can_read(self, obj):
if obj and obj.group: if obj and obj.group:
@@ -504,7 +504,7 @@ class InventoryUpdateAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = InventoryUpdate.objects.filter(active=True).distinct() 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_source__inventory')
inventory_sources_qs = self.user.get_queryset(InventorySource) inventory_sources_qs = self.user.get_queryset(InventorySource)
return qs.filter(inventory_source__in=inventory_sources_qs) return qs.filter(inventory_source__in=inventory_sources_qs)
@@ -531,16 +531,24 @@ class CredentialAccess(BaseAccess):
model = Credential model = Credential
def get_queryset(self): 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 = 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: if self.user.is_superuser:
return qs 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( return qs.filter(
Q(user=self.user) | Q(user=self.user) |
Q(user__organizations__in=orgs_as_admin) | Q(user__organizations__id__in=orgs_as_admin_ids) |
Q(user__admin_of_organizations__in=orgs_as_admin) | Q(user__admin_of_organizations__id__in=orgs_as_admin_ids) |
Q(team__organization__in=orgs_as_admin, team__active=True) | Q(team__organization__id__in=orgs_as_admin_ids, team__active=True) |
Q(team__users__in=[self.user], team__active=True) Q(team__users__in=[self.user], team__active=True)
) )
@@ -598,7 +606,7 @@ class TeamAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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: if self.user.is_superuser:
return qs return qs
return qs.filter( return qs.filter(
@@ -650,7 +658,7 @@ class ProjectAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = Project.objects.filter(active=True).distinct() 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: if self.user.is_superuser:
return qs return qs
allowed = [PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK] allowed = [PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
@@ -696,9 +704,9 @@ class ProjectUpdateAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = ProjectUpdate.objects.filter(active=True).distinct() qs = ProjectUpdate.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'project') qs = qs.select_related('created_by', 'modified_by', 'project')
projects_qs = self.user.get_queryset(Project) project_ids = set(self.user.get_queryset(Project).values_list('id', flat=True))
return qs.filter(project__in=projects_qs) return qs.filter(project_id__in=project_ids)
def can_cancel(self, obj): def can_cancel(self, obj):
return self.can_change(obj, {}) and obj.can_cancel return self.can_change(obj, {}) and obj.can_cancel
@@ -721,15 +729,15 @@ class PermissionAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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') 'project')
if self.user.is_superuser: if self.user.is_superuser:
return qs 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( return qs.filter(
Q(user__organizations__in=orgs_as_admin) | Q(user__organizations__in=orgs_as_admin_ids) |
Q(user__admin_of_organizations__in=orgs_as_admin) | Q(user__admin_of_organizations__in=orgs_as_admin_ids) |
Q(team__organization__in=orgs_as_admin, team__active=True) | Q(team__organization__in=orgs_as_admin_ids, team__active=True) |
Q(user=self.user) | Q(user=self.user) |
Q(team__users__in=[self.user], team__active=True) Q(team__users__in=[self.user], team__active=True)
) )
@@ -813,14 +821,14 @@ class JobTemplateAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'inventory', 'project', qs = qs.select_related('created_by', 'modified_by', 'inventory', 'project',
'credential') 'credential', 'cloud_credential', 'next_schedule')
if self.user.is_superuser: if self.user.is_superuser:
return qs 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( base_qs = qs.filter(
Q(credential__in=credential_qs) | Q(credential__isnull=True), Q(credential_id__in=credential_ids) | Q(credential__isnull=True),
Q(cloud_credential__in=credential_qs) | Q(cloud_credential__isnull=True), Q(cloud_credential_id__in=credential_ids) | Q(cloud_credential__isnull=True),
) )
# FIXME: Check active status on related objects! # FIXME: Check active status on related objects!
org_admin_qs = base_qs.filter( org_admin_qs = base_qs.filter(
@@ -830,35 +838,30 @@ class JobTemplateAccess(BaseAccess):
allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY] allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY]
allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK] allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
# perm_qs = base_qs.filter( team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
# 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]), deploy_permissions_ids = set(Permission.objects.filter(
# inventory__permissions__permission_type__in=allowed, Q(user=self.user) | Q(team_id__in=team_ids),
# project__permissions__permission_type__in=allowed, active=True,
# inventory__permissions__active=True, permission_type__in=allowed_deploy,
# project__permissions__active=True, ).values_list('id', flat=True))
# inventory__permissions__pk=F('project__permissions__pk'), 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( 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, job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__permission_type__in=allowed_deploy, inventory__permissions__in=deploy_permissions_ids,
project__permissions__permission_type__in=allowed_deploy, project__permissions__in=deploy_permissions_ids,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
) )
perm_check_qs = base_qs.filter( 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, job_type=PERM_INVENTORY_CHECK,
inventory__permissions__permission_type__in=allowed_check, inventory__permissions__in=check_permissions_ids,
project__permissions__permission_type__in=allowed_check, project__permissions__in=check_permissions_ids,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
) )
@@ -1010,13 +1013,14 @@ class JobAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() qs = self.model.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'job_template', 'inventory', qs = qs.select_related('created_by', 'modified_by', 'job_template', 'inventory',
'project', 'credential') 'project', 'credential', 'cloud_credential', 'job_template')
qs = qs.prefetch_related('unified_job_template')
if self.user.is_superuser: if self.user.is_superuser:
return qs 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( base_qs = qs.filter(
credential__in=credential_qs, credential_id__in=credential_ids,
) )
org_admin_qs = base_qs.filter( org_admin_qs = base_qs.filter(
project__organizations__admins__in=[self.user] project__organizations__admins__in=[self.user]
@@ -1036,28 +1040,33 @@ class JobAccess(BaseAccess):
# inventory__permissions__pk=F('project__permissions__pk'), # 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( 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, job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__permission_type__in=allowed_deploy, inventory__permissions__in=deploy_permissions_ids,
project__permissions__permission_type__in=allowed_deploy, project__permissions__in=deploy_permissions_ids,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
) )
perm_check_qs = base_qs.filter( 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, job_type=PERM_INVENTORY_CHECK,
inventory__permissions__permission_type__in=allowed_check, inventory__permissions__in=check_permissions_ids,
project__permissions__permission_type__in=allowed_check, project__permissions__in=check_permissions_ids,
inventory__permissions__active=True,
project__permissions__active=True,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
) )
# FIXME: I *think* this should work... needs more testing. # FIXME: I *think* this should work... needs more testing.
return org_admin_qs | perm_deploy_qs | perm_check_qs return org_admin_qs | perm_deploy_qs | perm_check_qs
@@ -1158,7 +1167,7 @@ class JobHostSummaryAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.distinct() 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') 'host')
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
@@ -1184,7 +1193,7 @@ class JobEventAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.distinct() 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') 'host', 'parent')
qs = qs.prefetch_related('hosts', 'children') qs = qs.prefetch_related('hosts', 'children')
@@ -1228,7 +1237,17 @@ class UnifiedJobTemplateAccess(BaseAccess):
qs = qs.filter(Q(Project___in=project_qs) | qs = qs.filter(Q(Project___in=project_qs) |
Q(InventorySource___in=inventory_source_qs) | Q(InventorySource___in=inventory_source_qs) |
Q(JobTemplate___in=job_template_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 return qs
class UnifiedJobAccess(BaseAccess): class UnifiedJobAccess(BaseAccess):
@@ -1249,7 +1268,21 @@ class UnifiedJobAccess(BaseAccess):
Q(InventoryUpdate___in=inventory_update_qs) | Q(InventoryUpdate___in=inventory_update_qs) |
Q(Job___in=job_qs) | Q(Job___in=job_qs) |
Q(SystemJob___in=system_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 return qs
class ScheduleAccess(BaseAccess): class ScheduleAccess(BaseAccess):
@@ -1261,7 +1294,8 @@ class ScheduleAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True).distinct() 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: if self.user.is_superuser:
return qs return qs
job_template_qs = self.user.get_queryset(JobTemplate) job_template_qs = self.user.get_queryset(JobTemplate)

View File

@@ -137,6 +137,13 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique):
editable=False, 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): def unique_error_message(self, model_class, unique_check):
# If polymorphic_ctype is part of a unique check, return a list of the # If polymorphic_ctype is part of a unique check, return a list of the
# remaining fields instead of the error message. # remaining fields instead of the error message.
@@ -438,6 +445,13 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
editable=False, 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 @classmethod
def _get_task_class(cls): def _get_task_class(cls):
raise NotImplementedError # Implement in subclasses. raise NotImplementedError # Implement in subclasses.

View File

@@ -35,6 +35,7 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
list = PortalJobTemplateList, list = PortalJobTemplateList,
view= GenerateList, view= GenerateList,
defaultUrl = GetBasePath('job_templates'), defaultUrl = GetBasePath('job_templates'),
max_rows,
buttons = { buttons = {
refresh: { refresh: {
mode: 'all', mode: 'all',
@@ -79,6 +80,7 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
searchSize: 'col-lg-6 col-md-6' searchSize: 'col-lg-6 col-md-6'
}); });
$scope.job_templatePageSize = $scope.getMaxRows();
SearchInit({ SearchInit({
scope: $scope, scope: $scope,
@@ -89,16 +91,10 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
PaginateInit({ PaginateInit({
scope: $scope, scope: $scope,
list: list, 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); $scope.search(list.iterator);
PortalJobsWidget({ PortalJobsWidget({
@@ -126,22 +122,53 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
jobs_scope.search('portal_job'); //processEvent(event); 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) { $scope.submitJob = function (id) {
PlaybookRun({ scope: $scope, id: id }); PlaybookRun({ scope: $scope, id: id });
}; };
$scope.refresh = function () { $scope.refresh = function () {
$scope.$emit('LoadPortal'); $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(); $scope.refresh();

View File

@@ -75,7 +75,7 @@ angular.module('SourceFormDefinition', [])
dataContainer: 'body' dataContainer: 'body'
}, },
group_by: { group_by: {
label: 'Group By', label: 'Only Group By',
type: 'text', type: 'text',
ngShow: "source && source.value == 'ec2'", ngShow: "source && source.value == 'ec2'",
addRequired: false, addRequired: false,
@@ -83,40 +83,58 @@ angular.module('SourceFormDefinition', [])
awMultiselect: 'group_by_choices', awMultiselect: 'group_by_choices',
dataTitle: 'Group By', dataTitle: 'Group By',
dataPlacement: 'right', 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' dataContainer: 'body'
}, },
group_tag_filters: { // group_tag_filters: {
label: 'Tag Filters', // label: 'Tag Filters',
type: 'text', // 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. // 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, // addRequired: false,
editRequired: false, // editRequired: false,
dataTitle: 'Tag Filters', // dataTitle: 'Tag Filters',
dataPlacement: 'right', // dataPlacement: 'right',
awPopOver: "<p>FIXME: When grouping by tags, specify which tag keys become groups.</p>", // awPopOver: "<p>FIXME: When grouping by tags, specify which tag keys become groups.</p>",
dataContainer: 'body' // dataContainer: 'body'
}, // },
custom_script: { source_script: {
label : "Custom Inventory Scripts", label : "Custom Inventory Scripts",
type: 'lookup', type: 'lookup',
ngShow: "source && source.value !== '' && source.value === 'custom'", ngShow: "source && source.value !== '' && source.value === 'custom'",
sourceModel: 'custom_script', sourceModel: 'source_script',
sourceField: 'name', sourceField: 'name',
ngClick: 'lookUpCustomScript()', ngClick: 'lookUpCustom_inventory()',
addRequired: false, addRequired: false,
editRequired: false editRequired: false
}, },
source_vars: { extra_vars: {
label: 'Source Variables', label: 'Environment Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'file' || source.value == 'ec2' || source.value == 'custom')", ngShow: "source && (source.value=='custom')",
type: 'textarea', type: 'textarea',
addRequired: false, addRequired: false,
editRequird: false, editRequird: false,
rows: 6, rows: 6,
'default': '---', 'default': '---',
parseTypeName: 'envParseType', 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', dataPlacement: 'right',
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " + 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\">" + "<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({ SearchInit({
scope: scope, scope: scope,
set: 'custum_inventories', set: 'custom_inventories',
list: list, list: list,
url: defaultUrl url: defaultUrl
}); });
@@ -187,12 +187,10 @@ function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInvento
view = GenerateList, view = GenerateList,
list = CustomInventoryList, list = CustomInventoryList,
url = GetBasePath('inventory_scripts'); 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.inject(form, { id:'custom-script-dialog', mode: 'add' , scope:scope, related: false, breadCrumbs: false});
generator.reset(); generator.reset();
// Save // Save
scope.formSave = function () { scope.formSave = function () {
generator.clearApiErrors(); generator.clearApiErrors();
@@ -211,7 +209,7 @@ function($compile, SchedulerInit, Rest, Wait, CustomInventoryList, CustomInvento
SearchInit({ SearchInit({
scope: scope, scope: scope,
set: 'custum_inventories', set: 'custom_inventories',
list: list, list: list,
url: url url: url
}); });
@@ -252,7 +250,6 @@ function($compile, CustomInventoryList, Rest, Wait, GenerateList, CustomInventor
list = CustomInventoryList, list = CustomInventoryList,
master = {}, master = {},
url = GetBasePath('inventory_scripts'); url = GetBasePath('inventory_scripts');
// base = $location.path().replace(/^\//, '').split('/')[0];
generator.inject(form, { generator.inject(form, {
id:'custom-script-dialog', id:'custom-script-dialog',

View File

@@ -207,13 +207,13 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
} }
]) ])
.factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', .factory('SourceChange', ['GetBasePath', 'CredentialList', 'LookUpInit', 'Empty', 'Wait', 'ParseTypeChange', 'CustomInventoryList' ,
function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange) { function (GetBasePath, CredentialList, LookUpInit, Empty, Wait, ParseTypeChange, CustomInventoryList) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
form = params.form, form = params.form,
kind, url, callback; kind, url, callback, invUrl;
if (!Empty(scope.source)) { if (!Empty(scope.source)) {
if (scope.source.value === 'file') { if (scope.source.value === 'file') {
@@ -234,7 +234,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
$('#source_form').removeClass('squeeze'); $('#source_form').removeClass('squeeze');
} else if (scope.source.value === 'ec2') { } else if (scope.source.value === 'ec2') {
scope.source_region_choices = scope.ec2_regions; scope.source_region_choices = scope.ec2_regions;
//$('#s2id_group_source_regions').select2('data', []);
$('#s2id_source_source_regions').select2('data', [{ $('#s2id_source_source_regions').select2('data', [{
id: 'all', id: 'all',
text: 'All' text: 'All'
@@ -273,6 +272,21 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}]); }]);
$('#source_form').addClass('squeeze'); $('#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') { 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' ; 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; url = GetBasePath('credentials') + '?cloud=true&kind=' + kind;
@@ -850,6 +864,8 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
Wait('start'); Wait('start');
ParseTypeChange({ scope: sources_scope, variable: 'source_vars', parse_variable: SourceForm.fields.source_vars.parseTypeName, ParseTypeChange({ scope: sources_scope, variable: 'source_vars', parse_variable: SourceForm.fields.source_vars.parseTypeName,
field_id: 'source_source_vars', onReady: waitStop }); 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') { else if ($(e.target).text() === 'Schedule') {
@@ -946,7 +962,12 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
// Parse source_vars, converting to YAML. // Parse source_vars, converting to YAML.
sources_scope.source_vars = ParseVariableString(data.source_vars); sources_scope.source_vars = ParseVariableString(data.source_vars);
master.source_vars = sources_scope.variables; 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]; sources_scope[fld] = data[fld];
master[fld] = sources_scope[fld]; master[fld] = sources_scope[fld];
} }
@@ -1144,6 +1165,7 @@ function($compile, SchedulerInit, Rest, Wait, SetSchedulesInnerDialogSize, Sched
credential: sources_scope.credential, credential: sources_scope.credential,
overwrite: sources_scope.overwrite, overwrite: sources_scope.overwrite,
overwrite_vars: sources_scope.overwrite_vars, overwrite_vars: sources_scope.overwrite_vars,
source_script: sources_scope.source_script,
update_on_launch: sources_scope.update_on_launch, update_on_launch: sources_scope.update_on_launch,
update_cache_timeout: (sources_scope.update_cache_timeout || 0) 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(); 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 // for ec2, validate variable data
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.source_vars, true); 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) { if (!parseError) {
Rest.setUrl(sources_scope.source_url); Rest.setUrl(sources_scope.source_url);
Rest.put(data) 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.setUrl(url);
Rest.post(job_launch_data) Rest.post(job_launch_data)
.success(function(data) { .success(function(data) {
@@ -386,6 +388,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
function buildHtml(extra_vars){ function buildHtml(extra_vars){
html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); 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" + 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\" " + "<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\">" + "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; question.index = index;
requiredAsterisk = (question.required===true) ? "prepend-asterisk" : ""; 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+' ">'; 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'; html += '<label for="'+question.variable+'">'+question.question_name+'</label>\n';
if(!Empty(question.question_description)){ if(!Empty(question.question_description)){
@@ -458,7 +460,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(question.type === 'text' ){ if(question.type === 'text' ){
html+='<input type="text" id="'+question.variable+'" ng-model="'+question.variable+'" '+ html+='<input type="text" id="'+question.variable+'" ng-model="'+question.variable+'" '+
'name="'+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 && ' + '<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>'+ '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 api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
@@ -467,7 +469,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(question.type === "textarea"){ if(question.type === "textarea"){
scope[question.variable] = question.default || question.default_textarea; scope[question.variable] = question.default || question.default_textarea;
html+='<textarea id="'+question.variable+'" name="'+question.variable+'" ng-model="'+question.variable+'" '+ 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 && ' + '<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>'+ '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 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" > '; html+='<div class="survey_taker_input" > ';
for( j = 0; j<choices.length; j++){ for( j = 0; j<choices.length; j++){
checked = (!Empty(question.default) && question.default.indexOf(choices[j])!==-1) ? "checked" : ""; 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>' ; '<span>'+choices[j] +'</span><br>' ;
} }
html+= '<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' + 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'){ if(question.type === 'integer'){
min = (!Empty(question.min)) ? Number(question.min) : ""; min = (!Empty(question.min)) ? Number(question.min) : "";
max = (!Empty(question.max)) ? Number(question.max) : "" ; 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.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>'; '<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"){ if(question.type === "float"){
min = (!Empty(question.min)) ? question.min : ""; min = (!Empty(question.min)) ? question.min : "";
max = (!Empty(question.max)) ? question.max : "" ; max = (!Empty(question.max)) ? question.max : "" ;
defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ; 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.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+'.$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>'; html+='</div>';
if(question.index === scope.survey_questions.length-1){ if(question.index === scope.survey_questions.length-1){
@@ -739,6 +746,12 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
if(passwords.length>0){ if(passwords.length>0){
scope.$emit('PromptForPasswords', passwords, html, url); 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); else scope.$emit('StartPlaybookRun', url);
}) })
.error(function (data, status) { .error(function (data, status) {
@@ -758,8 +771,8 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi
launch_url = url;//data.related.start; launch_url = url;//data.related.start;
scope.passwords_needed_to_start = data.passwords_needed_to_start; scope.passwords_needed_to_start = data.passwords_needed_to_start;
scope.prompt_for_vars = data.ask_variables_on_launch; scope.prompt_for_vars = data.ask_variables_on_launch;
// scope.extra_vars = data.variables_needed_to_start;
scope.survey_enabled = data.survey_enabled; 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>'; 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)) ? '?' : '&'; connect = (/\/$/.test(new_url)) ? '?' : '&';
new_url += connect + 'page=' + page; new_url += connect + 'page=' + page;
new_url += (scope[iterator + 'SearchParams']) ? '&' + scope[iterator + 'SearchParams'] + 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'); Wait('start');
Refresh({ scope: scope, set: set, iterator: iterator, url: new_url }); Refresh({ scope: scope, set: set, iterator: iterator, url: new_url });
}; };

View File

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

View File

@@ -11,8 +11,8 @@
'use strict'; 'use strict';
angular.module('PortalJobsWidget', ['RestServices', 'Utilities']) angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
.factory('PortalJobsWidget', ['$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) { function ($rootScope, $compile, LoadSchedulesScope, LoadJobsScope, PortalJobsList, ScheduledJobsList, GetChoices, GetBasePath, PortalJobTemplateList) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
target = params.target, target = params.target,
@@ -70,6 +70,7 @@ angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
}); });
$(window).resize(_.debounce(function() { $(window).resize(_.debounce(function() {
resizePortalJobsWidget(); resizePortalJobsWidget();
}, 500)); }, 500));
@@ -150,8 +151,9 @@ angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
setPortalJobsHeight(); setPortalJobsHeight();
jobs_scope[PortalJobsList.iterator + '_page_size'] = max_rows; jobs_scope[PortalJobsList.iterator + '_page_size'] = max_rows;
jobs_scope.changePageSize(PortalJobsList.name, PortalJobsList.iterator, false); jobs_scope.changePageSize(PortalJobsList.name, PortalJobsList.iterator, false);
// scheduled_scope[ScheduledJobsList.iterator + '_page_size'] = max_rows; scope[PortalJobTemplateList.iterator + '_page_size'] = max_rows;
// scheduled_scope.changePageSize(ScheduledJobsList.name, ScheduledJobsList.iterator, false); 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 class="tab-pane" id="configure-schedules-tab">
<div id="configure-schedules-overlay"></div> <div id="configure-schedules-overlay"></div>
<div id="configure-schedules-list"></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 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="#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_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> <a href="#/logout" id="mobile_logout">Logout</a>
</nav> </nav>
@@ -398,7 +398,37 @@
<div id="license-modal-dialog" style="display: none;"></div> <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="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="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 # Description: Ansible Tower provides an easy-to-use UI and dashboard, role-based access control and more for your Ansible initiative
### END INIT INFO ### END INIT INFO
if [ -e /etc/debian_version ] # Default configured services
then if [ -e /etc/debian_version ]; then
SERVICES=(postgresql redis-server apache2 supervisor) TOWER_CONFIG="/etc/default/ansible-tower"
else else
SERVICES=(postgresql redis httpd supervisord) TOWER_CONFIG="/etc/sysconfig/ansible-tower"
fi fi
# Load default configuration
[ -e "${TOWER_CONFIG}" ] && . "${TOWER_CONFIG}"
service_action() { service_action() {
for svc in ${SERVICES[@]}; do for svc in ${TOWER_SERVICES}; do
service ${svc} $1 service ${svc} $1
this_return=$? this_return=$?
if [ $this_return -gt $worst_return ]; then if [ $this_return -gt $worst_return ]; then