mirror of
https://github.com/ansible/awx.git
synced 2026-02-24 22:46:01 -03:30
Merge branch 'rbac' of github.com:ansible/ansible-tower into rbac
This commit is contained in:
@@ -6,7 +6,7 @@ from collections import OrderedDict
|
|||||||
# Django
|
# Django
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text, smart_text
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
@@ -37,6 +37,25 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
if value is not None and value != '':
|
if value is not None and value != '':
|
||||||
field_info[attr] = force_text(value, strings_only=True)
|
field_info[attr] = force_text(value, strings_only=True)
|
||||||
|
|
||||||
|
# Update help text for common fields.
|
||||||
|
serializer = getattr(field, 'parent', None)
|
||||||
|
if serializer:
|
||||||
|
field_help_text = {
|
||||||
|
'id': 'Database ID for this {}.',
|
||||||
|
'name': 'Name of this {}.',
|
||||||
|
'description': 'Optional description of this {}.',
|
||||||
|
'type': 'Data type for this {}.',
|
||||||
|
'url': 'URL for this {}.',
|
||||||
|
'related': 'Data structure with URLs of related resources.',
|
||||||
|
'summary_fields': 'Data structure with name/description for related resources.',
|
||||||
|
'created': 'Timestamp when this {} was created.',
|
||||||
|
'modified': 'Timestamp when this {} was last modified.',
|
||||||
|
}
|
||||||
|
if field.field_name in field_help_text:
|
||||||
|
opts = serializer.Meta.model._meta.concrete_model._meta
|
||||||
|
verbose_name = smart_text(opts.verbose_name)
|
||||||
|
field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
|
||||||
|
|
||||||
# Indicate if a field has a default value.
|
# Indicate if a field has a default value.
|
||||||
# FIXME: Still isn't showing all default values?
|
# FIXME: Still isn't showing all default values?
|
||||||
try:
|
try:
|
||||||
@@ -77,7 +96,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
|
|
||||||
# Update type of fields returned...
|
# Update type of fields returned...
|
||||||
if field.field_name == 'type':
|
if field.field_name == 'type':
|
||||||
field_info['type'] = 'multiple choice'
|
field_info['type'] = 'choice'
|
||||||
elif field.field_name == 'url':
|
elif field.field_name == 'url':
|
||||||
field_info['type'] = 'string'
|
field_info['type'] = 'string'
|
||||||
elif field.field_name in ('related', 'summary_fields'):
|
elif field.field_name in ('related', 'summary_fields'):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import pagination
|
from rest_framework import pagination
|
||||||
from rest_framework.utils.urls import remove_query_param, replace_query_param
|
from rest_framework.utils.urls import replace_query_param
|
||||||
|
|
||||||
|
|
||||||
class Pagination(pagination.PageNumberPagination):
|
class Pagination(pagination.PageNumberPagination):
|
||||||
@@ -22,6 +22,4 @@ class Pagination(pagination.PageNumberPagination):
|
|||||||
return None
|
return None
|
||||||
url = self.request and self.request.get_full_path() or ''
|
url = self.request and self.request.get_full_path() or ''
|
||||||
page_number = self.page.previous_page_number()
|
page_number = self.page.previous_page_number()
|
||||||
if page_number == 1:
|
|
||||||
return remove_query_param(url, self.page_query_param)
|
|
||||||
return replace_query_param(url, self.page_query_param, page_number)
|
return replace_query_param(url, self.page_query_param, page_number)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from django.core.urlresolvers import reverse
|
|||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
# from django.utils.translation import ugettext_lazy as _
|
# from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.encoding import force_text, smart_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
@@ -351,7 +351,6 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
return obj.modified
|
return obj.modified
|
||||||
|
|
||||||
def build_standard_field(self, field_name, model_field):
|
def build_standard_field(self, field_name, model_field):
|
||||||
|
|
||||||
# DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits
|
# DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits
|
||||||
# when a Model's editable field is set to False. The short circuit skips choice rendering.
|
# when a Model's editable field is set to False. The short circuit skips choice rendering.
|
||||||
#
|
#
|
||||||
@@ -368,27 +367,6 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
if was_editable is False:
|
if was_editable is False:
|
||||||
field_kwargs['read_only'] = True
|
field_kwargs['read_only'] = True
|
||||||
|
|
||||||
# Update help text for common fields.
|
|
||||||
opts = self.Meta.model._meta.concrete_model._meta
|
|
||||||
if field_name == 'id':
|
|
||||||
field_kwargs.setdefault('help_text', 'Database ID for this %s.' % smart_text(opts.verbose_name))
|
|
||||||
elif field_name == 'name':
|
|
||||||
field_kwargs['help_text'] = 'Name of this %s.' % smart_text(opts.verbose_name)
|
|
||||||
elif field_name == 'description':
|
|
||||||
field_kwargs['help_text'] = 'Optional description of this %s.' % smart_text(opts.verbose_name)
|
|
||||||
elif field_name == 'type':
|
|
||||||
field_kwargs['help_text'] = 'Data type for this %s.' % smart_text(opts.verbose_name)
|
|
||||||
elif field_name == 'url':
|
|
||||||
field_kwargs['help_text'] = 'URL for this %s.' % smart_text(opts.verbose_name)
|
|
||||||
elif field_name == 'related':
|
|
||||||
field_kwargs['help_text'] = 'Data structure with URLs of related resources.'
|
|
||||||
elif field_name == 'summary_fields':
|
|
||||||
field_kwargs['help_text'] = 'Data structure with name/description for related resources.'
|
|
||||||
elif field_name == 'created':
|
|
||||||
field_kwargs['help_text'] = 'Timestamp when this %s was created.' % smart_text(opts.verbose_name)
|
|
||||||
elif field_name == 'modified':
|
|
||||||
field_kwargs['help_text'] = 'Timestamp when this %s was last modified.' % smart_text(opts.verbose_name)
|
|
||||||
|
|
||||||
# Pass model field default onto the serializer field if field is not read-only.
|
# Pass model field default onto the serializer field if field is not read-only.
|
||||||
if model_field.has_default() and not field_kwargs.get('read_only', False):
|
if model_field.has_default() and not field_kwargs.get('read_only', False):
|
||||||
field_kwargs['default'] = field_kwargs['initial'] = model_field.get_default()
|
field_kwargs['default'] = field_kwargs['initial'] = model_field.get_default()
|
||||||
@@ -414,6 +392,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
# Update the message used for the unique validator to use capitalized
|
# Update the message used for the unique validator to use capitalized
|
||||||
# verbose name; keeps unique message the same as with DRF 2.x.
|
# verbose name; keeps unique message the same as with DRF 2.x.
|
||||||
|
opts = self.Meta.model._meta.concrete_model._meta
|
||||||
for validator in field_kwargs.get('validators', []):
|
for validator in field_kwargs.get('validators', []):
|
||||||
if isinstance(validator, validators.UniqueValidator):
|
if isinstance(validator, validators.UniqueValidator):
|
||||||
unique_error_message = model_field.error_messages.get('unique', None)
|
unique_error_message = model_field.error_messages.get('unique', None)
|
||||||
@@ -1662,6 +1641,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
|||||||
d['can_copy'] = False
|
d['can_copy'] = False
|
||||||
d['can_edit'] = False
|
d['can_edit'] = False
|
||||||
d['recent_jobs'] = [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in obj.jobs.order_by('-created')[:10]]
|
d['recent_jobs'] = [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in obj.jobs.order_by('-created')[:10]]
|
||||||
|
d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@@ -1703,6 +1683,11 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
|||||||
res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,))
|
res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def get_summary_fields(self, obj):
|
||||||
|
d = super(JobSerializer, self).get_summary_fields(obj)
|
||||||
|
d['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
|
||||||
|
return d
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
# When creating a new job and a job template is specified, populate any
|
# When creating a new job and a job template is specified, populate any
|
||||||
# fields not provided in data from the job template.
|
# fields not provided in data from the job template.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
{% for fn, fm in serializer_fields.items %}{% spaceless %}
|
||||||
{% if not write_only or not fm.read_only %}
|
{% if not write_only or not fm.read_only %}
|
||||||
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{{ fm.default }}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %}
|
* `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{% if fm.type == "field" and not fm.default %}None{% else %}{{ fm.default }}{% endif %}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %}
|
||||||
- `{% if c.0 == "" %}""{% else %}{{ c.0 }}{% endif %}`{% if c.1 != c.0 %}: {{ c.1 }}{% endif %}{% if write_only and c.0 == fm.default %} (default){% endif %}{% endfor %}{% endif %}{% endif %}
|
- `{% if c.0 == "" %}""{% else %}{{ c.0 }}{% endif %}`{% if c.1 != c.0 %}: {{ c.1 }}{% endif %}{% if write_only and c.0 == fm.default %} (default){% endif %}{% endfor %}{% endif %}{% endif %}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class ApiV1RootView(APIView):
|
|||||||
data['roles'] = reverse('api:role_list')
|
data['roles'] = reverse('api:role_list')
|
||||||
data['notifiers'] = reverse('api:notifier_list')
|
data['notifiers'] = reverse('api:notifier_list')
|
||||||
data['notifications'] = reverse('api:notification_list')
|
data['notifications'] = reverse('api:notification_list')
|
||||||
|
data['labels'] = reverse('api:label_list')
|
||||||
data['unified_job_templates'] = reverse('api:unified_job_template_list')
|
data['unified_job_templates'] = reverse('api:unified_job_template_list')
|
||||||
data['unified_jobs'] = reverse('api:unified_job_list')
|
data['unified_jobs'] = reverse('api:unified_job_list')
|
||||||
data['activity_stream'] = reverse('api:activity_stream_list')
|
data['activity_stream'] = reverse('api:activity_stream_list')
|
||||||
|
|||||||
@@ -1054,12 +1054,16 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
|||||||
'last_job',
|
'last_job',
|
||||||
'current_job',
|
'current_job',
|
||||||
)
|
)
|
||||||
qs = qs.prefetch_related(
|
|
||||||
#'project',
|
# WISH - sure would be nice if the following worked, but it does not.
|
||||||
'inventory',
|
# In the future, as django and polymorphic libs are upgraded, try again.
|
||||||
'credential',
|
|
||||||
'cloud_credential',
|
#qs = qs.prefetch_related(
|
||||||
)
|
# 'project',
|
||||||
|
# 'inventory',
|
||||||
|
# 'credential',
|
||||||
|
# 'cloud_credential',
|
||||||
|
#)
|
||||||
|
|
||||||
return qs.all()
|
return qs.all()
|
||||||
|
|
||||||
@@ -1089,20 +1093,26 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
)
|
)
|
||||||
qs = qs.prefetch_related(
|
qs = qs.prefetch_related(
|
||||||
'unified_job_template',
|
'unified_job_template',
|
||||||
'project',
|
|
||||||
'inventory',
|
|
||||||
'credential',
|
|
||||||
'job_template',
|
|
||||||
'inventory_source',
|
|
||||||
'cloud_credential',
|
|
||||||
'project___credential',
|
|
||||||
'inventory_source___credential',
|
|
||||||
'inventory_source___inventory',
|
|
||||||
'job_template__inventory',
|
|
||||||
'job_template__project',
|
|
||||||
'job_template__credential',
|
|
||||||
'job_template__cloud_credential',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# WISH - sure would be nice if the following worked, but it does not.
|
||||||
|
# In the future, as django and polymorphic libs are upgraded, try again.
|
||||||
|
|
||||||
|
#qs = qs.prefetch_related(
|
||||||
|
# 'project',
|
||||||
|
# 'inventory',
|
||||||
|
# 'credential',
|
||||||
|
# 'job_template',
|
||||||
|
# 'inventory_source',
|
||||||
|
# 'cloud_credential',
|
||||||
|
# 'project___credential',
|
||||||
|
# 'inventory_source___credential',
|
||||||
|
# 'inventory_source___inventory',
|
||||||
|
# 'job_template__inventory',
|
||||||
|
# 'job_template__project',
|
||||||
|
# 'job_template__credential',
|
||||||
|
# 'job_template__cloud_credential',
|
||||||
|
#)
|
||||||
return qs.all()
|
return qs.all()
|
||||||
|
|
||||||
class ScheduleAccess(BaseAccess):
|
class ScheduleAccess(BaseAccess):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'openstack_v3')
|
CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax', 'vmware', 'openstack')
|
||||||
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom',)
|
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom',)
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ class Migration(migrations.Migration):
|
|||||||
name='AdHocCommand',
|
name='AdHocCommand',
|
||||||
fields=[
|
fields=[
|
||||||
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
('unifiedjob_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='main.UnifiedJob')),
|
||||||
('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check'), (b'scan', 'Scan')])),
|
('job_type', models.CharField(default=b'run', max_length=64, choices=[(b'run', 'Run'), (b'check', 'Check')])),
|
||||||
('limit', models.CharField(default=b'', max_length=1024, blank=True)),
|
('limit', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||||
('module_name', models.CharField(default=b'', max_length=1024, blank=True)),
|
('module_name', models.CharField(default=b'', max_length=1024, blank=True)),
|
||||||
('module_args', models.TextField(default=b'', blank=True)),
|
('module_args', models.TextField(default=b'', blank=True)),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AdHocCommand(UnifiedJob):
|
|||||||
|
|
||||||
job_type = models.CharField(
|
job_type = models.CharField(
|
||||||
max_length=64,
|
max_length=64,
|
||||||
choices=JOB_TYPE_CHOICES,
|
choices=AD_HOC_JOB_TYPE_CHOICES,
|
||||||
default='run',
|
default='run',
|
||||||
)
|
)
|
||||||
inventory = models.ForeignKey(
|
inventory = models.ForeignKey(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ __all__ = ['VarsDictProperty', 'BaseModel', 'CreatedModifiedModel',
|
|||||||
'PERM_INVENTORY_ADMIN', 'PERM_INVENTORY_READ',
|
'PERM_INVENTORY_ADMIN', 'PERM_INVENTORY_READ',
|
||||||
'PERM_INVENTORY_WRITE', 'PERM_INVENTORY_DEPLOY', 'PERM_INVENTORY_SCAN',
|
'PERM_INVENTORY_WRITE', 'PERM_INVENTORY_DEPLOY', 'PERM_INVENTORY_SCAN',
|
||||||
'PERM_INVENTORY_CHECK', 'PERM_JOBTEMPLATE_CREATE', 'JOB_TYPE_CHOICES',
|
'PERM_INVENTORY_CHECK', 'PERM_JOBTEMPLATE_CREATE', 'JOB_TYPE_CHOICES',
|
||||||
'PERMISSION_TYPE_CHOICES', 'CLOUD_INVENTORY_SOURCES',
|
'AD_HOC_JOB_TYPE_CHOICES', 'PERMISSION_TYPE_CHOICES', 'CLOUD_INVENTORY_SOURCES',
|
||||||
'VERBOSITY_CHOICES']
|
'VERBOSITY_CHOICES']
|
||||||
|
|
||||||
PERM_INVENTORY_ADMIN = 'admin'
|
PERM_INVENTORY_ADMIN = 'admin'
|
||||||
@@ -46,6 +46,11 @@ JOB_TYPE_CHOICES = [
|
|||||||
(PERM_INVENTORY_SCAN, _('Scan')),
|
(PERM_INVENTORY_SCAN, _('Scan')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AD_HOC_JOB_TYPE_CHOICES = [
|
||||||
|
(PERM_INVENTORY_DEPLOY, _('Run')),
|
||||||
|
(PERM_INVENTORY_CHECK, _('Check')),
|
||||||
|
]
|
||||||
|
|
||||||
PERMISSION_TYPE_CHOICES = [
|
PERMISSION_TYPE_CHOICES = [
|
||||||
(PERM_INVENTORY_READ, _('Read Inventory')),
|
(PERM_INVENTORY_READ, _('Read Inventory')),
|
||||||
(PERM_INVENTORY_WRITE, _('Edit Inventory')),
|
(PERM_INVENTORY_WRITE, _('Edit Inventory')),
|
||||||
@@ -56,7 +61,7 @@ PERMISSION_TYPE_CHOICES = [
|
|||||||
(PERM_JOBTEMPLATE_CREATE, _('Create a Job Template')),
|
(PERM_JOBTEMPLATE_CREATE, _('Create a Job Template')),
|
||||||
]
|
]
|
||||||
|
|
||||||
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'openstack', 'openstack_v3', 'custom']
|
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'openstack', 'custom']
|
||||||
|
|
||||||
VERBOSITY_CHOICES = [
|
VERBOSITY_CHOICES = [
|
||||||
(0, '0 (Normal)'),
|
(0, '0 (Normal)'),
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
('gce', _('Google Compute Engine')),
|
('gce', _('Google Compute Engine')),
|
||||||
('azure', _('Microsoft Azure')),
|
('azure', _('Microsoft Azure')),
|
||||||
('openstack', _('OpenStack')),
|
('openstack', _('OpenStack')),
|
||||||
('openstack_v3', _('OpenStack V3')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
BECOME_METHOD_CHOICES = [
|
BECOME_METHOD_CHOICES = [
|
||||||
@@ -237,18 +236,12 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
host = self.host or ''
|
host = self.host or ''
|
||||||
if not host and self.kind == 'vmware':
|
if not host and self.kind == 'vmware':
|
||||||
raise ValidationError('Host required for VMware credential.')
|
raise ValidationError('Host required for VMware credential.')
|
||||||
if not host and self.kind in ('openstack', 'openstack_v3'):
|
if not host and self.kind == 'openstack':
|
||||||
raise ValidationError('Host required for OpenStack credential.')
|
raise ValidationError('Host required for OpenStack credential.')
|
||||||
return host
|
return host
|
||||||
|
|
||||||
def clean_domain(self):
|
def clean_domain(self):
|
||||||
"""For case of Keystone v3 identity service that requires a
|
return self.domain or ''
|
||||||
`domain`, that a domain is provided.
|
|
||||||
"""
|
|
||||||
domain = self.domain or ''
|
|
||||||
if not domain and self.kind == 'openstack_v3':
|
|
||||||
raise ValidationError('Domain required for OpenStack with Keystone v3.')
|
|
||||||
return domain
|
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
username = self.username or ''
|
username = self.username or ''
|
||||||
@@ -259,7 +252,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
'credential.')
|
'credential.')
|
||||||
if not username and self.kind == 'vmware':
|
if not username and self.kind == 'vmware':
|
||||||
raise ValidationError('Username required for VMware credential.')
|
raise ValidationError('Username required for VMware credential.')
|
||||||
if not username and self.kind in ('openstack', 'openstack_v3'):
|
if not username and self.kind == 'openstack':
|
||||||
raise ValidationError('Username required for OpenStack credential.')
|
raise ValidationError('Username required for OpenStack credential.')
|
||||||
return username
|
return username
|
||||||
|
|
||||||
@@ -271,13 +264,13 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
raise ValidationError('API key required for Rackspace credential.')
|
raise ValidationError('API key required for Rackspace credential.')
|
||||||
if not password and self.kind == 'vmware':
|
if not password and self.kind == 'vmware':
|
||||||
raise ValidationError('Password required for VMware credential.')
|
raise ValidationError('Password required for VMware credential.')
|
||||||
if not password and self.kind in ('openstack', 'openstack_v3'):
|
if not password and self.kind == 'openstack':
|
||||||
raise ValidationError('Password or API key required for OpenStack credential.')
|
raise ValidationError('Password or API key required for OpenStack credential.')
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def clean_project(self):
|
def clean_project(self):
|
||||||
project = self.project or ''
|
project = self.project or ''
|
||||||
if self.kind in ('openstack', 'openstack_v3') and not project:
|
if self.kind == 'openstack' and not project:
|
||||||
raise ValidationError('Project name required for OpenStack credential.')
|
raise ValidationError('Project name required for OpenStack credential.')
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
|||||||
@@ -733,7 +733,6 @@ class InventorySourceOptions(BaseModel):
|
|||||||
('azure', _('Microsoft Azure')),
|
('azure', _('Microsoft Azure')),
|
||||||
('vmware', _('VMware vCenter')),
|
('vmware', _('VMware vCenter')),
|
||||||
('openstack', _('OpenStack')),
|
('openstack', _('OpenStack')),
|
||||||
('openstack_v3', _('OpenStack V3')),
|
|
||||||
('custom', _('Custom Script')),
|
('custom', _('Custom Script')),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -962,11 +961,6 @@ class InventorySourceOptions(BaseModel):
|
|||||||
"""I don't think openstack has regions"""
|
"""I don't think openstack has regions"""
|
||||||
return [('all', 'All')]
|
return [('all', 'All')]
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_openstack_v3_region_choices(self):
|
|
||||||
"""Defer to the behavior of openstack"""
|
|
||||||
return self.get_openstack_region_choices()
|
|
||||||
|
|
||||||
def clean_credential(self):
|
def clean_credential(self):
|
||||||
if not self.source:
|
if not self.source:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -378,6 +378,10 @@ class BaseTask(Task):
|
|||||||
if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported:
|
if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported:
|
||||||
raise RuntimeError(OPENSSH_KEY_ERROR)
|
raise RuntimeError(OPENSSH_KEY_ERROR)
|
||||||
for name, data in private_data.iteritems():
|
for name, data in private_data.iteritems():
|
||||||
|
# OpenSSH formatted keys must have a trailing newline to be
|
||||||
|
# accepted by ssh-add.
|
||||||
|
if 'OPENSSH PRIVATE KEY' in data and not data.endswith('\n'):
|
||||||
|
data += '\n'
|
||||||
# For credentials used with ssh-add, write to a named pipe which
|
# For credentials used with ssh-add, write to a named pipe which
|
||||||
# will be read then closed, instead of leaving the SSH key on disk.
|
# will be read then closed, instead of leaving the SSH key on disk.
|
||||||
if name in ('credential', 'scm_credential', 'ad_hoc_credential') and not ssh_too_old:
|
if name in ('credential', 'scm_credential', 'ad_hoc_credential') and not ssh_too_old:
|
||||||
@@ -695,7 +699,7 @@ class RunJob(BaseTask):
|
|||||||
if credential.ssh_key_data not in (None, ''):
|
if credential.ssh_key_data not in (None, ''):
|
||||||
private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or ''
|
private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or ''
|
||||||
|
|
||||||
if job.cloud_credential and job.cloud_credential.kind in ('openstack', 'openstack_v3'):
|
if job.cloud_credential and job.cloud_credential.kind == 'openstack':
|
||||||
credential = job.cloud_credential
|
credential = job.cloud_credential
|
||||||
openstack_auth = dict(auth_url=credential.host,
|
openstack_auth = dict(auth_url=credential.host,
|
||||||
username=credential.username,
|
username=credential.username,
|
||||||
@@ -787,7 +791,7 @@ class RunJob(BaseTask):
|
|||||||
env['VMWARE_USER'] = cloud_cred.username
|
env['VMWARE_USER'] = cloud_cred.username
|
||||||
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
|
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
|
||||||
env['VMWARE_HOST'] = cloud_cred.host
|
env['VMWARE_HOST'] = cloud_cred.host
|
||||||
elif cloud_cred and cloud_cred.kind in ('openstack', 'openstack_v3'):
|
elif cloud_cred and cloud_cred.kind == 'openstack':
|
||||||
env['OS_CLIENT_CONFIG_FILE'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
|
env['OS_CLIENT_CONFIG_FILE'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
|
||||||
|
|
||||||
# Set environment variables related to scan jobs
|
# Set environment variables related to scan jobs
|
||||||
@@ -1136,7 +1140,7 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
credential = inventory_update.credential
|
credential = inventory_update.credential
|
||||||
return dict(cloud_credential=decrypt_field(credential, 'ssh_key_data'))
|
return dict(cloud_credential=decrypt_field(credential, 'ssh_key_data'))
|
||||||
|
|
||||||
if inventory_update.source in ('openstack', 'openstack_v3'):
|
if inventory_update.source == 'openstack':
|
||||||
credential = inventory_update.credential
|
credential = inventory_update.credential
|
||||||
openstack_auth = dict(auth_url=credential.host,
|
openstack_auth = dict(auth_url=credential.host,
|
||||||
username=credential.username,
|
username=credential.username,
|
||||||
@@ -1291,7 +1295,7 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
env['GCE_PROJECT'] = passwords.get('source_project', '')
|
env['GCE_PROJECT'] = passwords.get('source_project', '')
|
||||||
env['GCE_PEM_FILE_PATH'] = cloud_credential
|
env['GCE_PEM_FILE_PATH'] = cloud_credential
|
||||||
env['GCE_ZONE'] = inventory_update.source_regions
|
env['GCE_ZONE'] = inventory_update.source_regions
|
||||||
elif inventory_update.source in ('openstack', 'openstack_v3'):
|
elif inventory_update.source == 'openstack':
|
||||||
env['OS_CLIENT_CONFIG_FILE'] = cloud_credential
|
env['OS_CLIENT_CONFIG_FILE'] = cloud_credential
|
||||||
elif inventory_update.source == 'file':
|
elif inventory_update.source == 'file':
|
||||||
# FIXME: Parse source_env to dict, update env.
|
# FIXME: Parse source_env to dict, update env.
|
||||||
@@ -1334,11 +1338,6 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
# to a shorter variable. :)
|
# to a shorter variable. :)
|
||||||
src = inventory_update.source
|
src = inventory_update.source
|
||||||
|
|
||||||
# OpenStack V3 has everything in common with OpenStack aside
|
|
||||||
# from one extra parameter, so share these resources between them.
|
|
||||||
if src == 'openstack_v3':
|
|
||||||
src = 'openstack'
|
|
||||||
|
|
||||||
# Get the path to the inventory plugin, and append it to our
|
# Get the path to the inventory plugin, and append it to our
|
||||||
# arguments.
|
# arguments.
|
||||||
plugin_path = self.get_path_to('..', 'plugins', 'inventory',
|
plugin_path = self.get_path_to('..', 'plugins', 'inventory',
|
||||||
|
|||||||
@@ -84,8 +84,7 @@ HPUhg3adAmIJ9z9u/VmTErbVklcKWlyZuTUkxeQ/BJmSIRUQAAAIEA3oKAzdDURjy8zxLX
|
|||||||
gBLCPdi8AxCiqQJBCsGxXCgKtZewset1XJHIN9ryfb4QSZFkSOlm/LgdeGtS8Or0GNPRYd
|
gBLCPdi8AxCiqQJBCsGxXCgKtZewset1XJHIN9ryfb4QSZFkSOlm/LgdeGtS8Or0GNPRYd
|
||||||
hgnUCF0LkEsDQ7HzPZYujLrAwjumvGQH6ORp5vRh0tQb93o4e1/A2vpdSKeH7gCe/jfUSY
|
hgnUCF0LkEsDQ7HzPZYujLrAwjumvGQH6ORp5vRh0tQb93o4e1/A2vpdSKeH7gCe/jfUSY
|
||||||
h7dFGNoAI4cF7/0AAAAUcm9vdEBwaWxsb3cuaXhtbS5uZXQBAgMEBQYH
|
h7dFGNoAI4cF7/0AAAAUcm9vdEBwaWxsb3cuaXhtbS5uZXQBAgMEBQYH
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
-----END OPENSSH PRIVATE KEY-----'''
|
||||||
'''
|
|
||||||
|
|
||||||
TEST_OPENSSH_KEY_DATA_LOCKED = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
TEST_OPENSSH_KEY_DATA_LOCKED = '''-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABALaWMfjc
|
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABALaWMfjc
|
||||||
@@ -114,8 +113,7 @@ C6Oxl1Wsp3gPkK2yiuy8qcrvoEoJ25TeEhUGEAPWx2OuQJO/Lpq9aF/JJoqGwnBaXdCsi+
|
|||||||
5ig+ZMq5GKQtyydzyXImjlNEUH1w2prRDiGVEufANA5LSLCtqOLgDzXS62WUBjJBrQJVAM
|
5ig+ZMq5GKQtyydzyXImjlNEUH1w2prRDiGVEufANA5LSLCtqOLgDzXS62WUBjJBrQJVAM
|
||||||
YpWz1tiZQoyv1RT3Y0O0Vwe2Z5AK3fVM0I5jWdiLrIErtcR4ULa6T56QtA52DufhKzINTR
|
YpWz1tiZQoyv1RT3Y0O0Vwe2Z5AK3fVM0I5jWdiLrIErtcR4ULa6T56QtA52DufhKzINTR
|
||||||
Vg9TtUBqfKIpRQikPSjm7vpY/Xnbc=
|
Vg9TtUBqfKIpRQikPSjm7vpY/Xnbc=
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
-----END OPENSSH PRIVATE KEY-----'''
|
||||||
'''
|
|
||||||
|
|
||||||
TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE-----
|
TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE-----
|
||||||
MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93
|
MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ def test_two_organizations(resourced_organization, organizations, user, get):
|
|||||||
'teams': 0
|
'teams': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="resolution planned for after RBAC merge")
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.skipif("True") # XXX: This needs to be implemented
|
@pytest.mark.skipif("True") # XXX: This needs to be implemented
|
||||||
def test_JT_associated_with_project(organizations, project, user, get):
|
def test_JT_associated_with_project(organizations, project, user, get):
|
||||||
|
|||||||
@@ -1970,7 +1970,7 @@ class InventoryUpdatesTest(BaseTransactionTest):
|
|||||||
self.check_inventory_source(inventory_source)
|
self.check_inventory_source(inventory_source)
|
||||||
self.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
|
self.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
|
||||||
|
|
||||||
def test_update_from_openstack_v3(self):
|
def test_update_from_openstack_with_domain(self):
|
||||||
# Check that update works with Keystone v3 identity service
|
# Check that update works with Keystone v3 identity service
|
||||||
api_url = getattr(settings, 'TEST_OPENSTACK_HOST_V3', '')
|
api_url = getattr(settings, 'TEST_OPENSTACK_HOST_V3', '')
|
||||||
api_user = getattr(settings, 'TEST_OPENSTACK_USER', '')
|
api_user = getattr(settings, 'TEST_OPENSTACK_USER', '')
|
||||||
@@ -1978,15 +1978,15 @@ class InventoryUpdatesTest(BaseTransactionTest):
|
|||||||
api_project = getattr(settings, 'TEST_OPENSTACK_PROJECT', '')
|
api_project = getattr(settings, 'TEST_OPENSTACK_PROJECT', '')
|
||||||
api_domain = getattr(settings, 'TEST_OPENSTACK_DOMAIN', '')
|
api_domain = getattr(settings, 'TEST_OPENSTACK_DOMAIN', '')
|
||||||
if not all([api_url, api_user, api_password, api_project, api_domain]):
|
if not all([api_url, api_user, api_password, api_project, api_domain]):
|
||||||
self.skipTest("No test openstack v3 credentials defined")
|
self.skipTest("No test openstack credentials defined with a domain")
|
||||||
self.create_test_license_file()
|
self.create_test_license_file()
|
||||||
credential = Credential.objects.create(kind='openstack_v3',
|
credential = Credential.objects.create(kind='openstack',
|
||||||
host=api_url,
|
host=api_url,
|
||||||
username=api_user,
|
username=api_user,
|
||||||
password=api_password,
|
password=api_password,
|
||||||
project=api_project,
|
project=api_project,
|
||||||
domain=api_domain)
|
domain=api_domain)
|
||||||
inventory_source = self.update_inventory_source(self.group, source='openstack_v3', credential=credential)
|
inventory_source = self.update_inventory_source(self.group, source='openstack', credential=credential)
|
||||||
self.check_inventory_source(inventory_source)
|
self.check_inventory_source(inventory_source)
|
||||||
self.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
|
self.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
|
||||||
|
|
||||||
@@ -2034,27 +2034,3 @@ class InventoryCredentialTest(BaseTest):
|
|||||||
self.assertIn('password', response)
|
self.assertIn('password', response)
|
||||||
self.assertIn('host', response)
|
self.assertIn('host', response)
|
||||||
self.assertIn('project', response)
|
self.assertIn('project', response)
|
||||||
|
|
||||||
def test_openstack_v3_create_ok(self):
|
|
||||||
data = {
|
|
||||||
'kind': 'openstack_v3',
|
|
||||||
'name': 'Best credential ever',
|
|
||||||
'username': 'some_user',
|
|
||||||
'password': 'some_password',
|
|
||||||
'project': 'some_project',
|
|
||||||
'host': 'some_host',
|
|
||||||
'domain': 'some_domain',
|
|
||||||
}
|
|
||||||
self.post(self.url, data=data, expect=201, auth=self.get_super_credentials())
|
|
||||||
|
|
||||||
def test_openstack_v3_create_fail_required_fields(self):
|
|
||||||
data = {
|
|
||||||
'kind': 'openstack_v3',
|
|
||||||
'name': 'Best credential ever',
|
|
||||||
}
|
|
||||||
response = self.post(self.url, data=data, expect=400, auth=self.get_super_credentials())
|
|
||||||
self.assertIn('username', response)
|
|
||||||
self.assertIn('password', response)
|
|
||||||
self.assertIn('host', response)
|
|
||||||
self.assertIn('project', response)
|
|
||||||
self.assertIn('domain', response)
|
|
||||||
|
|||||||
@@ -43,11 +43,13 @@ $(function() {
|
|||||||
$('.description').addClass('prettyprint').parent().css('float', 'none');
|
$('.description').addClass('prettyprint').parent().css('float', 'none');
|
||||||
$('.hidden a.hide-description').prependTo('.description');
|
$('.hidden a.hide-description').prependTo('.description');
|
||||||
$('a.hide-description').click(function() {
|
$('a.hide-description').click(function() {
|
||||||
|
$(this).tooltip('hide');
|
||||||
$('.description').slideUp('fast');
|
$('.description').slideUp('fast');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
$('.hidden a.toggle-description').appendTo('.page-header h1');
|
$('.hidden a.toggle-description').appendTo('.page-header h1');
|
||||||
$('a.toggle-description').click(function() {
|
$('a.toggle-description').click(function() {
|
||||||
|
$(this).tooltip('hide');
|
||||||
$('.description').slideToggle('fast');
|
$('.description').slideToggle('fast');
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -68,6 +70,7 @@ $(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('a.resize').click(function() {
|
$('a.resize').click(function() {
|
||||||
|
$(this).tooltip('hide');
|
||||||
if ($(this).find('span.glyphicon-resize-full').size()) {
|
if ($(this).find('span.glyphicon-resize-full').size()) {
|
||||||
$(this).find('span.glyphicon').addClass('glyphicon-resize-small').removeClass('glyphicon-resize-full');
|
$(this).find('span.glyphicon').addClass('glyphicon-resize-small').removeClass('glyphicon-resize-full');
|
||||||
$('.container').addClass('container-fluid').removeClass('container');
|
$('.container').addClass('container-fluid').removeClass('container');
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Cr
|
|||||||
import {JobsListController} from './controllers/Jobs';
|
import {JobsListController} from './controllers/Jobs';
|
||||||
import {PortalController} from './controllers/Portal';
|
import {PortalController} from './controllers/Portal';
|
||||||
import systemTracking from './system-tracking/main';
|
import systemTracking from './system-tracking/main';
|
||||||
|
import inventories from './inventories/main';
|
||||||
import inventoryScripts from './inventory-scripts/main';
|
import inventoryScripts from './inventory-scripts/main';
|
||||||
import organizations from './organizations/main';
|
import organizations from './organizations/main';
|
||||||
import permissions from './permissions/main';
|
import permissions from './permissions/main';
|
||||||
@@ -55,7 +56,7 @@ import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
|
|||||||
import OrganizationsList from './organizations/list/organizations-list.controller';
|
import OrganizationsList from './organizations/list/organizations-list.controller';
|
||||||
import OrganizationsAdd from './organizations/add/organizations-add.controller';
|
import OrganizationsAdd from './organizations/add/organizations-add.controller';
|
||||||
import OrganizationsEdit from './organizations/edit/organizations-edit.controller';
|
import OrganizationsEdit from './organizations/edit/organizations-edit.controller';
|
||||||
import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories';
|
import {InventoriesAdd, InventoriesEdit, InventoriesList, InventoriesManage} from './inventories/main';
|
||||||
import {AdminsList} from './controllers/Admins';
|
import {AdminsList} from './controllers/Admins';
|
||||||
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
||||||
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
||||||
@@ -88,6 +89,7 @@ var tower = angular.module('Tower', [
|
|||||||
RestServices.name,
|
RestServices.name,
|
||||||
browserData.name,
|
browserData.name,
|
||||||
systemTracking.name,
|
systemTracking.name,
|
||||||
|
inventories.name,
|
||||||
inventoryScripts.name,
|
inventoryScripts.name,
|
||||||
organizations.name,
|
organizations.name,
|
||||||
permissions.name,
|
permissions.name,
|
||||||
@@ -182,7 +184,6 @@ var tower = angular.module('Tower', [
|
|||||||
'LogViewerStatusDefinition',
|
'LogViewerStatusDefinition',
|
||||||
'StandardOutHelper',
|
'StandardOutHelper',
|
||||||
'LogViewerOptionsDefinition',
|
'LogViewerOptionsDefinition',
|
||||||
'EventViewerHelper',
|
|
||||||
'JobDetailHelper',
|
'JobDetailHelper',
|
||||||
'SocketIO',
|
'SocketIO',
|
||||||
'lrInfiniteScroll',
|
'lrInfiniteScroll',
|
||||||
@@ -213,6 +214,8 @@ var tower = angular.module('Tower', [
|
|||||||
templateUrl: urlPrefix + 'partials/breadcrumb.html'
|
templateUrl: urlPrefix + 'partials/breadcrumb.html'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// route to the details pane of /job/:id/host-event/:eventId if no other child specified
|
||||||
|
$urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details')
|
||||||
// $urlRouterProvider.otherwise("/home");
|
// $urlRouterProvider.otherwise("/home");
|
||||||
$urlRouterProvider.otherwise(function($injector){
|
$urlRouterProvider.otherwise(function($injector){
|
||||||
var $state = $injector.get("$state");
|
var $state = $injector.get("$state");
|
||||||
@@ -370,69 +373,6 @@ var tower = angular.module('Tower', [
|
|||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('inventories', {
|
|
||||||
url: '/inventories',
|
|
||||||
templateUrl: urlPrefix + 'partials/inventories.html',
|
|
||||||
controller: InventoriesList,
|
|
||||||
data: {
|
|
||||||
activityStream: true,
|
|
||||||
activityStreamTarget: 'inventory'
|
|
||||||
},
|
|
||||||
ncyBreadcrumb: {
|
|
||||||
label: "INVENTORIES"
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('inventories.add', {
|
|
||||||
url: '/add',
|
|
||||||
templateUrl: urlPrefix + 'partials/inventories.html',
|
|
||||||
controller: InventoriesAdd,
|
|
||||||
ncyBreadcrumb: {
|
|
||||||
parent: "inventories",
|
|
||||||
label: "CREATE INVENTORY"
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('inventories.edit', {
|
|
||||||
url: '/:inventory_id',
|
|
||||||
templateUrl: urlPrefix + 'partials/inventories.html',
|
|
||||||
controller: InventoriesEdit,
|
|
||||||
data: {
|
|
||||||
activityStreamId: 'inventory_id'
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('inventoryManage', {
|
|
||||||
url: '/inventories/:inventory_id/manage?groups',
|
|
||||||
templateUrl: urlPrefix + 'partials/inventory-manage.html',
|
|
||||||
controller: InventoriesManage,
|
|
||||||
data: {
|
|
||||||
activityStream: true,
|
|
||||||
activityStreamTarget: 'inventory',
|
|
||||||
activityStreamId: 'inventory_id'
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('organizationAdmins', {
|
state('organizationAdmins', {
|
||||||
url: '/organizations/:organization_id/admins',
|
url: '/organizations/:organization_id/admins',
|
||||||
templateUrl: urlPrefix + 'partials/organizations.html',
|
templateUrl: urlPrefix + 'partials/organizations.html',
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -169,7 +169,7 @@ export default
|
|||||||
"host": {
|
"host": {
|
||||||
labelBind: 'hostLabel',
|
labelBind: 'hostLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
ngShow: "kind.value == 'vmware' || kind.value == 'openstack' || kind.value === 'openstack_v3'",
|
ngShow: "kind.value == 'vmware' || kind.value == 'openstack'",
|
||||||
awPopOverWatch: "hostPopOver",
|
awPopOverWatch: "hostPopOver",
|
||||||
awPopOver: "set in helpers/credentials",
|
awPopOver: "set in helpers/credentials",
|
||||||
dataTitle: 'Host',
|
dataTitle: 'Host',
|
||||||
@@ -243,7 +243,7 @@ export default
|
|||||||
"password": {
|
"password": {
|
||||||
labelBind: 'passwordLabel',
|
labelBind: 'passwordLabel',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'scm' || kind.value == 'vmware' || kind.value == 'openstack' || kind.value == 'openstack_v3'",
|
ngShow: "kind.value == 'scm' || kind.value == 'vmware' || kind.value == 'openstack'",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
ask: false,
|
ask: false,
|
||||||
@@ -338,10 +338,10 @@ export default
|
|||||||
"project": {
|
"project": {
|
||||||
labelBind: 'projectLabel',
|
labelBind: 'projectLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
ngShow: "kind.value == 'gce' || kind.value == 'openstack' || kind.value == 'openstack_v3'",
|
ngShow: "kind.value == 'gce' || kind.value == 'openstack'",
|
||||||
awPopOverWatch: "projectPopOver",
|
awPopOverWatch: "projectPopOver",
|
||||||
awPopOver: "set in helpers/credentials",
|
awPopOver: "set in helpers/credentials",
|
||||||
dataTitle: 'Project ID',
|
dataTitle: 'Project Name',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body",
|
dataContainer: "body",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
@@ -355,18 +355,17 @@ export default
|
|||||||
"domain": {
|
"domain": {
|
||||||
labelBind: 'domainLabel',
|
labelBind: 'domainLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
ngShow: "kind.value == 'openstack_v3'",
|
ngShow: "kind.value == 'openstack'",
|
||||||
awPopOverWatch: "domainPopOver",
|
awPopOver: "<p>OpenStack domains define administrative " +
|
||||||
awPopOver: "set in helpers/credentials",
|
"boundaries. It is only needed for Keystone v3 authentication URLs. " +
|
||||||
|
"Common scenarios include:<ul><li><b>v2 URLs</b> - leave blank</li>" +
|
||||||
|
"<li><b>v3 default</b> - set to 'default'</br></li>" +
|
||||||
|
"<li><b>v3 multi-domain</b> - your domain name</p></li></ul></p>",
|
||||||
dataTitle: 'Domain Name',
|
dataTitle: 'Domain Name',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body",
|
dataContainer: "body",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
awRequiredWhen: {
|
|
||||||
variable: 'domain_required',
|
|
||||||
init: false
|
|
||||||
},
|
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
"vault_password": {
|
"vault_password": {
|
||||||
|
|||||||
@@ -169,8 +169,7 @@ export default
|
|||||||
label: 'Source Variables', //"{{vars_label}}" ,
|
label: 'Source Variables', //"{{vars_label}}" ,
|
||||||
|
|
||||||
ngShow: "source && (source.value == 'vmware' || " +
|
ngShow: "source && (source.value == 'vmware' || " +
|
||||||
"source.value == 'openstack' || " +
|
"source.value == 'openstack')",
|
||||||
"source.value == 'openstack_v3')",
|
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
class: 'Form-textAreaLabel',
|
class: 'Form-textAreaLabel',
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import './lists';
|
|||||||
|
|
||||||
import Children from "./helpers/Children";
|
import Children from "./helpers/Children";
|
||||||
import Credentials from "./helpers/Credentials";
|
import Credentials from "./helpers/Credentials";
|
||||||
import EventViewer from "./helpers/EventViewer";
|
|
||||||
import Events from "./helpers/Events";
|
import Events from "./helpers/Events";
|
||||||
import Groups from "./helpers/Groups";
|
import Groups from "./helpers/Groups";
|
||||||
import Hosts from "./helpers/Hosts";
|
import Hosts from "./helpers/Hosts";
|
||||||
@@ -42,7 +41,6 @@ import ActivityStreamHelper from "./helpers/ActivityStream";
|
|||||||
export
|
export
|
||||||
{ Children,
|
{ Children,
|
||||||
Credentials,
|
Credentials,
|
||||||
EventViewer,
|
|
||||||
Events,
|
Events,
|
||||||
Groups,
|
Groups,
|
||||||
Hosts,
|
Hosts,
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
scope.project_required = false;
|
scope.project_required = false;
|
||||||
scope.passwordLabel = 'Password (API Key)';
|
scope.passwordLabel = 'Password (API Key)';
|
||||||
scope.projectPopOver = "<p>The project value</p>";
|
scope.projectPopOver = "<p>The project value</p>";
|
||||||
scope.domainPopOver = "<p>The domain name</p>";
|
|
||||||
scope.hostPopOver = "<p>The host value</p>";
|
scope.hostPopOver = "<p>The host value</p>";
|
||||||
|
|
||||||
if (!Empty(scope.kind)) {
|
if (!Empty(scope.kind)) {
|
||||||
@@ -126,32 +125,17 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
break;
|
break;
|
||||||
case 'openstack':
|
case 'openstack':
|
||||||
scope.hostLabel = "Host (Authentication URL)";
|
scope.hostLabel = "Host (Authentication URL)";
|
||||||
scope.projectLabel = "Project (Tenet Name/ID)";
|
scope.projectLabel = "Project (Tenant Name)";
|
||||||
scope.password_required = true;
|
|
||||||
scope.project_required = true;
|
|
||||||
scope.host_required = true;
|
|
||||||
scope.username_required = true;
|
|
||||||
scope.projectPopOver = "<p>This is the tenant name " +
|
|
||||||
"or tenant id. This value is usually the same " +
|
|
||||||
" as the username.</p>";
|
|
||||||
scope.hostPopOver = "<p>The host to authenticate with." +
|
|
||||||
"<br />For example, https://openstack.business.com/v2.0/";
|
|
||||||
case 'openstack_v3':
|
|
||||||
scope.hostLabel = "Host (Authentication URL)";
|
|
||||||
scope.projectLabel = "Project (Tenet Name/ID)";
|
|
||||||
scope.domainLabel = "Domain Name";
|
scope.domainLabel = "Domain Name";
|
||||||
scope.password_required = true;
|
scope.password_required = true;
|
||||||
scope.project_required = true;
|
scope.project_required = true;
|
||||||
scope.domain_required = true;
|
|
||||||
scope.host_required = true;
|
scope.host_required = true;
|
||||||
scope.username_required = true;
|
scope.username_required = true;
|
||||||
scope.projectPopOver = "<p>This is the tenant name " +
|
scope.projectPopOver = "<p>This is the tenant name. " +
|
||||||
"or tenant id. This value is usually the same " +
|
" This value is usually the same " +
|
||||||
" as the username.</p>";
|
" as the username.</p>";
|
||||||
scope.hostPopOver = "<p>The host to authenticate with." +
|
scope.hostPopOver = "<p>The host to authenticate with." +
|
||||||
"<br />For example, https://openstack.business.com/v3</p>";
|
"<br />For example, https://openstack.business.com/v2.0/";
|
||||||
scope.domainPopOver = "<p>Domain used for Keystone v3 " +
|
|
||||||
"<br />identity service.</p>";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,568 +0,0 @@
|
|||||||
/*************************************************
|
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
|
||||||
*
|
|
||||||
* All Rights Reserved
|
|
||||||
*************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngdoc function
|
|
||||||
* @name helpers.function:EventViewer
|
|
||||||
* @description eventviewerhelper
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default
|
|
||||||
angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition', 'HostsHelper'])
|
|
||||||
|
|
||||||
.factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'Empty', 'EventAddPreFormattedText',
|
|
||||||
function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, Empty, EventAddPreFormattedText) {
|
|
||||||
return function(params) {
|
|
||||||
var parent_scope = params.scope,
|
|
||||||
url = params.url,
|
|
||||||
event_id = params.event_id,
|
|
||||||
parent_id = params.parent_id,
|
|
||||||
title = params.title, //optional
|
|
||||||
scope = parent_scope.$new(true),
|
|
||||||
index = params.index,
|
|
||||||
page,
|
|
||||||
current_event;
|
|
||||||
|
|
||||||
if (scope.removeShowNextEvent) {
|
|
||||||
scope.removeShowNextEvent();
|
|
||||||
}
|
|
||||||
scope.removeShowNextEvent = scope.$on('ShowNextEvent', function(e, data, show_event) {
|
|
||||||
scope.events = data;
|
|
||||||
$('#event-next-spinner').slideUp(200);
|
|
||||||
if (show_event === 'prev') {
|
|
||||||
showEvent(scope.events.length - 1);
|
|
||||||
}
|
|
||||||
else if (show_event === 'next') {
|
|
||||||
showEvent(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// show scope.events[idx]
|
|
||||||
function showEvent(idx) {
|
|
||||||
var show_tabs = false, elem, data;
|
|
||||||
|
|
||||||
if (idx > scope.events.length - 1) {
|
|
||||||
GetEvent({
|
|
||||||
scope: scope,
|
|
||||||
url: scope.next_event_set,
|
|
||||||
show_event: 'next'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx < 0) {
|
|
||||||
GetEvent({
|
|
||||||
scope: scope,
|
|
||||||
url: scope.prev_event_set,
|
|
||||||
show_event: 'prev'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = scope.events[idx];
|
|
||||||
current_event = idx;
|
|
||||||
|
|
||||||
$('#status-form-container').empty();
|
|
||||||
$('#results-form-container').empty();
|
|
||||||
$('#timing-form-container').empty();
|
|
||||||
$('#stdout-form-container').empty();
|
|
||||||
$('#stderr-form-container').empty();
|
|
||||||
$('#traceback-form-container').empty();
|
|
||||||
$('#json-form-container').empty();
|
|
||||||
$('#eventview-tabs li:eq(1)').hide();
|
|
||||||
$('#eventview-tabs li:eq(2)').hide();
|
|
||||||
$('#eventview-tabs li:eq(3)').hide();
|
|
||||||
$('#eventview-tabs li:eq(4)').hide();
|
|
||||||
$('#eventview-tabs li:eq(5)').hide();
|
|
||||||
$('#eventview-tabs li:eq(6)').hide();
|
|
||||||
|
|
||||||
EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' });
|
|
||||||
|
|
||||||
if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) {
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(1)').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) {
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(2)').show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.stdout) {
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(3)').show();
|
|
||||||
EventAddPreFormattedText({
|
|
||||||
id: 'stdout-form-container',
|
|
||||||
val: data.stdout
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.stderr) {
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(4)').show();
|
|
||||||
EventAddPreFormattedText({
|
|
||||||
id: 'stderr-form-container',
|
|
||||||
val: data.stderr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.traceback) {
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(5)').show();
|
|
||||||
EventAddPreFormattedText({
|
|
||||||
id: 'traceback-form-container',
|
|
||||||
val: data.traceback
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
show_tabs = true;
|
|
||||||
$('#eventview-tabs li:eq(6)').show();
|
|
||||||
EventAddPreFormattedText({
|
|
||||||
id: 'json-form-container',
|
|
||||||
val: JSON.stringify(data, null, 2)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!show_tabs) {
|
|
||||||
$('#eventview-tabs').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
elem = angular.element(document.getElementById('eventviewer-modal-dialog'));
|
|
||||||
$compile(elem)(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setButtonMargin() {
|
|
||||||
var width = ($('.ui-dialog[aria-describedby="eventviewer-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#events-next-button').outerWidth() - 73;
|
|
||||||
$('#events-next-button').css({'margin-right': width + 'px'});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSpinner() {
|
|
||||||
var position;
|
|
||||||
if ($('#event-next-spinner').length > 0) {
|
|
||||||
$('#event-next-spinner').remove();
|
|
||||||
}
|
|
||||||
position = $('#events-next-button').position();
|
|
||||||
$('#events-next-button').after('<i class="fa fa-cog fa-spin" id="event-next-spinner" style="display:none; position:absolute; top:' + (position.top + 15) + 'px; left:' + (position.left + 75) + 'px;"></i>');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scope.removeModalReady) {
|
|
||||||
scope.removeModalReady();
|
|
||||||
}
|
|
||||||
scope.removeModalReady = scope.$on('ModalReady', function() {
|
|
||||||
Wait('stop');
|
|
||||||
$('#eventviewer-modal-dialog').dialog('open');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (scope.removeJobReady) {
|
|
||||||
scope.removeJobReady();
|
|
||||||
}
|
|
||||||
scope.removeEventReady = scope.$on('EventReady', function(e, data) {
|
|
||||||
var btns;
|
|
||||||
scope.events = data;
|
|
||||||
if (event_id) {
|
|
||||||
// find and show the selected event
|
|
||||||
data.every(function(row, idx) {
|
|
||||||
if (parseInt(row.id,10) === parseInt(event_id,10)) {
|
|
||||||
current_event = idx;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
current_event = 0;
|
|
||||||
}
|
|
||||||
showEvent(current_event);
|
|
||||||
|
|
||||||
btns = [];
|
|
||||||
if (scope.events.length > 1) {
|
|
||||||
btns.push({
|
|
||||||
label: "Prev",
|
|
||||||
onClick: function () {
|
|
||||||
if (current_event - 1 === 0 && !scope.prev_event_set) {
|
|
||||||
$('#events-prev-button').prop('disabled', true);
|
|
||||||
}
|
|
||||||
if (current_event - 1 < scope.events.length - 1) {
|
|
||||||
$('#events-next-button').prop('disabled', false);
|
|
||||||
}
|
|
||||||
showEvent(current_event - 1);
|
|
||||||
},
|
|
||||||
icon: "fa-chevron-left",
|
|
||||||
"class": "btn btn-primary",
|
|
||||||
id: "events-prev-button"
|
|
||||||
});
|
|
||||||
btns.push({
|
|
||||||
label: "Next",
|
|
||||||
onClick: function() {
|
|
||||||
if (current_event + 1 > 0) {
|
|
||||||
$('#events-prev-button').prop('disabled', false);
|
|
||||||
}
|
|
||||||
if (current_event + 1 >= scope.events.length - 1 && !scope.next_event_set) {
|
|
||||||
$('#events-next-button').prop('disabled', true);
|
|
||||||
}
|
|
||||||
showEvent(current_event + 1);
|
|
||||||
},
|
|
||||||
icon: "fa-chevron-right",
|
|
||||||
"class": "btn btn-primary",
|
|
||||||
id: "events-next-button"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
btns.push({
|
|
||||||
label: "OK",
|
|
||||||
onClick: function() {
|
|
||||||
scope.modalOK();
|
|
||||||
},
|
|
||||||
icon: "",
|
|
||||||
"class": "btn btn-primary",
|
|
||||||
id: "dialog-ok-button"
|
|
||||||
});
|
|
||||||
|
|
||||||
CreateDialog({
|
|
||||||
scope: scope,
|
|
||||||
width: 675,
|
|
||||||
height: 600,
|
|
||||||
minWidth: 450,
|
|
||||||
callback: 'ModalReady',
|
|
||||||
id: 'eventviewer-modal-dialog',
|
|
||||||
// onResizeStop: resizeText,
|
|
||||||
title: ( (title) ? title : 'Host Event' ),
|
|
||||||
buttons: btns,
|
|
||||||
closeOnEscape: true,
|
|
||||||
onResizeStop: function() {
|
|
||||||
setButtonMargin();
|
|
||||||
addSpinner();
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
try {
|
|
||||||
scope.$destroy();
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onOpen: function() {
|
|
||||||
$('#eventview-tabs a:first').tab('show');
|
|
||||||
$('#dialog-ok-button').focus();
|
|
||||||
if (scope.events.length > 1 && current_event === 0 && !scope.prev_event_set) {
|
|
||||||
$('#events-prev-button').prop('disabled', true);
|
|
||||||
}
|
|
||||||
if ((current_event === scope.events.length - 1) && !scope.next_event_set) {
|
|
||||||
$('#events-next-button').prop('disabled', true);
|
|
||||||
}
|
|
||||||
if (scope.events.length > 1) {
|
|
||||||
setButtonMargin();
|
|
||||||
addSpinner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
page = (index) ? Math.ceil((index+1)/50) : 1;
|
|
||||||
url += (/\/$/.test(url)) ? '?' : '&';
|
|
||||||
url += (parent_id) ? 'page='+page +'&parent=' + parent_id + '&page_size=50&order=host_name,counter' : 'page_size=50&order=host_name,counter';
|
|
||||||
|
|
||||||
GetEvent({
|
|
||||||
url: url,
|
|
||||||
scope: scope
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.modalOK = function() {
|
|
||||||
$('#eventviewer-modal-dialog').dialog('close');
|
|
||||||
scope.$destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
|
|
||||||
.factory('GetEvent', ['Wait', 'Rest', 'ProcessErrors',
|
|
||||||
function(Wait, Rest, ProcessErrors) {
|
|
||||||
return function(params) {
|
|
||||||
var url = params.url,
|
|
||||||
scope = params.scope,
|
|
||||||
show_event = params.show_event,
|
|
||||||
results= [];
|
|
||||||
|
|
||||||
if (show_event) {
|
|
||||||
$('#event-next-spinner').show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Wait('start');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStatus(e) {
|
|
||||||
return (e.event === "runner_on_unreachable") ? "unreachable" : (e.event === "runner_on_skipped") ? 'skipped' : (e.failed) ? 'failed' :
|
|
||||||
(e.changed) ? 'changed' : 'ok';
|
|
||||||
}
|
|
||||||
|
|
||||||
Rest.setUrl(url);
|
|
||||||
Rest.get()
|
|
||||||
.success( function(data) {
|
|
||||||
|
|
||||||
if(jQuery.isEmptyObject(data)) {
|
|
||||||
Wait('stop');
|
|
||||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
|
||||||
msg: 'Failed to get event ' + url + '. ' });
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
scope.next_event_set = data.next;
|
|
||||||
scope.prev_event_set = data.previous;
|
|
||||||
data.results.forEach(function(event) {
|
|
||||||
var msg, key, event_data = {};
|
|
||||||
if (event.event_data.res) {
|
|
||||||
if (typeof event.event_data.res !== 'object') {
|
|
||||||
// turn event_data.res into an object
|
|
||||||
msg = event.event_data.res;
|
|
||||||
event.event_data.res = {};
|
|
||||||
event.event_data.res.msg = msg;
|
|
||||||
}
|
|
||||||
for (key in event.event_data) {
|
|
||||||
if (key !== "res") {
|
|
||||||
event.event_data.res[key] = event.event_data[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.event_data.res.ansible_facts) {
|
|
||||||
// don't show fact gathering results
|
|
||||||
event.event_data.res.task = "Gathering Facts";
|
|
||||||
delete event.event_data.res.ansible_facts;
|
|
||||||
}
|
|
||||||
event.event_data.res.status = getStatus(event);
|
|
||||||
event_data = event.event_data.res;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
event.event_data.status = getStatus(event);
|
|
||||||
event_data = event.event_data;
|
|
||||||
}
|
|
||||||
// convert results to stdout
|
|
||||||
if (event_data.results && typeof event_data.results === "object" && Array.isArray(event_data.results)) {
|
|
||||||
event_data.stdout = "";
|
|
||||||
event_data.results.forEach(function(row) {
|
|
||||||
event_data.stdout += row + "\n";
|
|
||||||
});
|
|
||||||
delete event_data.results;
|
|
||||||
}
|
|
||||||
if (event_data.invocation) {
|
|
||||||
for (key in event_data.invocation) {
|
|
||||||
event_data[key] = event_data.invocation[key];
|
|
||||||
}
|
|
||||||
delete event_data.invocation;
|
|
||||||
}
|
|
||||||
event_data.play = event.play;
|
|
||||||
if (event.task) {
|
|
||||||
event_data.task = event.task;
|
|
||||||
}
|
|
||||||
event_data.created = event.created;
|
|
||||||
event_data.role = event.role;
|
|
||||||
event_data.host_id = event.host;
|
|
||||||
event_data.host_name = event.host_name;
|
|
||||||
if (event_data.host) {
|
|
||||||
delete event_data.host;
|
|
||||||
}
|
|
||||||
event_data.id = event.id;
|
|
||||||
event_data.parent = event.parent;
|
|
||||||
event_data.event = (event.event_display) ? event.event_display : event.event;
|
|
||||||
results.push(event_data);
|
|
||||||
});
|
|
||||||
if (show_event) {
|
|
||||||
scope.$emit('ShowNextEvent', results, show_event);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
scope.$emit('EventReady', results);
|
|
||||||
}
|
|
||||||
} //else statement
|
|
||||||
})
|
|
||||||
.error(function(data, status) {
|
|
||||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
|
||||||
msg: 'Failed to get event ' + url + '. GET returned: ' + status });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
|
|
||||||
.factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) {
|
|
||||||
return function(params) {
|
|
||||||
var scope = params.scope,
|
|
||||||
id = params.id,
|
|
||||||
event = params.event,
|
|
||||||
section = params.section,
|
|
||||||
html = '', e;
|
|
||||||
|
|
||||||
function parseObject(obj) {
|
|
||||||
// parse nested JSON objects. a mini version of parseJSON without references to the event form object.
|
|
||||||
var i, key, html = '';
|
|
||||||
for (key in obj) {
|
|
||||||
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
|
||||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">" + obj[key] + "</td></tr>";
|
|
||||||
}
|
|
||||||
else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
|
|
||||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">[";
|
|
||||||
for (i = 0; i < obj[key].length; i++) {
|
|
||||||
html += obj[key][i] + ",";
|
|
||||||
}
|
|
||||||
html = html.replace(/,$/,'');
|
|
||||||
html += "]</td></tr>\n";
|
|
||||||
}
|
|
||||||
else if (typeof obj[key] === "object") {
|
|
||||||
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(obj[key]) + "</tbody>\n</table>\n</td></tr>\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseItem(itm, key, label) {
|
|
||||||
var i, html = '';
|
|
||||||
if (Empty(itm)) {
|
|
||||||
// exclude empty items
|
|
||||||
}
|
|
||||||
else if (typeof itm === "boolean" || typeof itm === "number" || typeof itm === "string") {
|
|
||||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">";
|
|
||||||
if (key === "status") {
|
|
||||||
html += "<i class=\"fa icon-job-" + itm + "\"></i> " + itm;
|
|
||||||
}
|
|
||||||
else if (key === "start" || key === "end" || key === "created") {
|
|
||||||
if (!/Z$/.test(itm)) {
|
|
||||||
itm = itm.replace(/\ /,'T') + 'Z';
|
|
||||||
html += $filter('longDate')(itm);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
html += $filter('longDate')(itm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (key === "host_name" && event.host_id) {
|
|
||||||
html += "<a href=\"/#/home/hosts/?id=" + event.host_id + "\" target=\"_blank\" " +
|
|
||||||
"aw-tool-tip=\"View host. Opens in new tab or window.\" data-placement=\"top\" " +
|
|
||||||
">" + itm + "</a>";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if( typeof itm === "string"){
|
|
||||||
if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){
|
|
||||||
itm = $filter('sanitize')(itm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html += "<span ng-non-bindable>" + itm + "</span>";
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "</td></tr>\n";
|
|
||||||
}
|
|
||||||
else if (typeof itm === "object" && Array.isArray(itm)) {
|
|
||||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">[";
|
|
||||||
for (i = 0; i < itm.length; i++) {
|
|
||||||
html += itm[i] + ",";
|
|
||||||
}
|
|
||||||
html = html.replace(/,$/,'');
|
|
||||||
html += "]</td></tr>\n";
|
|
||||||
}
|
|
||||||
else if (typeof itm === "object") {
|
|
||||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(itm) + "</tbody>\n</table>\n</td></tr>\n";
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseJSON(obj) {
|
|
||||||
var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = "";
|
|
||||||
if (typeof obj === "object") {
|
|
||||||
html += "<table class=\"table eventviewer-status\">\n";
|
|
||||||
html += "<tbody>\n";
|
|
||||||
keys = [];
|
|
||||||
for (key in EventsViewerForm.fields) {
|
|
||||||
if (EventsViewerForm.fields[key].section === section) {
|
|
||||||
keys.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys.forEach(function(key) {
|
|
||||||
var h, label;
|
|
||||||
label = EventsViewerForm.fields[key].label;
|
|
||||||
h = parseItem(obj[key], key, label);
|
|
||||||
if (h) {
|
|
||||||
html += h;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (section === 'Results') {
|
|
||||||
// Add to result fields that might not be found in the form object.
|
|
||||||
for (key in obj) {
|
|
||||||
h = '';
|
|
||||||
if (key !== 'host_id' && key !== 'parent' && key !== 'event' && key !== 'src' && key !== 'md5sum' &&
|
|
||||||
key !== 'stdout' && key !== 'traceback' && key !== 'stderr' && key !== 'cmd' && key !=='changed' && key !== "verbose_override" &&
|
|
||||||
key !== 'feature_result' && key !== 'warnings') {
|
|
||||||
if (!EventsViewerForm.fields[key]) {
|
|
||||||
h = parseItem(obj[key], key, key);
|
|
||||||
if (h) {
|
|
||||||
html += h;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key === 'cmd') {
|
|
||||||
// only show cmd if it's a cmd that was run
|
|
||||||
if (!EventsViewerForm.fields[key] && obj[key].length > 0) {
|
|
||||||
// include the label head Shell Command instead of CMD in the modal
|
|
||||||
if(typeof(obj[key]) === 'string'){
|
|
||||||
obj[key] = [obj[key]];
|
|
||||||
}
|
|
||||||
string_cmd += obj[key].join(" ");
|
|
||||||
h = parseItem(string_cmd, key, "Shell Command");
|
|
||||||
if (h) {
|
|
||||||
html += h;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key === 'warnings') {
|
|
||||||
if (!EventsViewerForm.fields[key] && obj[key].length > 0) {
|
|
||||||
if(typeof(obj[key]) === 'string'){
|
|
||||||
obj[key] = [obj[key]];
|
|
||||||
}
|
|
||||||
string_warnings += obj[key].join(" ");
|
|
||||||
h = parseItem(string_warnings, key, "Warnings");
|
|
||||||
if (h) {
|
|
||||||
html += h;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html += "</tbody>\n";
|
|
||||||
html += "</table>\n";
|
|
||||||
}
|
|
||||||
return (found) ? html : '';
|
|
||||||
}
|
|
||||||
html = parseJSON(event);
|
|
||||||
|
|
||||||
e = angular.element(document.getElementById(id));
|
|
||||||
e.empty();
|
|
||||||
if (html) {
|
|
||||||
e.html(html);
|
|
||||||
$compile(e)(scope);
|
|
||||||
}
|
|
||||||
return (html) ? true : false;
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
|
|
||||||
.factory('EventAddTextarea', [ function() {
|
|
||||||
return function(params) {
|
|
||||||
var container_id = params.container_id,
|
|
||||||
val = params.val,
|
|
||||||
fld_id = params.fld_id,
|
|
||||||
html;
|
|
||||||
html = "<div class=\"form-group\">\n" +
|
|
||||||
"<textarea ng-non-bindable id=\"" + fld_id + "\" class=\"form-control mono-space\" rows=\"12\" readonly>" + val + "</textarea>" +
|
|
||||||
"</div>\n";
|
|
||||||
$('#' + container_id).empty().html(html);
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
|
|
||||||
.factory('EventAddPreFormattedText', ['$filter', function($filter) {
|
|
||||||
return function(params) {
|
|
||||||
var id = params.id,
|
|
||||||
val = params.val,
|
|
||||||
html;
|
|
||||||
if( typeof val === "string"){
|
|
||||||
if(val.indexOf('<') > -1 || val.indexOf('>') > -1){
|
|
||||||
val = $filter('sanitize')(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html = "<pre ng-non-bindable>" + val + "</pre>\n";
|
|
||||||
$('#' + id).empty().html(html);
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -305,8 +305,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
field_id: 'source_extra_vars', onReady: callback });
|
field_id: 'source_extra_vars', onReady: callback });
|
||||||
}
|
}
|
||||||
if(scope.source.value==="vmware" ||
|
if(scope.source.value==="vmware" ||
|
||||||
scope.source.value==="openstack" ||
|
scope.source.value==="openstack"){
|
||||||
scope.source.value==="openstack_v3"){
|
|
||||||
scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
|
scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
|
||||||
ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName,
|
ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName,
|
||||||
field_id: 'source_inventory_variables', onReady: callback });
|
field_id: 'source_inventory_variables', onReady: callback });
|
||||||
@@ -316,8 +315,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
scope.source.value==='gce' ||
|
scope.source.value==='gce' ||
|
||||||
scope.source.value === 'azure' ||
|
scope.source.value === 'azure' ||
|
||||||
scope.source.value === 'vmware' ||
|
scope.source.value === 'vmware' ||
|
||||||
scope.source.value === 'openstack' ||
|
scope.source.value === 'openstack') {
|
||||||
scope.source.value === 'openstack_v3') {
|
|
||||||
if (scope.source.value === 'ec2') {
|
if (scope.source.value === 'ec2') {
|
||||||
kind = 'aws';
|
kind = 'aws';
|
||||||
} else {
|
} else {
|
||||||
@@ -926,8 +924,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
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 });
|
||||||
} else if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
|
} else if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
|
||||||
sources_scope.source.value === 'openstack' ||
|
sources_scope.source.value === 'openstack')) {
|
||||||
sources_scope.source.value === 'openstack_v3')) {
|
|
||||||
Wait('start');
|
Wait('start');
|
||||||
ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.fields.inventory_variables.parseTypeName,
|
ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.fields.inventory_variables.parseTypeName,
|
||||||
field_id: 'source_inventory_variables', onReady: waitStop });
|
field_id: 'source_inventory_variables', onReady: waitStop });
|
||||||
@@ -1306,8 +1303,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
|
if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
|
||||||
sources_scope.source.value === 'openstack' ||
|
sources_scope.source.value === 'openstack')) {
|
||||||
sources_scope.source.value === 'openstack_v3')) {
|
|
||||||
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
|
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -437,10 +437,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
|
|||||||
|
|
||||||
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
|
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
|
||||||
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis',
|
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis',
|
||||||
'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize',
|
'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'ParamPass',
|
||||||
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
|
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
|
||||||
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON,
|
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON,
|
||||||
ParseVariableString, CreateDialog, TextareaResize) {
|
ParseVariableString, CreateDialog, TextareaResize, ParamPass) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
|
|
||||||
var parent_scope = params.host_scope,
|
var parent_scope = params.host_scope,
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name controllers.function:Inventories
|
||||||
|
* @description This controller's for the Inventory page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
|
||||||
|
$stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
|
||||||
|
ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
|
||||||
|
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
|
||||||
|
$state) {
|
||||||
|
|
||||||
|
ClearScope();
|
||||||
|
|
||||||
|
// Inject dynamic view
|
||||||
|
var defaultUrl = GetBasePath('inventory'),
|
||||||
|
form = InventoryForm(),
|
||||||
|
generator = GenerateForm;
|
||||||
|
|
||||||
|
form.formLabelSize = null;
|
||||||
|
form.formFieldSize = null;
|
||||||
|
|
||||||
|
generator.inject(form, { mode: 'add', related: false, scope: $scope });
|
||||||
|
|
||||||
|
generator.reset();
|
||||||
|
|
||||||
|
$scope.parseType = 'yaml';
|
||||||
|
ParseTypeChange({
|
||||||
|
scope: $scope,
|
||||||
|
variable: 'variables',
|
||||||
|
parse_variable: 'parseType',
|
||||||
|
field_id: 'inventory_variables'
|
||||||
|
});
|
||||||
|
|
||||||
|
LookUpInit({
|
||||||
|
scope: $scope,
|
||||||
|
form: form,
|
||||||
|
current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null,
|
||||||
|
list: OrganizationList,
|
||||||
|
field: 'organization',
|
||||||
|
input_type: 'radio'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save
|
||||||
|
$scope.formSave = function () {
|
||||||
|
generator.clearApiErrors();
|
||||||
|
Wait('start');
|
||||||
|
try {
|
||||||
|
var fld, json_data, data;
|
||||||
|
|
||||||
|
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||||
|
|
||||||
|
data = {};
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (form.fields[fld].realName) {
|
||||||
|
data[form.fields[fld].realName] = $scope[fld];
|
||||||
|
} else {
|
||||||
|
data[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
Rest.post(data)
|
||||||
|
.success(function (data) {
|
||||||
|
var inventory_id = data.id;
|
||||||
|
Wait('stop');
|
||||||
|
$location.path('/inventories/' + inventory_id + '/manage');
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors( $scope, data, status, form, { hdr: 'Error!',
|
||||||
|
msg: 'Failed to add new inventory. Post returned status: ' + status });
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Wait('stop');
|
||||||
|
Alert("Error", "Error parsing inventory variables. Parser returned: " + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.formCancel = function () {
|
||||||
|
$state.transitionTo('inventories');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default['$scope', '$rootScope', '$compile', '$location',
|
||||||
|
'$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
|
||||||
|
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
|
||||||
|
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
|
||||||
|
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd]
|
||||||
24
awx/ui/client/src/inventories/add/inventory-add.route.js
Normal file
24
awx/ui/client/src/inventories/add/inventory-add.route.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
import InventoriesAdd from './inventory-add.controller';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'inventories.add',
|
||||||
|
route: '/add',
|
||||||
|
templateUrl: templateUrl('inventories/inventories'),
|
||||||
|
controller: InventoriesAdd,
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
parent: "inventories",
|
||||||
|
label: "CREATE INVENTORY"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
14
awx/ui/client/src/inventories/add/main.js
Normal file
14
awx/ui/client/src/inventories/add/main.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './inventory-add.route';
|
||||||
|
import controller from './inventory-add.controller';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('inventoryAdd', [])
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route);
|
||||||
|
}]);
|
||||||
329
awx/ui/client/src/inventories/edit/inventory-edit.controller.js
Normal file
329
awx/ui/client/src/inventories/edit/inventory-edit.controller.js
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name controllers.function:Inventories
|
||||||
|
* @description This controller's for the Inventory page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function InventoriesEdit($scope, $rootScope, $compile, $location,
|
||||||
|
$log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
|
||||||
|
ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
|
||||||
|
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
|
||||||
|
ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
|
||||||
|
Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) {
|
||||||
|
|
||||||
|
ClearScope();
|
||||||
|
|
||||||
|
// Inject dynamic view
|
||||||
|
var defaultUrl = GetBasePath('inventory'),
|
||||||
|
form = InventoryForm(),
|
||||||
|
generator = GenerateForm,
|
||||||
|
inventory_id = $stateParams.inventory_id,
|
||||||
|
master = {},
|
||||||
|
fld, json_data, data,
|
||||||
|
relatedSets = {};
|
||||||
|
|
||||||
|
form.formLabelSize = null;
|
||||||
|
form.formFieldSize = null;
|
||||||
|
$scope.inventory_id = inventory_id;
|
||||||
|
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
||||||
|
|
||||||
|
generator.reset();
|
||||||
|
|
||||||
|
|
||||||
|
// After the project is loaded, retrieve each related set
|
||||||
|
if ($scope.inventoryLoadedRemove) {
|
||||||
|
$scope.inventoryLoadedRemove();
|
||||||
|
}
|
||||||
|
$scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () {
|
||||||
|
var set;
|
||||||
|
for (set in relatedSets) {
|
||||||
|
$scope.search(relatedSets[set].iterator);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
|
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
|
||||||
|
Rest.get()
|
||||||
|
.success(function (data) {
|
||||||
|
var fld;
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (fld === 'variables') {
|
||||||
|
$scope.variables = ParseVariableString(data.variables);
|
||||||
|
master.variables = $scope.variables;
|
||||||
|
} else if (fld === 'inventory_name') {
|
||||||
|
$scope[fld] = data.name;
|
||||||
|
master[fld] = $scope[fld];
|
||||||
|
} else if (fld === 'inventory_description') {
|
||||||
|
$scope[fld] = data.description;
|
||||||
|
master[fld] = $scope[fld];
|
||||||
|
} else if (data[fld]) {
|
||||||
|
$scope[fld] = data[fld];
|
||||||
|
master[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
if (form.fields[fld].sourceModel && data.summary_fields &&
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel]) {
|
||||||
|
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
||||||
|
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relatedSets = form.relatedSets(data.related);
|
||||||
|
|
||||||
|
// Initialize related search functions. Doing it here to make sure relatedSets object is populated.
|
||||||
|
RelatedSearchInit({
|
||||||
|
scope: $scope,
|
||||||
|
form: form,
|
||||||
|
relatedSets: relatedSets
|
||||||
|
});
|
||||||
|
RelatedPaginateInit({
|
||||||
|
scope: $scope,
|
||||||
|
relatedSets: relatedSets
|
||||||
|
});
|
||||||
|
|
||||||
|
Wait('stop');
|
||||||
|
$scope.parseType = 'yaml';
|
||||||
|
ParseTypeChange({
|
||||||
|
scope: $scope,
|
||||||
|
variable: 'variables',
|
||||||
|
parse_variable: 'parseType',
|
||||||
|
field_id: 'inventory_variables'
|
||||||
|
});
|
||||||
|
LookUpInit({
|
||||||
|
scope: $scope,
|
||||||
|
form: form,
|
||||||
|
current_item: $scope.organization,
|
||||||
|
list: OrganizationList,
|
||||||
|
field: 'organization',
|
||||||
|
input_type: 'radio'
|
||||||
|
});
|
||||||
|
$scope.$emit('inventoryLoaded');
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status });
|
||||||
|
});
|
||||||
|
// Save
|
||||||
|
$scope.formSave = function () {
|
||||||
|
Wait('start');
|
||||||
|
|
||||||
|
// Make sure we have valid variable data
|
||||||
|
json_data = ToJSON($scope.parseType, $scope.variables);
|
||||||
|
|
||||||
|
data = {};
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (form.fields[fld].realName) {
|
||||||
|
data[form.fields[fld].realName] = $scope[fld];
|
||||||
|
} else {
|
||||||
|
data[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rest.setUrl(defaultUrl + inventory_id + '/');
|
||||||
|
Rest.put(data)
|
||||||
|
.success(function () {
|
||||||
|
Wait('stop');
|
||||||
|
$location.path('/inventories/');
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
|
||||||
|
msg: 'Failed to update inventory. PUT returned status: ' + status });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.manageInventory = function(){
|
||||||
|
$location.path($location.path() + '/manage');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.formCancel = function () {
|
||||||
|
$state.transitionTo('inventories');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addScanJob = function(){
|
||||||
|
$location.path($location.path()+'/job_templates/add');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.launchScanJob = function(){
|
||||||
|
PlaybookRun({ scope: $scope, id: this.scan_job_template.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.scheduleScanJob = function(){
|
||||||
|
$location.path('/job_templates/'+this.scan_job_template.id+'/schedules');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editScanJob = function(){
|
||||||
|
$location.path($location.path()+'/job_templates/'+this.scan_job_template.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.copyScanJobTemplate = function(){
|
||||||
|
var id = this.scan_job_template.id,
|
||||||
|
name = this.scan_job_template.name,
|
||||||
|
element,
|
||||||
|
buttons = [{
|
||||||
|
"label": "Cancel",
|
||||||
|
"onClick": function() {
|
||||||
|
$(this).dialog('close');
|
||||||
|
},
|
||||||
|
"icon": "fa-times",
|
||||||
|
"class": "btn btn-default",
|
||||||
|
"id": "copy-close-button"
|
||||||
|
},{
|
||||||
|
"label": "Copy",
|
||||||
|
"onClick": function() {
|
||||||
|
copyAction();
|
||||||
|
},
|
||||||
|
"icon": "fa-copy",
|
||||||
|
"class": "btn btn-primary",
|
||||||
|
"id": "job-copy-button"
|
||||||
|
}],
|
||||||
|
copyAction = function () {
|
||||||
|
// retrieve the copy of the job template object from the api, then overwrite the name and throw away the id
|
||||||
|
Wait('start');
|
||||||
|
var url = GetBasePath('job_templates')+id;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function (data) {
|
||||||
|
data.name = $scope.new_copy_name;
|
||||||
|
delete data.id;
|
||||||
|
$scope.$emit('GoToCopy', data);
|
||||||
|
})
|
||||||
|
.error(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CreateDialog({
|
||||||
|
id: 'copy-job-modal' ,
|
||||||
|
title: "Copy",
|
||||||
|
scope: $scope,
|
||||||
|
buttons: buttons,
|
||||||
|
width: 500,
|
||||||
|
height: 300,
|
||||||
|
minWidth: 200,
|
||||||
|
callback: 'CopyDialogReady'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#job_name').text(name);
|
||||||
|
$('#copy-job-modal').show();
|
||||||
|
|
||||||
|
|
||||||
|
if ($scope.removeCopyDialogReady) {
|
||||||
|
$scope.removeCopyDialogReady();
|
||||||
|
}
|
||||||
|
$scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() {
|
||||||
|
//clear any old remaining text
|
||||||
|
$scope.new_copy_name = "" ;
|
||||||
|
$scope.copy_form.$setPristine();
|
||||||
|
$('#copy-job-modal').dialog('open');
|
||||||
|
$('#job-copy-button').attr('ng-disabled', "!copy_form.$valid");
|
||||||
|
element = angular.element(document.getElementById('job-copy-button'));
|
||||||
|
$compile(element)($scope);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeGoToCopy) {
|
||||||
|
$scope.removeGoToCopy();
|
||||||
|
}
|
||||||
|
$scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) {
|
||||||
|
var url = GetBasePath('job_templates'),
|
||||||
|
old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.post(data)
|
||||||
|
.success(function (data) {
|
||||||
|
if(data.survey_enabled===true){
|
||||||
|
$scope.$emit("CopySurvey", data, old_survey_url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#copy-job-modal').dialog('close');
|
||||||
|
Wait('stop');
|
||||||
|
$location.path($location.path() + '/job_templates/' + data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.error(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeCopySurvey) {
|
||||||
|
$scope.removeCopySurvey();
|
||||||
|
}
|
||||||
|
$scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) {
|
||||||
|
// var url = data.related.survey_spec;
|
||||||
|
Rest.setUrl(old_url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function (survey_data) {
|
||||||
|
|
||||||
|
Rest.setUrl(new_data.related.survey_spec);
|
||||||
|
Rest.post(survey_data)
|
||||||
|
.success(function () {
|
||||||
|
$('#copy-job-modal').dialog('close');
|
||||||
|
Wait('stop');
|
||||||
|
$location.path($location.path() + '/job_templates/' + new_data.id);
|
||||||
|
})
|
||||||
|
.error(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.error(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteScanJob = function () {
|
||||||
|
var id = this.scan_job_template.id ,
|
||||||
|
action = function () {
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
Wait('start');
|
||||||
|
deleteJobTemplate(id)
|
||||||
|
.success(function () {
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
$scope.search(form.related.scan_job_templates.iterator);
|
||||||
|
})
|
||||||
|
.error(function (data) {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Prompt({
|
||||||
|
hdr: 'Delete',
|
||||||
|
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the job template below?</div><div class="Prompt-bodyTarget">' + this.scan_job_template.name + '</div>',
|
||||||
|
action: action,
|
||||||
|
actionText: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ['$scope', '$rootScope', '$compile', '$location',
|
||||||
|
'$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
|
||||||
|
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
|
||||||
|
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
|
||||||
|
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString',
|
||||||
|
'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt',
|
||||||
|
'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state',
|
||||||
|
InventoriesEdit,
|
||||||
|
];
|
||||||
26
awx/ui/client/src/inventories/edit/inventory-edit.route.js
Normal file
26
awx/ui/client/src/inventories/edit/inventory-edit.route.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
import InventoriesEdit from './inventory-edit.controller';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'inventories.edit',
|
||||||
|
route: '/:inventory_id',
|
||||||
|
templateUrl: templateUrl('inventories/inventories'),
|
||||||
|
controller: InventoriesEdit,
|
||||||
|
data: {
|
||||||
|
activityStreamId: 'inventory_id'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY EDIT"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
14
awx/ui/client/src/inventories/edit/main.js
Normal file
14
awx/ui/client/src/inventories/edit/main.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './inventory-edit.route';
|
||||||
|
import controller from './inventory-edit.controller';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('inventoryEdit', [])
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route);
|
||||||
|
}]);
|
||||||
364
awx/ui/client/src/inventories/list/inventory-list.controller.js
Normal file
364
awx/ui/client/src/inventories/list/inventory-list.controller.js
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name controllers.function:Inventories
|
||||||
|
* @description This controller's for the Inventory page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function InventoriesList($scope, $rootScope, $location, $log,
|
||||||
|
$stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
|
||||||
|
generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
|
||||||
|
ClearScope, ProcessErrors, GetBasePath, Wait,
|
||||||
|
Find, Empty, $state) {
|
||||||
|
|
||||||
|
var list = InventoryList,
|
||||||
|
defaultUrl = GetBasePath('inventory'),
|
||||||
|
view = generateList,
|
||||||
|
paths = $location.path().replace(/^\//, '').split('/'),
|
||||||
|
mode = (paths[0] === 'inventories') ? 'edit' : 'select';
|
||||||
|
|
||||||
|
function ellipsis(a) {
|
||||||
|
if (a.length > 20) {
|
||||||
|
return a.substr(0,20) + '...';
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachElem(event, html, title) {
|
||||||
|
var elem = $(event.target).parent();
|
||||||
|
try {
|
||||||
|
elem.tooltip('hide');
|
||||||
|
elem.popover('destroy');
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
$('.popover').each(function() {
|
||||||
|
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
$('.tooltip').each( function() {
|
||||||
|
// close any lingering tool tipss
|
||||||
|
$(this).hide();
|
||||||
|
});
|
||||||
|
elem.attr({
|
||||||
|
"aw-pop-over": html,
|
||||||
|
"data-popover-title": title,
|
||||||
|
"data-placement": "right" });
|
||||||
|
$compile(elem)($scope);
|
||||||
|
elem.on('shown.bs.popover', function() {
|
||||||
|
$('.popover').each(function() {
|
||||||
|
$compile($(this))($scope); //make nested directives work!
|
||||||
|
});
|
||||||
|
$('.popover-content, .popover-title').click(function() {
|
||||||
|
elem.popover('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
elem.popover('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
view.inject(InventoryList, { mode: mode, scope: $scope });
|
||||||
|
$rootScope.flashMessage = null;
|
||||||
|
|
||||||
|
SearchInit({
|
||||||
|
scope: $scope,
|
||||||
|
set: 'inventories',
|
||||||
|
list: list,
|
||||||
|
url: defaultUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
PaginateInit({
|
||||||
|
scope: $scope,
|
||||||
|
list: list,
|
||||||
|
url: defaultUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($stateParams.name) {
|
||||||
|
$scope[InventoryList.iterator + 'InputDisable'] = false;
|
||||||
|
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name;
|
||||||
|
$scope[InventoryList.iterator + 'SearchField'] = 'name';
|
||||||
|
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label;
|
||||||
|
$scope[InventoryList.iterator + 'SearchSelectValue'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stateParams.has_active_failures) {
|
||||||
|
$scope[InventoryList.iterator + 'InputDisable'] = true;
|
||||||
|
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures;
|
||||||
|
$scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures';
|
||||||
|
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label;
|
||||||
|
$scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? {
|
||||||
|
value: 1
|
||||||
|
} : {
|
||||||
|
value: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stateParams.has_inventory_sources) {
|
||||||
|
$scope[InventoryList.iterator + 'InputDisable'] = true;
|
||||||
|
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources;
|
||||||
|
$scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources';
|
||||||
|
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label;
|
||||||
|
$scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? {
|
||||||
|
value: 1
|
||||||
|
} : {
|
||||||
|
value: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stateParams.inventory_sources_with_failures) {
|
||||||
|
// pass a value of true, however this field actually contains an integer value
|
||||||
|
$scope[InventoryList.iterator + 'InputDisable'] = true;
|
||||||
|
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures;
|
||||||
|
$scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures';
|
||||||
|
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label;
|
||||||
|
$scope[InventoryList.iterator + 'SearchType'] = 'gtzero';
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.search(list.iterator);
|
||||||
|
|
||||||
|
if ($scope.removePostRefresh) {
|
||||||
|
$scope.removePostRefresh();
|
||||||
|
}
|
||||||
|
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
|
||||||
|
//If we got here by deleting an inventory, stop the spinner and cleanup events
|
||||||
|
Wait('stop');
|
||||||
|
try {
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
$scope.inventories.forEach(function(inventory, idx) {
|
||||||
|
$scope.inventories[idx].launch_class = "";
|
||||||
|
if (inventory.has_inventory_sources) {
|
||||||
|
if (inventory.inventory_sources_with_failures > 0) {
|
||||||
|
$scope.inventories[idx].syncStatus = 'error';
|
||||||
|
$scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.inventories[idx].syncStatus = 'successful';
|
||||||
|
$scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.inventories[idx].syncStatus = 'na';
|
||||||
|
$scope.inventories[idx].syncTip = 'Not configured for inventory sync.';
|
||||||
|
$scope.inventories[idx].launch_class = "btn-disabled";
|
||||||
|
}
|
||||||
|
if (inventory.has_active_failures) {
|
||||||
|
$scope.inventories[idx].hostsStatus = 'error';
|
||||||
|
$scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.';
|
||||||
|
}
|
||||||
|
else if (inventory.total_hosts) {
|
||||||
|
$scope.inventories[idx].hostsStatus = 'successful';
|
||||||
|
$scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.inventories[idx].hostsStatus = 'none';
|
||||||
|
$scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeRefreshInventories) {
|
||||||
|
$scope.removeRefreshInventories();
|
||||||
|
}
|
||||||
|
$scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () {
|
||||||
|
// Reflect changes after inventory properties edit completes
|
||||||
|
$scope.search(list.iterator);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeHostSummaryReady) {
|
||||||
|
$scope.removeHostSummaryReady();
|
||||||
|
}
|
||||||
|
$scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) {
|
||||||
|
|
||||||
|
var html, title = "Recent Jobs";
|
||||||
|
Wait('stop');
|
||||||
|
if (data.count > 0) {
|
||||||
|
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
|
||||||
|
html += "<thead>\n";
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<th>Status</th>";
|
||||||
|
html += "<th>Finished</th>";
|
||||||
|
html += "<th>Name</th>";
|
||||||
|
html += "</tr>\n";
|
||||||
|
html += "</thead>\n";
|
||||||
|
html += "<tbody>\n";
|
||||||
|
|
||||||
|
data.results.forEach(function(row) {
|
||||||
|
html += "<tr>\n";
|
||||||
|
html += "<td><a href=\"#/jobs/" + row.id + "\" " + "aw-tool-tip=\"" + row.status.charAt(0).toUpperCase() + row.status.slice(1) +
|
||||||
|
". Click for details\" aw-tip-placement=\"top\"><i class=\"fa icon-job-" + row.status + "\"></i></a></td>\n";
|
||||||
|
html += "<td>" + ($filter('longDate')(row.finished)).replace(/ /,'<br />') + "</td>";
|
||||||
|
html += "<td><a href=\"#/jobs/" + row.id + "\" " + "aw-tool-tip=\"" + row.status.charAt(0).toUpperCase() + row.status.slice(1) +
|
||||||
|
". Click for details\" aw-tip-placement=\"top\">" + ellipsis(row.name) + "</a></td>";
|
||||||
|
html += "</tr>\n";
|
||||||
|
});
|
||||||
|
html += "</tbody>\n";
|
||||||
|
html += "</table>\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
html = "<p>No recent job data available for this inventory.</p>\n";
|
||||||
|
}
|
||||||
|
attachElem(event, html, title);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeGroupSummaryReady) {
|
||||||
|
$scope.removeGroupSummaryReady();
|
||||||
|
}
|
||||||
|
$scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) {
|
||||||
|
var html, title;
|
||||||
|
|
||||||
|
Wait('stop');
|
||||||
|
|
||||||
|
// Build the html for our popover
|
||||||
|
html = "<table class=\"table table-condensed flyout\" style=\"width: 100%\">\n";
|
||||||
|
html += "<thead>\n";
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<th>Status</th>";
|
||||||
|
html += "<th>Last Sync</th>";
|
||||||
|
html += "<th>Group</th>";
|
||||||
|
html += "</tr>";
|
||||||
|
html += "</thead>\n";
|
||||||
|
html += "<tbody>\n";
|
||||||
|
data.results.forEach( function(row) {
|
||||||
|
if (row.related.last_update) {
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<td><a href=\"\" ng-click=\"viewJob('" + row.related.last_update + "')\" aw-tool-tip=\"" + row.status.charAt(0).toUpperCase() + row.status.slice(1) + ". Click for details\" aw-tip-placement=\"top\"><i class=\"fa icon-job-" + row.status + "\"></i></a></td>";
|
||||||
|
html += "<td>" + ($filter('longDate')(row.last_updated)).replace(/ /,'<br />') + "</td>";
|
||||||
|
html += "<td><a href=\"\" ng-click=\"viewJob('" + row.related.last_update + "')\">" + ellipsis(row.summary_fields.group.name) + "</a></td>";
|
||||||
|
html += "</tr>\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<td><a href=\"\" aw-tool-tip=\"No sync data\" aw-tip-placement=\"top\"><i class=\"fa icon-job-none\"></i></a></td>";
|
||||||
|
html += "<td>NA</td>";
|
||||||
|
html += "<td><a href=\"\">" + ellipsis(row.summary_fields.group.name) + "</a></td>";
|
||||||
|
html += "</tr>\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html += "</tbody>\n";
|
||||||
|
html += "</table>\n";
|
||||||
|
title = "Sync Status";
|
||||||
|
attachElem(event, html, title);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.showGroupSummary = function(event, id) {
|
||||||
|
var inventory;
|
||||||
|
if (!Empty(id)) {
|
||||||
|
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
|
||||||
|
if (inventory.syncStatus !== 'na') {
|
||||||
|
Wait('start');
|
||||||
|
Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5');
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
$scope.$emit('GroupSummaryReady', event, inventory, data);
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showHostSummary = function(event, id) {
|
||||||
|
var url, inventory;
|
||||||
|
if (!Empty(id)) {
|
||||||
|
inventory = Find({ list: $scope.inventories, key: 'id', val: id });
|
||||||
|
if (inventory.total_hosts > 0) {
|
||||||
|
Wait('start');
|
||||||
|
url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed=";
|
||||||
|
url += (inventory.has_active_failures) ? 'true' : "false";
|
||||||
|
url += "&order_by=-finished&page_size=5";
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.get()
|
||||||
|
.success( function(data) {
|
||||||
|
$scope.$emit('HostSummaryReady', event, data);
|
||||||
|
})
|
||||||
|
.error( function(data, status) {
|
||||||
|
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. GET returned: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.viewJob = function(url) {
|
||||||
|
|
||||||
|
// Pull the id out of the URL
|
||||||
|
var id = url.replace(/^\//, '').split('/')[3];
|
||||||
|
|
||||||
|
$state.go('inventorySyncStdout', {id: id});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addInventory = function () {
|
||||||
|
$state.go('inventories.add');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editInventory = function (id) {
|
||||||
|
$state.go('inventories.edit', {inventory_id: id});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.manageInventory = function(id){
|
||||||
|
$location.path($location.path() + '/' + id + '/manage');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteInventory = function (id, name) {
|
||||||
|
|
||||||
|
var action = function () {
|
||||||
|
var url = defaultUrl + id + '/';
|
||||||
|
Wait('start');
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.destroy()
|
||||||
|
.success(function () {
|
||||||
|
$scope.search(list.iterator);
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Prompt({
|
||||||
|
hdr: 'Delete',
|
||||||
|
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the inventory below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(name) + '</div>',
|
||||||
|
action: action,
|
||||||
|
actionText: 'DELETE'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.lookupOrganization = function (organization_id) {
|
||||||
|
Rest.setUrl(GetBasePath('organizations') + organization_id + '/');
|
||||||
|
Rest.get()
|
||||||
|
.success(function (data) {
|
||||||
|
return data.name;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status
|
||||||
|
$scope.viewJobs = function (id) {
|
||||||
|
$location.url('/jobs/?inventory__int=' + id);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.viewFailedJobs = function (id) {
|
||||||
|
$location.url('/jobs/?inventory__int=' + id + '&status=failed');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ['$scope', '$rootScope', '$location', '$log',
|
||||||
|
'$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList',
|
||||||
|
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
|
||||||
|
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', InventoriesList];
|
||||||
27
awx/ui/client/src/inventories/list/inventory-list.route.js
Normal file
27
awx/ui/client/src/inventories/list/inventory-list.route.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
import InventoriesList from './inventory-list.controller';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'inventories',
|
||||||
|
route: '/inventories',
|
||||||
|
templateUrl: templateUrl('inventories/inventories'),
|
||||||
|
controller: InventoriesList,
|
||||||
|
data: {
|
||||||
|
activityStream: true,
|
||||||
|
activityStreamTarget: 'inventory'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORIES"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
14
awx/ui/client/src/inventories/list/main.js
Normal file
14
awx/ui/client/src/inventories/list/main.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './inventory-list.route';
|
||||||
|
import controller from './inventory-list.controller';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('inventoryList', [])
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route);
|
||||||
|
}]);
|
||||||
18
awx/ui/client/src/inventories/main.js
Normal file
18
awx/ui/client/src/inventories/main.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import inventoryAdd from './add/main';
|
||||||
|
import inventoryEdit from './edit/main';
|
||||||
|
import inventoryList from './list/main';
|
||||||
|
import inventoryManage from './manage/main';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('inventory', [
|
||||||
|
inventoryAdd.name,
|
||||||
|
inventoryEdit.name,
|
||||||
|
inventoryList.name,
|
||||||
|
inventoryManage.name,
|
||||||
|
]);
|
||||||
@@ -0,0 +1,525 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name controllers.function:Inventories
|
||||||
|
* @description This controller's for the Inventory page
|
||||||
|
*/
|
||||||
|
|
||||||
|
function InventoriesManage($log, $scope, $rootScope, $location,
|
||||||
|
$state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert,
|
||||||
|
GetBasePath, ProcessErrors, InventoryGroups,
|
||||||
|
InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg,
|
||||||
|
GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate,
|
||||||
|
ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete,
|
||||||
|
EditInventoryProperties, ToggleHostEnabled, ShowJobSummary,
|
||||||
|
InventoryGroupsHelp, HelpDialog,
|
||||||
|
GroupsCopy, HostsCopy, $stateParams, ParamPass) {
|
||||||
|
|
||||||
|
var PreviousSearchParams,
|
||||||
|
url,
|
||||||
|
hostScope = $scope.$new();
|
||||||
|
|
||||||
|
ClearScope();
|
||||||
|
|
||||||
|
// TODO: only display adhoc button if the user has permission to use it.
|
||||||
|
// TODO: figure out how to get the action-list partial to update so that
|
||||||
|
// the tooltip can be changed based off things being selected or not.
|
||||||
|
$scope.adhocButtonTipContents = "Launch adhoc command for the inventory";
|
||||||
|
|
||||||
|
// watcher for the group list checkbox changes
|
||||||
|
$scope.$on('multiSelectList.selectionChanged', function(e, selection) {
|
||||||
|
if (selection.length > 0) {
|
||||||
|
$scope.groupsSelected = true;
|
||||||
|
// $scope.adhocButtonTipContents = "Launch adhoc command for the "
|
||||||
|
// + "selected groups and hosts.";
|
||||||
|
} else {
|
||||||
|
$scope.groupsSelected = false;
|
||||||
|
// $scope.adhocButtonTipContents = "Launch adhoc command for the "
|
||||||
|
// + "inventory.";
|
||||||
|
}
|
||||||
|
$scope.groupsSelectedItems = selection.selectedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
// watcher for the host list checkbox changes
|
||||||
|
hostScope.$on('multiSelectList.selectionChanged', function(e, selection) {
|
||||||
|
// you need this so that the event doesn't bubble to the watcher above
|
||||||
|
// for the host list
|
||||||
|
e.stopPropagation();
|
||||||
|
if (selection.length === 0) {
|
||||||
|
$scope.hostsSelected = false;
|
||||||
|
} else if (selection.length === 1) {
|
||||||
|
$scope.systemTrackingTooltip = "Compare host over time";
|
||||||
|
$scope.hostsSelected = true;
|
||||||
|
$scope.systemTrackingDisabled = false;
|
||||||
|
} else if (selection.length === 2) {
|
||||||
|
$scope.systemTrackingTooltip = "Compare hosts against each other";
|
||||||
|
$scope.hostsSelected = true;
|
||||||
|
$scope.systemTrackingDisabled = false;
|
||||||
|
} else {
|
||||||
|
$scope.hostsSelected = true;
|
||||||
|
$scope.systemTrackingDisabled = true;
|
||||||
|
}
|
||||||
|
$scope.hostsSelectedItems = selection.selectedItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.systemTracking = function() {
|
||||||
|
var hostIds = _.map($scope.hostsSelectedItems, function(x){
|
||||||
|
return x.id;
|
||||||
|
});
|
||||||
|
$state.transitionTo('systemTracking',
|
||||||
|
{ inventory: $scope.inventory,
|
||||||
|
inventoryId: $scope.inventory.id,
|
||||||
|
hosts: $scope.hostsSelectedItems,
|
||||||
|
hostIds: hostIds
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// populates host patterns based on selected hosts/groups
|
||||||
|
$scope.populateAdhocForm = function() {
|
||||||
|
var host_patterns = "all";
|
||||||
|
if ($scope.hostsSelected || $scope.groupsSelected) {
|
||||||
|
var allSelectedItems = [];
|
||||||
|
if ($scope.groupsSelectedItems) {
|
||||||
|
allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems);
|
||||||
|
}
|
||||||
|
if ($scope.hostsSelectedItems) {
|
||||||
|
allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems);
|
||||||
|
}
|
||||||
|
if (allSelectedItems) {
|
||||||
|
host_patterns = _.pluck(allSelectedItems, "name").join(":");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$rootScope.hostPatterns = host_patterns;
|
||||||
|
$state.go('inventoryManage.adhoc');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.refreshHostsOnGroupRefresh = false;
|
||||||
|
$scope.selected_group_id = null;
|
||||||
|
|
||||||
|
Wait('start');
|
||||||
|
|
||||||
|
|
||||||
|
if ($scope.removeHostReloadComplete) {
|
||||||
|
$scope.removeHostReloadComplete();
|
||||||
|
}
|
||||||
|
$scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() {
|
||||||
|
if ($scope.initial_height) {
|
||||||
|
var host_height = $('#hosts-container .well').height(),
|
||||||
|
group_height = $('#group-list-container .well').height(),
|
||||||
|
new_height;
|
||||||
|
|
||||||
|
if (host_height > group_height) {
|
||||||
|
new_height = host_height - (host_height - group_height);
|
||||||
|
}
|
||||||
|
else if (host_height < group_height) {
|
||||||
|
new_height = host_height + (group_height - host_height);
|
||||||
|
}
|
||||||
|
if (new_height) {
|
||||||
|
$('#hosts-container .well').height(new_height);
|
||||||
|
}
|
||||||
|
$scope.initial_height = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeRowCountReady) {
|
||||||
|
$scope.removeRowCountReady();
|
||||||
|
}
|
||||||
|
$scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) {
|
||||||
|
// Add hosts view
|
||||||
|
$scope.show_failures = false;
|
||||||
|
InjectHosts({
|
||||||
|
group_scope: $scope,
|
||||||
|
host_scope: hostScope,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
tree_id: null,
|
||||||
|
group_id: null,
|
||||||
|
pageSize: rows
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups });
|
||||||
|
PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows });
|
||||||
|
$scope.search(InventoryGroups.iterator, null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removeInventoryLoaded) {
|
||||||
|
$scope.removeInventoryLoaded();
|
||||||
|
}
|
||||||
|
$scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() {
|
||||||
|
var rows;
|
||||||
|
|
||||||
|
// Add groups view
|
||||||
|
generateList.inject(InventoryGroups, {
|
||||||
|
mode: 'edit',
|
||||||
|
id: 'group-list-container',
|
||||||
|
searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
|
||||||
|
scope: $scope
|
||||||
|
});
|
||||||
|
|
||||||
|
rows = 20;
|
||||||
|
hostScope.host_page_size = rows;
|
||||||
|
$scope.group_page_size = rows;
|
||||||
|
|
||||||
|
$scope.show_failures = false;
|
||||||
|
InjectHosts({
|
||||||
|
group_scope: $scope,
|
||||||
|
host_scope: hostScope,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
tree_id: null,
|
||||||
|
group_id: null,
|
||||||
|
pageSize: rows
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load data
|
||||||
|
SearchInit({
|
||||||
|
scope: $scope,
|
||||||
|
set: 'groups',
|
||||||
|
list: InventoryGroups,
|
||||||
|
url: $scope.inventory.related.root_groups
|
||||||
|
});
|
||||||
|
|
||||||
|
PaginateInit({
|
||||||
|
scope: $scope,
|
||||||
|
list: InventoryGroups ,
|
||||||
|
url: $scope.inventory.related.root_groups,
|
||||||
|
pageSize: rows
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.search(InventoryGroups.iterator, null, true);
|
||||||
|
|
||||||
|
$scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.removePostRefresh) {
|
||||||
|
$scope.removePostRefresh();
|
||||||
|
}
|
||||||
|
$scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) {
|
||||||
|
if (set === 'groups') {
|
||||||
|
$scope.groups.forEach( function(group, idx) {
|
||||||
|
var stat, hosts_status;
|
||||||
|
stat = GetSyncStatusMsg({
|
||||||
|
status: group.summary_fields.inventory_source.status,
|
||||||
|
has_inventory_sources: group.has_inventory_sources,
|
||||||
|
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
|
||||||
|
}); // from helpers/Groups.js
|
||||||
|
$scope.groups[idx].status_class = stat['class'];
|
||||||
|
$scope.groups[idx].status_tooltip = stat.tooltip;
|
||||||
|
$scope.groups[idx].launch_tooltip = stat.launch_tip;
|
||||||
|
$scope.groups[idx].launch_class = stat.launch_class;
|
||||||
|
hosts_status = GetHostsStatusMsg({
|
||||||
|
active_failures: group.hosts_with_active_failures,
|
||||||
|
total_hosts: group.total_hosts,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
group_id: group.id
|
||||||
|
}); // from helpers/Groups.js
|
||||||
|
$scope.groups[idx].hosts_status_tip = hosts_status.tooltip;
|
||||||
|
$scope.groups[idx].show_failures = hosts_status.failures;
|
||||||
|
$scope.groups[idx].hosts_status_class = hosts_status['class'];
|
||||||
|
|
||||||
|
$scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null;
|
||||||
|
$scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null;
|
||||||
|
|
||||||
|
});
|
||||||
|
if ($scope.refreshHostsOnGroupRefresh) {
|
||||||
|
$scope.refreshHostsOnGroupRefresh = false;
|
||||||
|
HostsReload({
|
||||||
|
scope: hostScope,
|
||||||
|
group_id: $scope.selected_group_id,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
pageSize: hostScope.host_page_size
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Wait('stop');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load Inventory
|
||||||
|
url = GetBasePath('inventory') + $stateParams.inventory_id + '/';
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function (data) {
|
||||||
|
$scope.inventory = data;
|
||||||
|
$scope.$emit('InventoryLoaded');
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id +
|
||||||
|
' GET returned status: ' + status });
|
||||||
|
});
|
||||||
|
|
||||||
|
// start watching for real-time updates
|
||||||
|
if ($rootScope.removeWatchUpdateStatus) {
|
||||||
|
$rootScope.removeWatchUpdateStatus();
|
||||||
|
}
|
||||||
|
$rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) {
|
||||||
|
var stat, group;
|
||||||
|
if (data.group_id) {
|
||||||
|
group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
|
||||||
|
if (data.status === "failed" || data.status === "successful") {
|
||||||
|
if (data.group_id === $scope.selected_group_id || group) {
|
||||||
|
// job completed, fefresh all groups
|
||||||
|
$log.debug('Update completed. Refreshing the tree.');
|
||||||
|
$scope.refreshGroups();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (group) {
|
||||||
|
// incremental update, just update
|
||||||
|
$log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status);
|
||||||
|
stat = GetSyncStatusMsg({
|
||||||
|
status: data.status,
|
||||||
|
has_inventory_sources: group.has_inventory_sources,
|
||||||
|
source: group.source
|
||||||
|
});
|
||||||
|
$log.debug('changing tooltip to: ' + stat.tooltip);
|
||||||
|
group.status = data.status;
|
||||||
|
group.status_class = stat['class'];
|
||||||
|
group.status_tooltip = stat.tooltip;
|
||||||
|
group.launch_tooltip = stat.launch_tip;
|
||||||
|
group.launch_class = stat.launch_class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load group on selection
|
||||||
|
function loadGroups(url) {
|
||||||
|
SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url });
|
||||||
|
PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size });
|
||||||
|
$scope.search(InventoryGroups.iterator, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.refreshHosts = function() {
|
||||||
|
HostsReload({
|
||||||
|
scope: hostScope,
|
||||||
|
group_id: $scope.selected_group_id,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
pageSize: hostScope.host_page_size
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.refreshGroups = function() {
|
||||||
|
$scope.refreshHostsOnGroupRefresh = true;
|
||||||
|
$scope.search(InventoryGroups.iterator, null, true, false, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.restoreSearch = function() {
|
||||||
|
// Restore search params and related stuff, plus refresh
|
||||||
|
// groups and hosts lists
|
||||||
|
SearchInit({
|
||||||
|
scope: $scope,
|
||||||
|
set: PreviousSearchParams.set,
|
||||||
|
list: PreviousSearchParams.list,
|
||||||
|
url: PreviousSearchParams.defaultUrl,
|
||||||
|
iterator: PreviousSearchParams.iterator,
|
||||||
|
sort_order: PreviousSearchParams.sort_order,
|
||||||
|
setWidgets: false
|
||||||
|
});
|
||||||
|
$scope.refreshHostsOnGroupRefresh = true;
|
||||||
|
$scope.search(InventoryGroups.iterator, null, true, false, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.groupSelect = function(id) {
|
||||||
|
var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id });
|
||||||
|
if($state.params.groups){
|
||||||
|
groups.push($state.params.groups);
|
||||||
|
}
|
||||||
|
groups.push(group.id);
|
||||||
|
groups = groups.join();
|
||||||
|
$state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false });
|
||||||
|
loadGroups(group.related.children, group.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.createGroup = function () {
|
||||||
|
PreviousSearchParams = Store('group_current_search_params');
|
||||||
|
var params = {
|
||||||
|
scope: $scope,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
group_id: $scope.selected_group_id,
|
||||||
|
mode: 'add'
|
||||||
|
}
|
||||||
|
ParamPass.set(params);
|
||||||
|
$state.go('inventoryManage.addGroup');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editGroup = function (id) {
|
||||||
|
PreviousSearchParams = Store('group_current_search_params');
|
||||||
|
var params = {
|
||||||
|
scope: $scope,
|
||||||
|
inventory_id: $scope.inventory.id,
|
||||||
|
group_id: id,
|
||||||
|
mode: 'edit'
|
||||||
|
}
|
||||||
|
ParamPass.set(params);
|
||||||
|
$state.go('inventoryManage.editGroup', {group_id: id});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Launch inventory sync
|
||||||
|
$scope.updateGroup = function (id) {
|
||||||
|
var group = Find({ list: $scope.groups, key: 'id', val: id });
|
||||||
|
if (group) {
|
||||||
|
if (Empty(group.source)) {
|
||||||
|
// if no source, do nothing.
|
||||||
|
} else if (group.status === 'updating') {
|
||||||
|
Alert('Update in Progress', 'The inventory update process is currently running for group <em>' +
|
||||||
|
group.name + '</em> Click the <i class="fa fa-refresh"></i> button to monitor the status.', 'alert-info', null, null, null, null, true);
|
||||||
|
} else {
|
||||||
|
Wait('start');
|
||||||
|
Rest.setUrl(group.related.inventory_source);
|
||||||
|
Rest.get()
|
||||||
|
.success(function (data) {
|
||||||
|
InventoryUpdate({
|
||||||
|
scope: $scope,
|
||||||
|
url: data.related.update,
|
||||||
|
group_name: data.summary_fields.group.name,
|
||||||
|
group_source: data.source,
|
||||||
|
group_id: group.id,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
|
||||||
|
group.related.inventory_source + ' GET returned status: ' + status });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancelUpdate = function (id) {
|
||||||
|
GroupsCancelUpdate({ scope: $scope, id: id });
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.viewUpdateStatus = function (id) {
|
||||||
|
ViewUpdateStatus({
|
||||||
|
scope: $scope,
|
||||||
|
group_id: id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.copyGroup = function(id) {
|
||||||
|
PreviousSearchParams = Store('group_current_search_params');
|
||||||
|
GroupsCopy({
|
||||||
|
scope: $scope,
|
||||||
|
group_id: id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteGroup = function (id) {
|
||||||
|
GroupsDelete({
|
||||||
|
scope: $scope,
|
||||||
|
group_id: id,
|
||||||
|
inventory_id: $scope.inventory.id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editInventoryProperties = function () {
|
||||||
|
// EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id });
|
||||||
|
$location.path('/inventories/' + $scope.inventory.id + '/');
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.createHost = function () {
|
||||||
|
var params = {
|
||||||
|
host_scope: hostScope,
|
||||||
|
group_scope: $scope,
|
||||||
|
mode: 'add',
|
||||||
|
host_id: null,
|
||||||
|
selected_group_id: $scope.selected_group_id,
|
||||||
|
inventory_id: $scope.inventory.id
|
||||||
|
}
|
||||||
|
ParamPass.set(params);
|
||||||
|
$state.go('inventoryManage.addHost');
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.editHost = function (host_id) {
|
||||||
|
var params = {
|
||||||
|
host_scope: hostScope,
|
||||||
|
group_scope: $scope,
|
||||||
|
mode: 'edit',
|
||||||
|
host_id: host_id,
|
||||||
|
inventory_id: $scope.inventory.id
|
||||||
|
}
|
||||||
|
ParamPass.set(params);
|
||||||
|
$state.go('inventoryManage.editHost', {host_id: host_id});
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.deleteHost = function (host_id, host_name) {
|
||||||
|
HostsDelete({
|
||||||
|
parent_scope: $scope,
|
||||||
|
host_scope: hostScope,
|
||||||
|
host_id: host_id,
|
||||||
|
host_name: host_name
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.copyHost = function(id) {
|
||||||
|
PreviousSearchParams = Store('group_current_search_params');
|
||||||
|
HostsCopy({
|
||||||
|
group_scope: $scope,
|
||||||
|
host_scope: hostScope,
|
||||||
|
host_id: id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.toggleHostEnabled = function (host_id, external_source) {
|
||||||
|
ToggleHostEnabled({
|
||||||
|
parent_scope: $scope,
|
||||||
|
host_scope: hostScope,
|
||||||
|
host_id: host_id,
|
||||||
|
external_source: external_source
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
hostScope.showJobSummary = function (job_id) {
|
||||||
|
ShowJobSummary({
|
||||||
|
job_id: job_id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showGroupHelp = function (params) {
|
||||||
|
var opts = {
|
||||||
|
defn: InventoryGroupsHelp
|
||||||
|
};
|
||||||
|
if (params) {
|
||||||
|
opts.autoShow = params.autoShow || false;
|
||||||
|
}
|
||||||
|
HelpDialog(opts);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
$scope.showHosts = function (group_id, show_failures) {
|
||||||
|
// Clicked on group
|
||||||
|
if (group_id !== null) {
|
||||||
|
Wait('start');
|
||||||
|
hostScope.show_failures = show_failures;
|
||||||
|
$scope.groupSelect(group_id);
|
||||||
|
hostScope.hosts = [];
|
||||||
|
$scope.show_failures = show_failures; // turn on failed hosts
|
||||||
|
// filter in hosts view
|
||||||
|
} else {
|
||||||
|
Wait('stop');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($scope.removeGroupDeleteCompleted) {
|
||||||
|
$scope.removeGroupDeleteCompleted();
|
||||||
|
}
|
||||||
|
$scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted',
|
||||||
|
function() {
|
||||||
|
$scope.refreshGroups();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
'$log', '$scope', '$rootScope', '$location',
|
||||||
|
'$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait',
|
||||||
|
'Rest', 'Alert', 'GetBasePath', 'ProcessErrors',
|
||||||
|
'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload',
|
||||||
|
'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg',
|
||||||
|
'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus',
|
||||||
|
'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete',
|
||||||
|
'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary',
|
||||||
|
'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy',
|
||||||
|
'HostsCopy', '$stateParams', 'ParamPass', InventoriesManage,
|
||||||
|
];
|
||||||
@@ -10,9 +10,6 @@
|
|||||||
<div id="host-list-container" class="Panel"></div>
|
<div id="host-list-container" class="Panel"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="inventory-modal-container"></div>
|
|
||||||
|
|
||||||
<div id="group-copy-dialog" style="display: none;">
|
<div id="group-copy-dialog" style="display: none;">
|
||||||
<div id="copy-group-radio-container" class="well">
|
<div id="copy-group-radio-container" class="well">
|
||||||
<div class="title"><span class="highlight">1.</span> Copy or move <span ng-bind="name"></span>?</div>
|
<div class="title"><span class="highlight">1.</span> Copy or move <span ng-bind="name"></span>?</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
import InventoriesManage from './inventory-manage.controller';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'inventoryManage',
|
||||||
|
url: '/inventories/:inventory_id/manage?groups',
|
||||||
|
templateUrl: templateUrl('inventories/manage/inventory-manage'),
|
||||||
|
controller: InventoriesManage,
|
||||||
|
data: {
|
||||||
|
activityStream: true,
|
||||||
|
activityStreamTarget: 'inventory',
|
||||||
|
activityStreamId: 'inventory_id'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY MANAGE"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
19
awx/ui/client/src/inventories/manage/main.js
Normal file
19
awx/ui/client/src/inventories/manage/main.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './inventory-manage.route';
|
||||||
|
|
||||||
|
import manageHosts from './manage-hosts/main';
|
||||||
|
import manageGroups from './manage-groups/main';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('inventoryManage', [
|
||||||
|
manageHosts.name,
|
||||||
|
manageGroups.name
|
||||||
|
])
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route);
|
||||||
|
}]);
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
function manageGroupsDirectiveController($filter, $rootScope, $location, $log, $stateParams, $compile, $state, $scope, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
|
||||||
|
GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait,
|
||||||
|
GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString, ToJSON, GroupsScheduleListInit,
|
||||||
|
SourceForm, SetSchedulesInnerDialogSize, CreateSelect2, ParamPass) {
|
||||||
|
|
||||||
|
var vm = this;
|
||||||
|
var params = ParamPass.get();
|
||||||
|
if(params === undefined) {
|
||||||
|
params = {};
|
||||||
|
params.scope = $scope.$new();
|
||||||
|
}
|
||||||
|
var parent_scope = params.scope,
|
||||||
|
group_id = $stateParams.group_id,
|
||||||
|
mode = $state.current.data.mode, // 'add' or 'edit'
|
||||||
|
inventory_id = $stateParams.inventory_id,
|
||||||
|
generator = GenerateForm,
|
||||||
|
group_created = false,
|
||||||
|
defaultUrl,
|
||||||
|
master = {},
|
||||||
|
choicesReady,
|
||||||
|
modal_scope = parent_scope.$new(),
|
||||||
|
properties_scope = parent_scope.$new(),
|
||||||
|
sources_scope = parent_scope.$new(),
|
||||||
|
elem, group,
|
||||||
|
schedules_url = '';
|
||||||
|
|
||||||
|
if (mode === 'edit') {
|
||||||
|
defaultUrl = GetBasePath('groups') + group_id + '/';
|
||||||
|
} else {
|
||||||
|
defaultUrl = (group_id !== undefined) ? GetBasePath('groups') + group_id + '/children/' :
|
||||||
|
GetBasePath('inventory') + inventory_id + '/groups/';
|
||||||
|
}
|
||||||
|
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
group = data;
|
||||||
|
for (var fld in GroupForm.fields) {
|
||||||
|
if (data[fld]) {
|
||||||
|
properties_scope[fld] = data[fld];
|
||||||
|
master[fld] = properties_scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(mode === 'edit') {
|
||||||
|
schedules_url = data.related.inventory_source + 'schedules/';
|
||||||
|
properties_scope.variable_url = data.related.variable_data;
|
||||||
|
sources_scope.source_url = data.related.inventory_source;
|
||||||
|
modal_scope.$emit('LoadSourceData');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors(modal_scope, data, status, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('#properties-tab').empty();
|
||||||
|
$('#sources-tab').empty();
|
||||||
|
|
||||||
|
elem = document.getElementById('group-manage-panel');
|
||||||
|
$compile(elem)(modal_scope);
|
||||||
|
|
||||||
|
$scope.parseType = 'yaml';
|
||||||
|
|
||||||
|
var form_scope =
|
||||||
|
generator.inject(GroupForm, {
|
||||||
|
mode: mode,
|
||||||
|
id: 'properties-tab',
|
||||||
|
related: false,
|
||||||
|
scope: properties_scope,
|
||||||
|
cancelButton: false,
|
||||||
|
});
|
||||||
|
var source_form_scope =
|
||||||
|
generator.inject(SourceForm, {
|
||||||
|
mode: mode,
|
||||||
|
id: 'sources-tab',
|
||||||
|
related: false,
|
||||||
|
scope: sources_scope,
|
||||||
|
cancelButton: false
|
||||||
|
});
|
||||||
|
|
||||||
|
generator.reset();
|
||||||
|
|
||||||
|
GetSourceTypeOptions({
|
||||||
|
scope: sources_scope,
|
||||||
|
variable: 'source_type_options'
|
||||||
|
});
|
||||||
|
sources_scope.source = SourceForm.fields.source['default'];
|
||||||
|
sources_scope.sourcePathRequired = false;
|
||||||
|
sources_scope[SourceForm.fields.source_vars.parseTypeName] = 'yaml';
|
||||||
|
sources_scope.update_cache_timeout = 0;
|
||||||
|
properties_scope.parseType = 'yaml';
|
||||||
|
|
||||||
|
function waitStop() {
|
||||||
|
Wait('stop');
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSourceChange() {
|
||||||
|
parent_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value !== "manual") ? true : false;
|
||||||
|
SourceChange({
|
||||||
|
scope: sources_scope,
|
||||||
|
form: SourceForm
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// JT -- this gets called after the properties & properties variables are loaded, and is emitted from (groupLoaded)
|
||||||
|
if (modal_scope.removeLoadSourceData) {
|
||||||
|
modal_scope.removeLoadSourceData();
|
||||||
|
}
|
||||||
|
modal_scope.removeLoadSourceData = modal_scope.$on('LoadSourceData', function() {
|
||||||
|
ParseTypeChange({
|
||||||
|
scope: form_scope,
|
||||||
|
variable: 'variables',
|
||||||
|
parse_variable: 'parseType',
|
||||||
|
field_id: 'group_variables'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sources_scope.source_url) {
|
||||||
|
// get source data
|
||||||
|
Rest.setUrl(sources_scope.source_url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
var fld, i, j, flag, found, set, opts, list, form;
|
||||||
|
form = SourceForm;
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (fld === 'checkbox_group') {
|
||||||
|
for (i = 0; i < form.fields[fld].fields.length; i++) {
|
||||||
|
flag = form.fields[fld].fields[i];
|
||||||
|
if (data[flag.name] !== undefined) {
|
||||||
|
sources_scope[flag.name] = data[flag.name];
|
||||||
|
master[flag.name] = sources_scope[flag.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fld === 'source') {
|
||||||
|
found = false;
|
||||||
|
data.source = (data.source === "") ? "manual" : data.source;
|
||||||
|
for (i = 0; i < sources_scope.source_type_options.length; i++) {
|
||||||
|
if (sources_scope.source_type_options[i].value === data.source) {
|
||||||
|
sources_scope.source = sources_scope.source_type_options[i];
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found || sources_scope.source.value === "manual") {
|
||||||
|
sources_scope.groupUpdateHide = true;
|
||||||
|
} else {
|
||||||
|
sources_scope.groupUpdateHide = false;
|
||||||
|
}
|
||||||
|
master.source = sources_scope.source;
|
||||||
|
} else if (fld === 'source_vars') {
|
||||||
|
// Parse source_vars, converting to YAML.
|
||||||
|
sources_scope.source_vars = ParseVariableString(data.source_vars);
|
||||||
|
master.source_vars = sources_scope.variables;
|
||||||
|
} else if (fld === "inventory_script") {
|
||||||
|
// the API stores it as 'source_script', we call it inventory_script
|
||||||
|
data.summary_fields['inventory_script'] = data.summary_fields.source_script;
|
||||||
|
sources_scope.inventory_script = data.source_script;
|
||||||
|
master.inventory_script = sources_scope.inventory_script;
|
||||||
|
} else if (fld === "source_regions") {
|
||||||
|
if (data[fld] === "") {
|
||||||
|
sources_scope[fld] = data[fld];
|
||||||
|
master[fld] = sources_scope[fld];
|
||||||
|
} else {
|
||||||
|
sources_scope[fld] = data[fld].split(",");
|
||||||
|
master[fld] = sources_scope[fld];
|
||||||
|
}
|
||||||
|
} else if (data[fld] !== undefined) {
|
||||||
|
sources_scope[fld] = data[fld];
|
||||||
|
master[fld] = sources_scope[fld];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.fields[fld].sourceModel && data.summary_fields &&
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel]) {
|
||||||
|
sources_scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
||||||
|
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initSourceChange();
|
||||||
|
|
||||||
|
if (data.source_regions) {
|
||||||
|
if (data.source === 'ec2' ||
|
||||||
|
data.source === 'rax' ||
|
||||||
|
data.source === 'gce' ||
|
||||||
|
data.source === 'azure') {
|
||||||
|
if (data.source === 'ec2') {
|
||||||
|
set = sources_scope.ec2_regions;
|
||||||
|
} else if (data.source === 'rax') {
|
||||||
|
set = sources_scope.rax_regions;
|
||||||
|
} else if (data.source === 'gce') {
|
||||||
|
set = sources_scope.gce_regions;
|
||||||
|
} else if (data.source === 'azure') {
|
||||||
|
set = sources_scope.azure_regions;
|
||||||
|
}
|
||||||
|
opts = [];
|
||||||
|
list = data.source_regions.split(',');
|
||||||
|
for (i = 0; i < list.length; i++) {
|
||||||
|
for (j = 0; j < set.length; j++) {
|
||||||
|
if (list[i] === set[j].value) {
|
||||||
|
opts.push({
|
||||||
|
id: set [j].value,
|
||||||
|
text: set [j].label
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
master.source_regions = opts;
|
||||||
|
CreateSelect2({
|
||||||
|
element: "#source_source_regions",
|
||||||
|
opts: opts
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If empty, default to all
|
||||||
|
master.source_regions = [{
|
||||||
|
id: 'all',
|
||||||
|
text: 'All'
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
if (data.group_by && data.source === 'ec2') {
|
||||||
|
set = sources_scope.ec2_group_by;
|
||||||
|
opts = [];
|
||||||
|
list = data.group_by.split(',');
|
||||||
|
for (i = 0; i < list.length; i++) {
|
||||||
|
for (j = 0; j < set.length; j++) {
|
||||||
|
if (list[i] === set[j].value) {
|
||||||
|
opts.push({
|
||||||
|
id: set [j].value,
|
||||||
|
text: set [j].label
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
master.group_by = opts;
|
||||||
|
CreateSelect2({
|
||||||
|
element: "#source_group_by",
|
||||||
|
opts: opts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sources_scope.group_update_url = data.related.update;
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
sources_scope.source = "";
|
||||||
|
ProcessErrors(modal_scope, data, status, null, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to retrieve inventory source. GET status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sources_scope.removeScopeSourceTypeOptionsReady) {
|
||||||
|
sources_scope.removeScopeSourceTypeOptionsReady();
|
||||||
|
}
|
||||||
|
sources_scope.removeScopeSourceTypeOptionsReady = sources_scope.$on('sourceTypeOptionsReady', function() {
|
||||||
|
if (mode === 'add') {
|
||||||
|
sources_scope.source = Find({
|
||||||
|
list: sources_scope.source_type_options,
|
||||||
|
key: 'value',
|
||||||
|
val: ''
|
||||||
|
});
|
||||||
|
modal_scope.showSchedulesTab = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
choicesReady = 0;
|
||||||
|
|
||||||
|
if (sources_scope.removeChoicesReady) {
|
||||||
|
sources_scope.removeChoicesReady();
|
||||||
|
}
|
||||||
|
sources_scope.removeChoicesReady = sources_scope.$on('choicesReadyGroup', function() {
|
||||||
|
CreateSelect2({
|
||||||
|
element: '#source_source',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
modal_scope.$emit('LoadSourceData');
|
||||||
|
|
||||||
|
choicesReady++;
|
||||||
|
if (choicesReady === 5) {
|
||||||
|
if (mode !== 'edit') {
|
||||||
|
properties_scope.variables = "---";
|
||||||
|
master.variables = properties_scope.variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load options for source regions
|
||||||
|
GetChoices({
|
||||||
|
scope: sources_scope,
|
||||||
|
url: GetBasePath('inventory_sources'),
|
||||||
|
field: 'source_regions',
|
||||||
|
variable: 'rax_regions',
|
||||||
|
choice_name: 'rax_region_choices',
|
||||||
|
callback: 'choicesReadyGroup'
|
||||||
|
});
|
||||||
|
|
||||||
|
GetChoices({
|
||||||
|
scope: sources_scope,
|
||||||
|
url: GetBasePath('inventory_sources'),
|
||||||
|
field: 'source_regions',
|
||||||
|
variable: 'ec2_regions',
|
||||||
|
choice_name: 'ec2_region_choices',
|
||||||
|
callback: 'choicesReadyGroup'
|
||||||
|
});
|
||||||
|
|
||||||
|
GetChoices({
|
||||||
|
scope: sources_scope,
|
||||||
|
url: GetBasePath('inventory_sources'),
|
||||||
|
field: 'source_regions',
|
||||||
|
variable: 'gce_regions',
|
||||||
|
choice_name: 'gce_region_choices',
|
||||||
|
callback: 'choicesReadyGroup'
|
||||||
|
});
|
||||||
|
|
||||||
|
GetChoices({
|
||||||
|
scope: sources_scope,
|
||||||
|
url: GetBasePath('inventory_sources'),
|
||||||
|
field: 'source_regions',
|
||||||
|
variable: 'azure_regions',
|
||||||
|
choice_name: 'azure_region_choices',
|
||||||
|
callback: 'choicesReadyGroup'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load options for group_by
|
||||||
|
GetChoices({
|
||||||
|
scope: sources_scope,
|
||||||
|
url: GetBasePath('inventory_sources'),
|
||||||
|
field: 'group_by',
|
||||||
|
variable: 'ec2_group_by',
|
||||||
|
choice_name: 'ec2_group_by_choices',
|
||||||
|
callback: 'choicesReadyGroup'
|
||||||
|
});
|
||||||
|
|
||||||
|
//Wait('start');
|
||||||
|
|
||||||
|
if (parent_scope.removeAddTreeRefreshed) {
|
||||||
|
parent_scope.removeAddTreeRefreshed();
|
||||||
|
}
|
||||||
|
parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() {
|
||||||
|
// Clean up
|
||||||
|
Wait('stop');
|
||||||
|
|
||||||
|
if (modal_scope.searchCleanUp) {
|
||||||
|
modal_scope.searchCleanup();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//$('#group-modal-dialog').dialog('close');
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modal_scope.removeSaveComplete) {
|
||||||
|
modal_scope.removeSaveComplete();
|
||||||
|
}
|
||||||
|
modal_scope.removeSaveComplete = modal_scope.$on('SaveComplete', function(e, error) {
|
||||||
|
if (!error) {
|
||||||
|
modal_scope.cancelPanel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modal_scope.removeFormSaveSuccess) {
|
||||||
|
modal_scope.removeFormSaveSuccess();
|
||||||
|
}
|
||||||
|
modal_scope.removeFormSaveSuccess = modal_scope.$on('formSaveSuccess', function() {
|
||||||
|
|
||||||
|
// Source data gets stored separately from the group. Validate and store Source
|
||||||
|
// related fields, then call SaveComplete to wrap things up.
|
||||||
|
|
||||||
|
var parseError = false,
|
||||||
|
regions, r, i,
|
||||||
|
group_by,
|
||||||
|
data = {
|
||||||
|
group: group_id,
|
||||||
|
source: ((sources_scope.source && sources_scope.source.value !== 'manual') ? sources_scope.source.value : ''),
|
||||||
|
source_path: sources_scope.source_path,
|
||||||
|
credential: sources_scope.credential,
|
||||||
|
overwrite: sources_scope.overwrite,
|
||||||
|
overwrite_vars: sources_scope.overwrite_vars,
|
||||||
|
source_script: sources_scope.inventory_script,
|
||||||
|
update_on_launch: sources_scope.update_on_launch,
|
||||||
|
update_cache_timeout: (sources_scope.update_cache_timeout || 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a string out of selected list of regions
|
||||||
|
if (sources_scope.source_regions) {
|
||||||
|
regions = $('#source_source_regions').select2("data");
|
||||||
|
r = [];
|
||||||
|
for (i = 0; i < regions.length; i++) {
|
||||||
|
r.push(regions[i].id);
|
||||||
|
}
|
||||||
|
data.source_regions = r.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sources_scope.source && (sources_scope.source.value === 'ec2')) {
|
||||||
|
data.instance_filters = sources_scope.instance_filters;
|
||||||
|
// Create a string out of selected list of regions
|
||||||
|
group_by = $('#source_group_by').select2("data");
|
||||||
|
r = [];
|
||||||
|
for (i = 0; i < group_by.length; i++) {
|
||||||
|
r.push(group_by[i].id);
|
||||||
|
}
|
||||||
|
data.group_by = r.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 && (sources_scope.source.value === 'vmware' ||
|
||||||
|
sources_scope.source.value === 'openstack')) {
|
||||||
|
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the API doesn't expect the credential to be passed with a custom inv script
|
||||||
|
if (sources_scope.source && sources_scope.source.value === 'custom') {
|
||||||
|
delete(data.credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parseError) {
|
||||||
|
Rest.setUrl(sources_scope.source_url);
|
||||||
|
Rest.put(data)
|
||||||
|
.success(function() {
|
||||||
|
modal_scope.$emit('SaveComplete', false);
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
$('#group_tabs a:eq(1)').tab('show');
|
||||||
|
ProcessErrors(sources_scope, data, status, SourceForm, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to update group inventory source. PUT status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
modal_scope.cancelPanel = function() {
|
||||||
|
Wait('stop');
|
||||||
|
$state.go('inventoryManage', {}, {reload: true})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save
|
||||||
|
modal_scope.saveGroup = function() {
|
||||||
|
Wait('start');
|
||||||
|
var fld, data, json_data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
json_data = ToJSON(properties_scope.parseType, properties_scope.variables, true);
|
||||||
|
|
||||||
|
data = {};
|
||||||
|
for (fld in GroupForm.fields) {
|
||||||
|
data[fld] = properties_scope[fld];
|
||||||
|
}
|
||||||
|
|
||||||
|
data.inventory = inventory_id;
|
||||||
|
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
if (mode === 'edit' || (mode === 'add' && group_created)) {
|
||||||
|
Rest.put(data)
|
||||||
|
.success(function() {
|
||||||
|
modal_scope.$emit('formSaveSuccess');
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
$('#group_tabs a:eq(0)').tab('show');
|
||||||
|
ProcessErrors(properties_scope, data, status, GroupForm, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to update group: ' + group_id + '. PUT status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Rest.post(data)
|
||||||
|
.success(function(data) {
|
||||||
|
group_created = true;
|
||||||
|
group_id = data.id;
|
||||||
|
sources_scope.source_url = data.related.inventory_source;
|
||||||
|
modal_scope.$emit('formSaveSuccess');
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
$('#group_tabs a:eq(0)').tab('show');
|
||||||
|
ProcessErrors(properties_scope, data, status, GroupForm, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to create group: ' + group_id + '. POST status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore. ToJSON will have already alerted the user
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the update process
|
||||||
|
modal_scope.updateGroup = function() {
|
||||||
|
if (sources_scope.source === "manual" || sources_scope.source === null) {
|
||||||
|
Alert('Missing Configuration', 'The selected group is not configured for updates. You must first edit the group, provide Source settings, ' +
|
||||||
|
'and then run an update.', 'alert-info');
|
||||||
|
} else if (sources_scope.status === 'updating') {
|
||||||
|
Alert('Update in Progress', 'The inventory update process is currently running for group <em>' +
|
||||||
|
$filter('sanitize')(sources_scope.summary_fields.group.name) + '</em>. Use the Refresh button to monitor the status.', 'alert-info', null, null, null, null, true);
|
||||||
|
} else {
|
||||||
|
InventoryUpdate({
|
||||||
|
scope: parent_scope,
|
||||||
|
group_id: group_id,
|
||||||
|
url: properties_scope.group_update_url,
|
||||||
|
group_name: properties_scope.name,
|
||||||
|
group_source: sources_scope.source.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change the lookup and regions when the source changes
|
||||||
|
sources_scope.sourceChange = function() {
|
||||||
|
sources_scope.credential_name = "";
|
||||||
|
sources_scope.credential = "";
|
||||||
|
if (sources_scope.credential_name_api_error) {
|
||||||
|
delete sources_scope.credential_name_api_error;
|
||||||
|
}
|
||||||
|
initSourceChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
angular.extend(vm, {
|
||||||
|
cancelPanel : modal_scope.cancelPanel,
|
||||||
|
saveGroup: modal_scope.saveGroup
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ['$filter', '$rootScope', '$location', '$log', '$stateParams', '$compile', '$state', '$scope', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
|
||||||
|
'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate',
|
||||||
|
'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find',
|
||||||
|
'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', 'CreateSelect2', 'ParamPass',
|
||||||
|
manageGroupsDirectiveController
|
||||||
|
];
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/* jshint unused: vars */
|
||||||
|
import manageGroupsDirectiveController from './manage-groups.directive.controller';
|
||||||
|
|
||||||
|
export default ['templateUrl', 'ParamPass',
|
||||||
|
function(templateUrl, ParamPass) {
|
||||||
|
return {
|
||||||
|
restrict: 'EA',
|
||||||
|
scope: true,
|
||||||
|
replace: true,
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-groups/directive/manage-groups.directive'),
|
||||||
|
link: function(scope, element, attrs) {
|
||||||
|
|
||||||
|
},
|
||||||
|
controller: manageGroupsDirectiveController,
|
||||||
|
controllerAs: 'vm',
|
||||||
|
bindToController: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div>
|
||||||
|
<div class="Form-exitHolder">
|
||||||
|
<button class="Form-exit" ng-click="vm.cancelPanel()">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="group-manage-panel">
|
||||||
|
<div id="properties-tab"></div>
|
||||||
|
<div id="sources-tab"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
|
||||||
|
<div class="ui-dialog-buttonset">
|
||||||
|
<button type="button" class="btn btn-primary Form-saveButton" id="Inventory-groupManage--okButton" ng-click="vm.saveGroup()">
|
||||||
|
Save</button>
|
||||||
|
<button type="button" class="btn btn-default Form-cancelButton" id="Inventory-groupManage--cancelButton" ng-click="vm.cancelPanel()">
|
||||||
|
Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
16
awx/ui/client/src/inventories/manage/manage-groups/main.js
Normal file
16
awx/ui/client/src/inventories/manage/manage-groups/main.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './manage-groups.route';
|
||||||
|
import manageGroupsDirective from './directive/manage-groups.directive';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('manage-groups', [])
|
||||||
|
.directive('manageGroups', manageGroupsDirective)
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route.edit);
|
||||||
|
$stateExtender.addState(route.add);
|
||||||
|
}]);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="tab-pane" id="Inventory-groupManage">
|
||||||
|
<div ng-cloak id="Inventory-groupManage--panel" class="Panel">
|
||||||
|
<manage-groups></manage-groups>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
import {
|
||||||
|
templateUrl
|
||||||
|
} from '../../../shared/template-url/template-url.factory';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
edit: {
|
||||||
|
name: 'inventoryManage.editGroup',
|
||||||
|
route: '/:group_id/editGroup',
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
|
||||||
|
data: {
|
||||||
|
group_id: 'group_id',
|
||||||
|
mode: 'edit'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY EDIT GROUPS"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
add: {
|
||||||
|
name: 'inventoryManage.addGroup',
|
||||||
|
route: '/addGroup',
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY ADD GROUP"
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
mode: 'add'
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
function manageHostsDirectiveController($rootScope, $location, $log, $stateParams, $state, $scope, Rest, Alert, HostForm,
|
||||||
|
GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait,
|
||||||
|
Find, SetStatus, ApplyEllipsis, ToJSON, ParseVariableString, CreateDialog, TextareaResize, ParamPass) {
|
||||||
|
|
||||||
|
var vm = this;
|
||||||
|
|
||||||
|
var params = ParamPass.get();
|
||||||
|
if(params === undefined) {
|
||||||
|
params = {};
|
||||||
|
params.host_scope = $scope.$new();
|
||||||
|
params.group_scope = $scope.$new();
|
||||||
|
}
|
||||||
|
var parent_scope = params.host_scope,
|
||||||
|
group_scope = params.group_scope,
|
||||||
|
inventory_id = $stateParams.inventory_id,
|
||||||
|
mode = $state.current.data.mode, // 'add' or 'edit'
|
||||||
|
selected_group_id = params.selected_group_id,
|
||||||
|
generator = GenerateForm,
|
||||||
|
form = HostForm,
|
||||||
|
defaultUrl,
|
||||||
|
scope = parent_scope.$new(),
|
||||||
|
master = {},
|
||||||
|
relatedSets = {},
|
||||||
|
url, form_scope;
|
||||||
|
|
||||||
|
var host_id = $stateParams.host_id || undefined;
|
||||||
|
|
||||||
|
form_scope =
|
||||||
|
generator.inject(HostForm, {
|
||||||
|
mode: 'edit',
|
||||||
|
id: 'host-panel-form',
|
||||||
|
related: false,
|
||||||
|
scope: scope,
|
||||||
|
cancelButton: false
|
||||||
|
});
|
||||||
|
generator.reset();
|
||||||
|
console.info(angular.element(document.getElementById('host_variables')));
|
||||||
|
|
||||||
|
$scope.parseType = 'yaml';
|
||||||
|
ParseTypeChange({
|
||||||
|
scope: form_scope,
|
||||||
|
variable: 'variables',
|
||||||
|
parse_variable: 'parseType',
|
||||||
|
field_id: 'host_variables'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Retrieve detail record and prepopulate the form
|
||||||
|
if (mode === 'edit') {
|
||||||
|
defaultUrl = GetBasePath('hosts') + host_id + '/';
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
var set, fld, related;
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (data[fld]) {
|
||||||
|
scope[fld] = data[fld];
|
||||||
|
master[fld] = scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
related = data.related;
|
||||||
|
for (set in form.related) {
|
||||||
|
if (related[set]) {
|
||||||
|
relatedSets[set] = {
|
||||||
|
url: related[set],
|
||||||
|
iterator: form.related[set].iterator
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.variable_url = data.related.variable_data;
|
||||||
|
scope.has_inventory_sources = data.has_inventory_sources;
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors(parent_scope, data, status, form, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (selected_group_id) {
|
||||||
|
// adding hosts to a group
|
||||||
|
url = GetBasePath('groups') + selected_group_id + '/';
|
||||||
|
} else {
|
||||||
|
// adding hosts to the top-level (inventory)
|
||||||
|
url = GetBasePath('inventory') + inventory_id + '/';
|
||||||
|
}
|
||||||
|
// Add mode
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.get()
|
||||||
|
.success(function(data) {
|
||||||
|
scope.has_inventory_sources = data.has_inventory_sources;
|
||||||
|
scope.enabled = true;
|
||||||
|
scope.variables = '---';
|
||||||
|
defaultUrl = data.related.hosts;
|
||||||
|
//scope.$emit('hostVariablesLoaded');
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors(parent_scope, data, status, form, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope.removeSaveCompleted) {
|
||||||
|
scope.removeSaveCompleted();
|
||||||
|
}
|
||||||
|
scope.removeSaveCompleted = scope.$on('saveCompleted', function() {
|
||||||
|
Wait('stop');
|
||||||
|
try {
|
||||||
|
$('#host-modal-dialog').dialog('close');
|
||||||
|
} catch (err) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
if (group_scope && group_scope.refreshHosts) {
|
||||||
|
group_scope.refreshHosts();
|
||||||
|
}
|
||||||
|
if (parent_scope.refreshHosts) {
|
||||||
|
parent_scope.refreshHosts();
|
||||||
|
}
|
||||||
|
scope.$destroy();
|
||||||
|
$state.go('inventoryManage', {}, {
|
||||||
|
reload: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save changes to the parent
|
||||||
|
var saveHost = function() {
|
||||||
|
Wait('start');
|
||||||
|
var fld, data = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
data.variables = ToJSON(scope.parseType, scope.variables, true);
|
||||||
|
for (fld in form.fields) {
|
||||||
|
data[fld] = scope[fld];
|
||||||
|
}
|
||||||
|
data.inventory = inventory_id;
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
if (mode === 'edit') {
|
||||||
|
Rest.put(data)
|
||||||
|
.success(function() {
|
||||||
|
scope.$emit('saveCompleted');
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors(scope, data, status, form, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Rest.post(data)
|
||||||
|
.success(function() {
|
||||||
|
scope.$emit('saveCompleted');
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors(scope, data, status, form, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to create host. POST returned status: ' + status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore. ToJSON will have already alerted the user
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var cancelPanel = function() {
|
||||||
|
scope.$destroy();
|
||||||
|
if (scope.codeMirror) {
|
||||||
|
scope.codeMirror.destroy();
|
||||||
|
}
|
||||||
|
$state.go('inventoryManage');
|
||||||
|
};
|
||||||
|
|
||||||
|
angular.extend(vm, {
|
||||||
|
cancelPanel: cancelPanel,
|
||||||
|
saveHost: saveHost,
|
||||||
|
mode: mode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ['$rootScope', '$location', '$log', '$stateParams', '$state', '$scope', 'Rest', 'Alert', 'HostForm',
|
||||||
|
'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange',
|
||||||
|
'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', 'ToJSON', 'ParseVariableString',
|
||||||
|
'CreateDialog', 'TextareaResize', 'ParamPass', manageHostsDirectiveController
|
||||||
|
];
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
/* jshint unused: vars */
|
||||||
|
import manageHostsDirectiveController from './manage-hosts.directive.controller';
|
||||||
|
|
||||||
|
export default ['templateUrl', 'ParamPass',
|
||||||
|
function(templateUrl, ParamPass) {
|
||||||
|
return {
|
||||||
|
restrict: 'EA',
|
||||||
|
scope: true,
|
||||||
|
replace: true,
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-hosts/directive/manage-hosts.directive'),
|
||||||
|
link: function(scope, element, attrs) {
|
||||||
|
|
||||||
|
},
|
||||||
|
controller: manageHostsDirectiveController,
|
||||||
|
controllerAs: 'vm',
|
||||||
|
bindToController: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<div>
|
||||||
|
<div class="Form-header" ng-if="vm.mode === 'add'">
|
||||||
|
<div class="Form-title ng-binding" >Create Host</div>
|
||||||
|
<div class="Form-exitHolder">
|
||||||
|
<button class="Form-exit" ng-click="vm.cancelPanel()">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="Form-exitHolder" ng-if="vm.mode === 'edit'">
|
||||||
|
<button class="Form-exit" ng-click="vm.cancelPanel()">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="host-panel-form"></div>
|
||||||
|
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
|
||||||
|
<div class="ui-dialog-buttonset">
|
||||||
|
<button type="button" class="btn btn-primary Form-saveButton" id="Inventory-hostManage--okButton" ng-click="vm.saveHost()">
|
||||||
|
Save</button>
|
||||||
|
<button type="button" class="btn btn-default Form-cancelButton" id="Inventory-hostManage--cancelButton" ng-click="vm.cancelPanel()">
|
||||||
|
Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
16
awx/ui/client/src/inventories/manage/manage-hosts/main.js
Normal file
16
awx/ui/client/src/inventories/manage/manage-hosts/main.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import route from './manage-hosts.route';
|
||||||
|
import manageHostsDirective from './directive/manage-hosts.directive';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('manage-hosts', [])
|
||||||
|
.directive('manageHosts', manageHostsDirective)
|
||||||
|
.run(['$stateExtender', function($stateExtender) {
|
||||||
|
$stateExtender.addState(route.edit);
|
||||||
|
$stateExtender.addState(route.add);
|
||||||
|
}]);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="tab-pane" id="Inventory-hostManage">
|
||||||
|
<div ng-cloak id="Inventory-hostManage--panel" class="Panel">
|
||||||
|
<manage-hosts></manage-hosts>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
import {
|
||||||
|
templateUrl
|
||||||
|
} from '../../../shared/template-url/template-url.factory';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
edit: {
|
||||||
|
name: 'inventoryManage.editHost',
|
||||||
|
route: '/:host_id/editHost',
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
|
||||||
|
data: {
|
||||||
|
host_id: 'host_id',
|
||||||
|
mode: 'edit'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY EDIT HOSTS"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
add: {
|
||||||
|
name: 'inventoryManage.addHost',
|
||||||
|
route: '/addHost',
|
||||||
|
templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
|
||||||
|
data: {
|
||||||
|
mode: 'add'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
label: "INVENTORY ADD HOST"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
18
awx/ui/client/src/inventories/manage/manage.block.less
Normal file
18
awx/ui/client/src/inventories/manage/manage.block.less
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#Inventory-groupManage--panel,
|
||||||
|
#Inventory-hostManage--panel {
|
||||||
|
.ui-dialog-buttonpane.ui-widget-content {
|
||||||
|
border: none;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#host-panel-form,
|
||||||
|
#properties-tab {
|
||||||
|
.Form-header {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form-textArea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<div class="HostEvent-details--left">
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<div class="HostEvent-title">EVENT</div>
|
||||||
|
<span class="HostEvent-field--content"></span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">HOST</span>
|
||||||
|
<span class="HostEvent-field--content">
|
||||||
|
<a ui-sref="jobDetail.host-events({hostName: event.host_name})">{{event.host_name || "No result found"}}</a></span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">STATUS</span>
|
||||||
|
<span class="HostEvent-field--content">
|
||||||
|
<a class="HostEvents-status">
|
||||||
|
<i class="fa fa-circle" ng-class="processEventStatus"></i>
|
||||||
|
</a>
|
||||||
|
{{event.status || "No result found"}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">ID</span>
|
||||||
|
<span class="HostEvent-field--content">{{event.id || "No result found"}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">CREATED</span>
|
||||||
|
<span class="HostEvent-field--content">{{event.created || "No result found"}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">PLAY</span>
|
||||||
|
<span class="HostEvent-field--content">{{event.play || "No result found"}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">TASK</span>
|
||||||
|
<span class="HostEvent-field--content">{{event.task || "No result found"}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-field">
|
||||||
|
<span class="HostEvent-field--label">MODULE</span>
|
||||||
|
<span class="HostEvent-field--content">{{event.event_data.res.invocation.module_name || "No result found"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-details--right" ng-show="event.event_data.res">
|
||||||
|
<div class="HostEvent-title">RESULTS</div>
|
||||||
|
<!-- discard any objects in the ansible response until we decide to flatten them -->
|
||||||
|
<div class="HostEvent-field" ng-repeat="(key, value) in results = event.event_data.res track by $index" ng-if="processResults(value)">
|
||||||
|
<span class="HostEvent-field--label">{{key}}</span>
|
||||||
|
<span class="HostEvent-field--content">{{value}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<textarea id="HostEvent-json" class="HostEvent-json">
|
||||||
|
</textarea>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<div id="HostEvent" class="HostEvent modal fade" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<!-- modal body -->
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="HostEvent-header">
|
||||||
|
<span class="HostEvent-title">HOST EVENT</span>
|
||||||
|
<!-- close -->
|
||||||
|
<button ui-sref="jobDetail" type="button" class="close">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-nav">
|
||||||
|
<!-- view navigation buttons -->
|
||||||
|
<button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default" >Details</button>
|
||||||
|
<button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default ">JSON</button>
|
||||||
|
<button ng-show="event.stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button>
|
||||||
|
<button ng-show="event.timing" ui-sref="jobDetail.host-event.timing" type="button" class="btn btn-sm btn-default ">Timing</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="HostEvent-body">
|
||||||
|
<!-- views -->
|
||||||
|
<div ui-view></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- controls -->
|
||||||
|
<div class="HostEvent-controls">
|
||||||
|
<button ng-show="showPrev()" ng-click="goPrev()"
|
||||||
|
class="btn btn-sm btn-default">Prev</button>
|
||||||
|
<button ng-show="showNext()"ng-click="goNext()" class="btn btn-sm btn-default">Next</button>
|
||||||
|
<button ui-sref="jobDetail" class="btn btn-sm btn-default" ng-show="true" >Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="EventHost-stdoutPanel Panel">
|
||||||
|
<div class="StandardOut-panelHeader">
|
||||||
|
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
|
||||||
|
<div class="StandardOut-panelHeaderActions">
|
||||||
|
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
|
||||||
|
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
|
||||||
|
<i class="fa fa-download"></i>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<standard-out-log stdout-endpoint="event._stdout"></standard-out-log>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div>timing</div>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
@import "awx/ui/client/src/shared/branding/colors.less";
|
||||||
|
@import "awx/ui/client/src/shared/branding/colors.default.less";
|
||||||
|
@import "awx/ui/client/src/shared/layouts/one-plus-two.less";
|
||||||
|
|
||||||
|
.HostEvent .modal-footer{
|
||||||
|
border: 0;
|
||||||
|
margin-top: 0px;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
.HostEvent-controls{
|
||||||
|
float: right;
|
||||||
|
button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.HostEvent-status--ok{
|
||||||
|
color: @green;
|
||||||
|
}
|
||||||
|
.HostEvent-status--unreachable{
|
||||||
|
color: @unreachable;
|
||||||
|
}
|
||||||
|
.HostEvent-status--changed{
|
||||||
|
color: @changed;
|
||||||
|
}
|
||||||
|
.HostEvent-status--failed{
|
||||||
|
color: @default-err;
|
||||||
|
}
|
||||||
|
.HostEvent-status--skipped{
|
||||||
|
color: @skipped;
|
||||||
|
}
|
||||||
|
.HostEvent-title{
|
||||||
|
color: @default-interface-txt;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.HostEvent .modal-body{
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.HostEvent-nav{
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
.HostEvent-field{
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.HostEvent-field--label{
|
||||||
|
.OnePlusTwo-left--detailsLabel;
|
||||||
|
width: 80px;
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.HostEvent-field{
|
||||||
|
.OnePlusTwo-left--detailsRow;
|
||||||
|
}
|
||||||
|
.HostEvent-field--content{
|
||||||
|
.OnePlusTwo-left--detailsContent;
|
||||||
|
}
|
||||||
|
.HostEvent-details--left, .HostEvent-details--right{
|
||||||
|
vertical-align:top;
|
||||||
|
width:270px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
}
|
||||||
|
.HostEvent-details--right{
|
||||||
|
.HostEvent-field--label{
|
||||||
|
width: 170px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
export default
|
||||||
|
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event',
|
||||||
|
function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){
|
||||||
|
// Avoid rendering objects in the details fieldset
|
||||||
|
// ng-if="processResults(value)" via host-event-details.partial.html
|
||||||
|
$scope.processResults = function(value){
|
||||||
|
if (typeof value == 'object'){return false}
|
||||||
|
else {return true}
|
||||||
|
};
|
||||||
|
|
||||||
|
var codeMirror = function(){
|
||||||
|
var el = $('#HostEvent-json')[0];
|
||||||
|
var editor = CodeMirror.fromTextArea(el, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: {name: "javascript", json: true}
|
||||||
|
});
|
||||||
|
editor.getDoc().setValue(JSON.stringify($scope.json, null, 4));
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getActiveHostIndex = function(){
|
||||||
|
var result = $scope.hostResults.filter(function( obj ) {
|
||||||
|
return obj.id == $scope.event.id;
|
||||||
|
});
|
||||||
|
return $scope.hostResults.indexOf(result[0])
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showPrev = function(){
|
||||||
|
return $scope.getActiveHostIndex() != 0
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showNext = function(){
|
||||||
|
return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1])
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.goNext = function(){
|
||||||
|
var index = $scope.getActiveHostIndex() + 1;
|
||||||
|
var id = $scope.hostResults[index].id;
|
||||||
|
$state.go('jobDetail.host-event.details', {eventId: id})
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.goPrev = function(){
|
||||||
|
var index = $scope.getActiveHostIndex() - 1;
|
||||||
|
var id = $scope.hostResults[index].id;
|
||||||
|
$state.go('jobDetail.host-event.details', {eventId: id})
|
||||||
|
};
|
||||||
|
|
||||||
|
var init = function(){
|
||||||
|
$scope.event = event.data.results[0];
|
||||||
|
$scope.event.created = moment($scope.event.created).format();
|
||||||
|
$scope.processEventStatus = JobDetailService.processEventStatus($scope.event);
|
||||||
|
$scope.hostResults = $stateParams.hostResults;
|
||||||
|
$scope.json = JobDetailService.processJson($scope.event);
|
||||||
|
if ($state.current.name == 'jobDetail.host-event.json'){
|
||||||
|
codeMirror();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$scope.stdout = $scope.event.event_data.res.stdout
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
$scope.sdout = null;
|
||||||
|
}
|
||||||
|
$('#HostEvent').modal('show');
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
}];
|
||||||
86
awx/ui/client/src/job-detail/host-event/host-event.route.js
Normal file
86
awx/ui/client/src/job-detail/host-event/host-event.route.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
|
||||||
|
var hostEventModal = {
|
||||||
|
name: 'jobDetail.host-event',
|
||||||
|
url: '/host-event/:eventId',
|
||||||
|
controller: 'HostEventController',
|
||||||
|
params:{
|
||||||
|
hostResults: {
|
||||||
|
value: null,
|
||||||
|
squash: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
templateUrl: templateUrl('job-detail/host-event/host-event-modal'),
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService){
|
||||||
|
return FeaturesService.get();
|
||||||
|
}],
|
||||||
|
event: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
||||||
|
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||||
|
id: $stateParams.eventId
|
||||||
|
}).success(function(res){ return res.results[0]})
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
onExit: function($state){
|
||||||
|
// close the modal
|
||||||
|
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
|
||||||
|
$('#HostEvent').modal('hide');
|
||||||
|
// hacky way to handle user browsing away via URL bar
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostEventDetails = {
|
||||||
|
name: 'jobDetail.host-event.details',
|
||||||
|
url: '/details',
|
||||||
|
controller: 'HostEventController',
|
||||||
|
templateUrl: templateUrl('job-detail/host-event/host-event-details'),
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService){
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostEventJson = {
|
||||||
|
name: 'jobDetail.host-event.json',
|
||||||
|
url: '/json',
|
||||||
|
controller: 'HostEventController',
|
||||||
|
templateUrl: templateUrl('job-detail/host-event/host-event-json'),
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService){
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var hostEventTiming = {
|
||||||
|
name: 'jobDetail.host-event.timing',
|
||||||
|
url: '/timing',
|
||||||
|
controller: 'HostEventController',
|
||||||
|
templateUrl: templateUrl('job-detail/host-event/host-event-timing'),
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService){
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var hostEventStdout = {
|
||||||
|
name: 'jobDetail.host-event.stdout',
|
||||||
|
url: '/stdout',
|
||||||
|
controller: 'HostEventController',
|
||||||
|
templateUrl: templateUrl('job-detail/host-event/host-event-stdout'),
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService){
|
||||||
|
return FeaturesService.get();
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {hostEventDetails, hostEventJson, hostEventTiming, hostEventStdout, hostEventModal}
|
||||||
21
awx/ui/client/src/job-detail/host-event/main.js
Normal file
21
awx/ui/client/src/job-detail/host-event/main.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {hostEventModal, hostEventDetails, hostEventTiming,
|
||||||
|
hostEventJson, hostEventStdout} from './host-event.route';
|
||||||
|
import controller from './host-event.controller';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('jobDetail.hostEvent', [])
|
||||||
|
.controller('HostEventController', controller)
|
||||||
|
|
||||||
|
.run(['$stateExtender', function($stateExtender){
|
||||||
|
$stateExtender.addState(hostEventModal);
|
||||||
|
$stateExtender.addState(hostEventDetails);
|
||||||
|
$stateExtender.addState(hostEventTiming);
|
||||||
|
$stateExtender.addState(hostEventJson);
|
||||||
|
$stateExtender.addState(hostEventStdout);
|
||||||
|
}]);
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
color: @changed;
|
color: @changed;
|
||||||
}
|
}
|
||||||
.HostEvents-status--failed{
|
.HostEvents-status--failed{
|
||||||
color: @warning;
|
color: @default-err;
|
||||||
}
|
}
|
||||||
.HostEvents-status--skipped{
|
.HostEvents-status--skipped{
|
||||||
color: @skipped;
|
color: @skipped;
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
}
|
}
|
||||||
.HostEvents-title{
|
.HostEvents-title{
|
||||||
|
text-transform: uppercase;
|
||||||
color: @default-interface-txt;
|
color: @default-interface-txt;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,14 @@
|
|||||||
|
|
||||||
export default
|
export default
|
||||||
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
|
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
|
||||||
'JobDetailService', 'CreateSelect2',
|
'JobDetailService', 'CreateSelect2', 'hosts',
|
||||||
function($stateParams, $scope, $rootScope, $state, Wait,
|
function($stateParams, $scope, $rootScope, $state, Wait,
|
||||||
JobDetailService, CreateSelect2){
|
JobDetailService, CreateSelect2, hosts){
|
||||||
|
|
||||||
// pagination not implemented yet, but it'll depend on this
|
// pagination not implemented yet, but it'll depend on this
|
||||||
$scope.page_size = $stateParams.page_size;
|
$scope.page_size = $stateParams.page_size;
|
||||||
|
|
||||||
|
$scope.processEventStatus = JobDetailService.processEventStatus;
|
||||||
$scope.activeFilter = $stateParams.filter || null;
|
$scope.activeFilter = $stateParams.filter || null;
|
||||||
|
|
||||||
$scope.search = function(){
|
$scope.search = function(){
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
|
|
||||||
var filter = function(filter){
|
var filter = function(filter){
|
||||||
Wait('start');
|
Wait('start');
|
||||||
|
|
||||||
if (filter == 'all'){
|
if (filter == 'all'){
|
||||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||||
host_name: $stateParams.hostName,
|
host_name: $stateParams.hostName,
|
||||||
@@ -104,47 +106,17 @@
|
|||||||
filter($('.HostEvents-select').val());
|
filter($('.HostEvents-select').val());
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.processStatus = function(event, $index){
|
|
||||||
// the stack for which status to display is
|
|
||||||
// unreachable > failed > changed > ok
|
|
||||||
// uses the API's runner events and convenience properties .failed .changed to determine status.
|
|
||||||
// see: job_event_callback.py
|
|
||||||
if (event.event == 'runner_on_unreachable'){
|
|
||||||
$scope.results[$index].status = 'Unreachable';
|
|
||||||
return 'HostEvents-status--unreachable'
|
|
||||||
}
|
|
||||||
// equiv to 'runner_on_error' && 'runner on failed'
|
|
||||||
if (event.failed){
|
|
||||||
$scope.results[$index].status = 'Failed';
|
|
||||||
return 'HostEvents-status--failed'
|
|
||||||
}
|
|
||||||
// catch the changed case before ok, because both can be true
|
|
||||||
if (event.changed){
|
|
||||||
$scope.results[$index].status = 'Changed';
|
|
||||||
return 'HostEvents-status--changed'
|
|
||||||
}
|
|
||||||
if (event.event == 'runner_on_ok'){
|
|
||||||
$scope.results[$index].status = 'OK';
|
|
||||||
return 'HostEvents-status--ok'
|
|
||||||
}
|
|
||||||
if (event.event == 'runner_on_skipped'){
|
|
||||||
$scope.results[$index].status = 'Skipped';
|
|
||||||
return 'HostEvents-status--skipped'
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// study a case where none of these apply
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var init = function(){
|
var init = function(){
|
||||||
|
$scope.hostName = $stateParams.hostName;
|
||||||
// create filter dropdown
|
// create filter dropdown
|
||||||
|
console.log($stateParams)
|
||||||
CreateSelect2({
|
CreateSelect2({
|
||||||
element: '.HostEvents-select',
|
element: '.HostEvents-select',
|
||||||
multiple: false
|
multiple: false
|
||||||
});
|
});
|
||||||
// process the filter if one was passed
|
// process the filter if one was passed
|
||||||
if ($stateParams.filter){
|
if ($stateParams.filter){
|
||||||
|
Wait('start');
|
||||||
filter($stateParams.filter).success(function(res){
|
filter($stateParams.filter).success(function(res){
|
||||||
$scope.results = res.results;
|
$scope.results = res.results;
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
@@ -152,25 +124,11 @@
|
|||||||
});;
|
});;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
Wait('start');
|
$scope.results = hosts.data.results;
|
||||||
JobDetailService.getRelatedJobEvents($stateParams.id, {
|
$('#HostEvents').modal('show');
|
||||||
host_name: $stateParams.hostName,
|
|
||||||
page_size: $stateParams.page_size})
|
|
||||||
.success(function(res){
|
|
||||||
$scope.pagination = res;
|
|
||||||
$scope.results = res.results;
|
|
||||||
Wait('stop');
|
|
||||||
$('#HostEvents').modal('show');
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.goBack = function(){
|
|
||||||
// go back to the job details state
|
|
||||||
// we're leaning on $stateProvider's onExit to close the modal
|
|
||||||
$state.go('jobDetail');
|
|
||||||
};
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="HostEvents-header">
|
<div class="HostEvents-header">
|
||||||
<span class="HostEvents-title">HOST EVENTS</span>
|
<span class="HostEvents-title">HOST EVENTS | {{hostName}}</span>
|
||||||
<!-- Close -->
|
<!-- Close -->
|
||||||
<button ng-click="goBack()" type="button" class="close">
|
<button ui-sref="jobDetail" type="button" class="close">
|
||||||
<i class="fa fa fa-times-circle"></i>
|
<i class="fa fa fa-times-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<!-- column labels -->
|
<!-- column labels -->
|
||||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">STATUS</th>
|
<th ng-hide="results.length == 0" class="HostEvents-table--header">STATUS</th>
|
||||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">HOST</th>
|
|
||||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">PLAY</th>
|
<th ng-hide="results.length == 0" class="HostEvents-table--header">PLAY</th>
|
||||||
<th ng-hide="results.length == 0" class="HostEvents-table--header">TASK</th>
|
<th ng-hide="results.length == 0" class="HostEvents-table--header">TASK</th>
|
||||||
<!-- result rows -->
|
<!-- result rows -->
|
||||||
@@ -37,11 +36,10 @@
|
|||||||
<td class=HostEvents-table--cell>
|
<td class=HostEvents-table--cell>
|
||||||
<!-- status circles -->
|
<!-- status circles -->
|
||||||
<a class="HostEvents-status">
|
<a class="HostEvents-status">
|
||||||
<i class="fa fa-circle" ng-class="processStatus(event, $index)"></i>
|
<i class="fa fa-circle" ng-class="processEventStatus(event)"></i>
|
||||||
</a>
|
</a>
|
||||||
{{event.status}}
|
{{event.status}}
|
||||||
</td>
|
</td>
|
||||||
<td class=HostEvents-table--cell>{{event.host_name}}</td>
|
|
||||||
<td class=HostEvents-table--cell>{{event.play}}</td>
|
<td class=HostEvents-table--cell>{{event.play}}</td>
|
||||||
<td class=HostEvents-table--cell>{{event.task}}</td>
|
<td class=HostEvents-table--cell>{{event.task}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -56,7 +54,7 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<!-- pagination -->
|
<!-- pagination -->
|
||||||
<!-- close -->
|
<!-- close -->
|
||||||
<button ng-click="goBack()" class="btn btn-default pull-right HostEvents-close">OK</button>
|
<button ui-sref="jobDetail" class="btn btn-default pull-right HostEvents-close">OK</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*************************************************
|
/*************************************************
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
*
|
*
|
||||||
* All Rights Reserved
|
* All Rights Reserved
|
||||||
*************************************************/
|
*************************************************/
|
||||||
@@ -25,6 +25,11 @@ export default {
|
|||||||
resolve: {
|
resolve: {
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
return FeaturesService.get();
|
return FeaturesService.get();
|
||||||
}]
|
}],
|
||||||
|
hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
||||||
|
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||||
|
host_name: $stateParams.hostName
|
||||||
|
}).success(function(res){ return res.results[0]})
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default
|
|||||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
|
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
|
||||||
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
|
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
|
||||||
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
|
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
|
||||||
'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer',
|
'EditSchedule', 'ParseTypeChange', 'JobDetailService',
|
||||||
function(
|
function(
|
||||||
$location, $rootScope, $filter, $scope, $compile, $stateParams,
|
$location, $rootScope, $filter, $scope, $compile, $stateParams,
|
||||||
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
|
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
|
||||||
@@ -27,7 +27,7 @@ export default
|
|||||||
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
|
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
|
||||||
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
|
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
|
||||||
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
|
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
|
||||||
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer
|
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
|
||||||
) {
|
) {
|
||||||
ClearScope();
|
ClearScope();
|
||||||
|
|
||||||
@@ -1119,17 +1119,6 @@ export default
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.viewHostResults = function(id) {
|
|
||||||
EventViewer({
|
|
||||||
scope: scope,
|
|
||||||
url: scope.job.related.job_events,
|
|
||||||
parent_id: scope.selectedTask,
|
|
||||||
event_id: id,
|
|
||||||
index: this.$index,
|
|
||||||
title: 'Host Event'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (scope.removeDeleteFinished) {
|
if (scope.removeDeleteFinished) {
|
||||||
scope.removeDeleteFinished();
|
scope.removeDeleteFinished();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,7 +343,9 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
|
<tr class="List-tableRow cursor-pointer" ng-class-odd="'List-tableRow--oddRow'" ng-class-even="'List-tableRow--evenRow'" ng-repeat="result in results = (hostResults) track by $index">
|
||||||
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column"><a ng-click="viewHostResults(result.id)" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a></td>
|
<td class="List-tableCell col-lg-4 col-md-3 col-sm-3 col-xs-3 status-column">
|
||||||
|
<a ui-sref="jobDetail.host-event.details({eventId: result.id, hostResults: hostResults})" aw-tool-tip="Event ID: {{ result.id }}<br \>Status: {{ result.status_text }}. Click for details" data-placement="top"><i ng-show="result.status_text != 'Unreachable'" class="JobDetail-statusIcon fa icon-job-{{ result.status }}"></i><span ng-show="result.status_text != 'Unreachable'">{{ result.name }}</span><i ng-show="result.status_text == 'Unreachable'" class="JobDetail-statusIcon fa icon-job-unreachable"></i><span ng-show="result.status_text == 'Unreachable'">{{ result.name }}</span></a>
|
||||||
|
</td>
|
||||||
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
|
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3 item-column">{{ result.item }}</td>
|
||||||
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
|
<td class="List-tableCell col-lg-3 col-md-4 col-sm-3 col-xs-3">{{ result.msg }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -422,7 +424,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr class="List-tableRow" ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
|
<tr class="List-tableRow" ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}" ng-class-even="'List-tableRow--evenRow'" ng-class-odd="'List-tableRow--oddRow'">
|
||||||
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
|
<td class="List-tableCell name col-lg-6 col-md-6 col-sm-6 col-xs-6">
|
||||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id})" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
|
<a ui-sref="jobDetail.host-events({hostName: host.name})" aw-tool-tip="View events" data-placement="top">{{ host.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
|
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
|
||||||
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'ok'})" aw-tool-tip="{{ host.okTip }}" data-tip-watch="host.okTip" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
<a ui-sref="jobDetail.host-events({hostName: host.name, hostId: host.id, filter: 'ok'})" aw-tool-tip="{{ host.okTip }}" data-tip-watch="host.okTip" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
||||||
@@ -480,8 +482,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-include="'/static/partials/eventviewer.html'"></div>
|
|
||||||
|
|
||||||
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
|
<div id="host-modal-dialog" style="display: none;" class="dialog-content"></div>
|
||||||
|
|
||||||
<div ng-include="'/static/partials/schedule_dialog.html'"></div>
|
<div ng-include="'/static/partials/schedule_dialog.html'"></div>
|
||||||
|
|||||||
@@ -2,13 +2,85 @@ export default
|
|||||||
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
|
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
|
||||||
return {
|
return {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
For ES6
|
For ES6
|
||||||
it might be useful to set some default params here, e.g.
|
it might be useful to set some default params here, e.g.
|
||||||
getJobHostSummaries: function(id, page_size=200, order='host_name'){}
|
getJobHostSummaries: function(id, page_size=200, order='host_name'){}
|
||||||
without ES6, we'd have to supply defaults like this:
|
without ES6, we'd have to supply defaults like this:
|
||||||
this.page_size = params.page_size ? params.page_size : 200;
|
this.page_size = params.page_size ? params.page_size : 200;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// the the API passes through Ansible's event_data response
|
||||||
|
// we need to massage away the verbose and redundant properties
|
||||||
|
|
||||||
|
processJson: function(data){
|
||||||
|
// a deep copy
|
||||||
|
var result = $.extend(true, {}, data);
|
||||||
|
// configure fields to ignore
|
||||||
|
var ignored = [
|
||||||
|
'event_data',
|
||||||
|
'related',
|
||||||
|
'summary_fields',
|
||||||
|
'url',
|
||||||
|
'ansible_facts',
|
||||||
|
];
|
||||||
|
|
||||||
|
// remove ignored properties
|
||||||
|
Object.keys(result).forEach(function(key, index){
|
||||||
|
if (ignored.indexOf(key) > -1) {
|
||||||
|
delete result[key]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// flatten Ansible's passed-through response
|
||||||
|
try{
|
||||||
|
result.event_data = {};
|
||||||
|
Object.keys(data.event_data.res).forEach(function(key, index){
|
||||||
|
if (ignored.indexOf(key) > -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
result.event_data[key] = data.event_data.res[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(err){result.event_data = null;}
|
||||||
|
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
processEventStatus: function(event){
|
||||||
|
// Generate a helper class for job_event statuses
|
||||||
|
// the stack for which status to display is
|
||||||
|
// unreachable > failed > changed > ok
|
||||||
|
// uses the API's runner events and convenience properties .failed .changed to determine status.
|
||||||
|
// see: job_event_callback.py
|
||||||
|
if (event.event == 'runner_on_unreachable'){
|
||||||
|
event.status = 'Unreachable';
|
||||||
|
return 'HostEvents-status--unreachable'
|
||||||
|
}
|
||||||
|
// equiv to 'runner_on_error' && 'runner on failed'
|
||||||
|
if (event.failed){
|
||||||
|
event.status = 'Failed';
|
||||||
|
return 'HostEvents-status--failed'
|
||||||
|
}
|
||||||
|
// catch the changed case before ok, because both can be true
|
||||||
|
if (event.changed){
|
||||||
|
event.status = 'Changed';
|
||||||
|
return 'HostEvents-status--changed'
|
||||||
|
}
|
||||||
|
if (event.event == 'runner_on_ok'){
|
||||||
|
event.status = 'OK';
|
||||||
|
return 'HostEvents-status--ok'
|
||||||
|
}
|
||||||
|
if (event.event == 'runner_on_skipped'){
|
||||||
|
event.status = 'Skipped';
|
||||||
|
return 'HostEvents-status--skipped'
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// study a case where none of these apply
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// GET events related to a job run
|
// GET events related to a job run
|
||||||
// e.g.
|
// e.g.
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import route from './job-detail.route';
|
|||||||
import controller from './job-detail.controller';
|
import controller from './job-detail.controller';
|
||||||
import service from './job-detail.service';
|
import service from './job-detail.service';
|
||||||
import hostEvents from './host-events/main';
|
import hostEvents from './host-events/main';
|
||||||
|
import hostEvent from './host-event/main';
|
||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('jobDetail', [
|
angular.module('jobDetail', [
|
||||||
hostEvents.name
|
hostEvents.name,
|
||||||
|
hostEvent.name
|
||||||
])
|
])
|
||||||
.controller('JobDetailController', controller)
|
.controller('JobDetailController', controller)
|
||||||
.service('JobDetailService', service)
|
.service('JobDetailService', service)
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
set: function(data){
|
set: function(data){
|
||||||
var defaultUrl = GetBasePath('job_templates');
|
var defaultUrl = GetBasePath('job_templates');
|
||||||
Rest.setUrl(defaultUrl);
|
Rest.setUrl(defaultUrl);
|
||||||
data.results[0].name = data.results[0].name + ' ' + moment().format('h:mm:ss a'); // 2:49:11 pm
|
var name = this.buildName(data.results[0].name)
|
||||||
|
data.results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm
|
||||||
return Rest.post(data.results[0])
|
return Rest.post(data.results[0])
|
||||||
.success(function(res){
|
.success(function(res){
|
||||||
return res
|
return res
|
||||||
@@ -32,6 +33,10 @@
|
|||||||
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
|
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
|
||||||
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
|
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
buildName: function(name){
|
||||||
|
var result = name.split('@')[0];
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ export default
|
|||||||
},{
|
},{
|
||||||
name: "OpenStack",
|
name: "OpenStack",
|
||||||
value: "openstack"
|
value: "openstack"
|
||||||
},{
|
|
||||||
name: "OpenStack V3",
|
|
||||||
value: "openstack_v3"
|
|
||||||
}],
|
}],
|
||||||
sourceModel: 'inventory_source',
|
sourceModel: 'inventory_source',
|
||||||
sourceField: 'source',
|
sourceField: 'source',
|
||||||
@@ -87,7 +84,7 @@ export default
|
|||||||
has_external_source: {
|
has_external_source: {
|
||||||
label: 'Has external source?',
|
label: 'Has external source?',
|
||||||
searchType: 'in',
|
searchType: 'in',
|
||||||
searchValue: 'ec2,rax,vmware,azure,gce,openstack,openstack_v3',
|
searchValue: 'ec2,rax,vmware,azure,gce,openstack',
|
||||||
searchOnly: true,
|
searchOnly: true,
|
||||||
sourceModel: 'inventory_source',
|
sourceModel: 'inventory_source',
|
||||||
sourceField: 'source'
|
sourceField: 'source'
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ export default
|
|||||||
},{
|
},{
|
||||||
name: "OpenStack",
|
name: "OpenStack",
|
||||||
value: "openstack"
|
value: "openstack"
|
||||||
},{
|
|
||||||
name: "OpenStack V3",
|
|
||||||
value: "openstack_v3"
|
|
||||||
}],
|
}],
|
||||||
sourceModel: 'inventory_source',
|
sourceModel: 'inventory_source',
|
||||||
sourceField: 'source',
|
sourceField: 'source',
|
||||||
@@ -62,7 +59,7 @@ export default
|
|||||||
has_external_source: {
|
has_external_source: {
|
||||||
label: 'Has external source?',
|
label: 'Has external source?',
|
||||||
searchType: 'in',
|
searchType: 'in',
|
||||||
searchValue: 'ec2,rax,vmware,azure,gce,openstack,openstack_v3',
|
searchValue: 'ec2,rax,vmware,azure,gce,openstack',
|
||||||
searchOnly: true,
|
searchOnly: true,
|
||||||
sourceModel: 'inventory_source',
|
sourceModel: 'inventory_source',
|
||||||
sourceField: 'source'
|
sourceField: 'source'
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<div id="eventviewer-modal-dialog" style="display: none;">
|
|
||||||
<ul id="eventview-tabs" class="nav nav-tabs">
|
|
||||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Event</a></li>
|
|
||||||
<li><a href="#results" id="results-link" data-toggle="tab" ng-click="toggleTab($event, 'results-link', 'eventview-tabs')">Results</a></li>
|
|
||||||
<li><a href="#timing" id="timing-link" data-toggle="tab" ng-click="toggleTab($event, 'timing-link', 'eventview-tabs')">Timing</a></li>
|
|
||||||
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'eventview-tabs')">Standard Out</a></li>
|
|
||||||
<li><a href="#stderr" id="stderr-link" data-toggle="tab" ng-click="toggleTab($event, 'stderr-link', 'eventview-tabs')">Standard Error</a></li>
|
|
||||||
<li><a href="#traceback" id="traceback-link" data-toggle="tab" ng-click="toggleTab($event, 'traceback-link', 'eventview-tabs')">Traceback</a></li>
|
|
||||||
<li><a href="#json" id="json-link" data-toggle="tab" ng-click="toggleTab($event, 'json-link', 'eventview-tabs')">JSON</a></li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content">
|
|
||||||
<div class="tab-pane active" id="status">
|
|
||||||
<div id="status-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="results">
|
|
||||||
<div id="results-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="timing">
|
|
||||||
<div id="timing-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="stdout">
|
|
||||||
<div id="stdout-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="stderr">
|
|
||||||
<div id="stderr-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="traceback">
|
|
||||||
<div id="traceback-form-container"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="json">
|
|
||||||
<div id="json-form-container"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -873,4 +873,20 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
|
|||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
])
|
||||||
|
.factory('ParamPass', function() {
|
||||||
|
var savedData = undefined;
|
||||||
|
|
||||||
|
function set(data) {
|
||||||
|
savedData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get() {
|
||||||
|
return savedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: set,
|
||||||
|
get: get
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -1394,13 +1394,18 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
|
|||||||
"ng-if=is_superuser>Admin</span>";
|
"ng-if=is_superuser>Admin</span>";
|
||||||
}
|
}
|
||||||
html += "</div>\n";
|
html += "</div>\n";
|
||||||
|
if(options.cancelButton !== undefined && options.cancelButton === false) {
|
||||||
|
html += "<div class=\"Form-exitHolder\">";
|
||||||
|
html += "</div>";
|
||||||
|
} else {
|
||||||
|
html += "<div class=\"Form-exitHolder\">";
|
||||||
|
html += "<button class=\"Form-exit\" ng-click=\"formCancel()\">";
|
||||||
|
html += "<i class=\"fa fa-times-circle\"></i>";
|
||||||
|
html += "</button></div>\n";
|
||||||
|
}
|
||||||
|
html += "</div>\n"; //end of Form-header
|
||||||
|
|
||||||
html += "<div class=\"Form-exitHolder\">";
|
|
||||||
html += "<button class=\"Form-exit\" ng-click=\"formCancel()\">";
|
|
||||||
html += "<i class=\"fa fa-times-circle\"></i>";
|
|
||||||
html += "</button></div>\n";
|
|
||||||
|
|
||||||
html += "</div>\n"; //end of Form-header
|
|
||||||
|
|
||||||
if (this.form.tabs) {
|
if (this.form.tabs) {
|
||||||
var collection;
|
var collection;
|
||||||
|
|||||||
@@ -61,7 +61,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.OnePlusTwo-left--detailsLabel {
|
.OnePlusTwo-left--detailsLabel {
|
||||||
width: 140px;
|
word-wrap: break-word;
|
||||||
|
width: 170px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: @default-interface-txt;
|
color: @default-interface-txt;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
1252
npm-shrinkwrap.json
generated
1252
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,29 +13,8 @@ fi
|
|||||||
service_action() {
|
service_action() {
|
||||||
SERVICES=$TOWER_SERVICES
|
SERVICES=$TOWER_SERVICES
|
||||||
|
|
||||||
# When determining whether mongod is required, postgres is required. The
|
|
||||||
# following ensures mongod is started after postgres, and stopped before
|
|
||||||
# postgres.
|
|
||||||
case ${1} in
|
|
||||||
start|status)
|
|
||||||
SERVICES="$SERVICES mongod"
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
SERVICES="mongod $SERVICES"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
for svc in ${SERVICES}; do
|
for svc in ${SERVICES}; do
|
||||||
|
|
||||||
# Determine whether mongod is needed
|
|
||||||
if [[ ${svc} == mongod ]]; then
|
|
||||||
tower-manage uses_mongo --local 2> /dev/null >/dev/null
|
|
||||||
# if mongod is not required, break
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
service ${svc} $1
|
service ${svc} $1
|
||||||
this_return=$?
|
this_return=$?
|
||||||
if [ $this_return -gt $worst_return ]; then
|
if [ $this_return -gt $worst_return ]; then
|
||||||
@@ -43,7 +22,7 @@ service_action() {
|
|||||||
fi
|
fi
|
||||||
# Allow supervisor time to cleanup child pids (ubuntu only)
|
# Allow supervisor time to cleanup child pids (ubuntu only)
|
||||||
if [[ ${svc} == supervisor* && ${1} == stop && -e /etc/debian_version ]]; then
|
if [[ ${svc} == supervisor* && ${1} == stop && -e /etc/debian_version ]]; then
|
||||||
S_PID=$(pidof -x supervisord)
|
S_PID=$(pidof -x supervisord)
|
||||||
echo "Waiting to allow supervisor time to cleanup ... pid ${S_PID}"
|
echo "Waiting to allow supervisor time to cleanup ... pid ${S_PID}"
|
||||||
if [ "${S_PID}" ]; then
|
if [ "${S_PID}" ]; then
|
||||||
i=0
|
i=0
|
||||||
@@ -76,22 +55,22 @@ case "$1" in
|
|||||||
usage
|
usage
|
||||||
;;
|
;;
|
||||||
start)
|
start)
|
||||||
echo "Starting Tower"
|
echo "Starting Tower"
|
||||||
service_action start
|
service_action start
|
||||||
;;
|
;;
|
||||||
stop)
|
stop)
|
||||||
echo "Stopping Tower"
|
echo "Stopping Tower"
|
||||||
service_action stop
|
service_action stop
|
||||||
;;
|
;;
|
||||||
restart)
|
restart)
|
||||||
echo "Restarting Tower"
|
echo "Restarting Tower"
|
||||||
service_action stop
|
service_action stop
|
||||||
service_action start
|
service_action start
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
echo "Showing Tower Status"
|
echo "Showing Tower Status"
|
||||||
service_action status
|
service_action status
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
usage
|
||||||
worst_return=1
|
worst_return=1
|
||||||
|
|||||||
Reference in New Issue
Block a user