diff --git a/awx/api/metadata.py b/awx/api/metadata.py
index 6fccdb887d..f5c72fed97 100644
--- a/awx/api/metadata.py
+++ b/awx/api/metadata.py
@@ -6,7 +6,7 @@ from collections import OrderedDict
# Django
from django.core.exceptions import PermissionDenied
from django.http import Http404
-from django.utils.encoding import force_text
+from django.utils.encoding import force_text, smart_text
# Django REST Framework
from rest_framework import exceptions
@@ -37,6 +37,25 @@ class Metadata(metadata.SimpleMetadata):
if value is not None and value != '':
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.
# FIXME: Still isn't showing all default values?
try:
@@ -77,7 +96,7 @@ class Metadata(metadata.SimpleMetadata):
# Update type of fields returned...
if field.field_name == 'type':
- field_info['type'] = 'multiple choice'
+ field_info['type'] = 'choice'
elif field.field_name == 'url':
field_info['type'] = 'string'
elif field.field_name in ('related', 'summary_fields'):
diff --git a/awx/api/pagination.py b/awx/api/pagination.py
index 822e6065ee..ee17aee0e1 100644
--- a/awx/api/pagination.py
+++ b/awx/api/pagination.py
@@ -3,7 +3,7 @@
# Django REST Framework
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):
@@ -22,6 +22,4 @@ class Pagination(pagination.PageNumberPagination):
return None
url = self.request and self.request.get_full_path() or ''
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)
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index 46e27a2ccc..e9d34c64d7 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -21,7 +21,7 @@ from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError
from django.db import models
# 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
# Django REST Framework
@@ -351,7 +351,6 @@ class BaseSerializer(serializers.ModelSerializer):
return obj.modified
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
# 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:
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.
if model_field.has_default() and not field_kwargs.get('read_only', False):
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
# 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', []):
if isinstance(validator, validators.UniqueValidator):
unique_error_message = model_field.error_messages.get('unique', None)
@@ -1662,6 +1641,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
d['can_copy'] = 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['labels'] = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('-name')[:10]]
return d
def validate(self, attrs):
@@ -1703,6 +1683,11 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
res['relaunch'] = reverse('api:job_relaunch', args=(obj.pk,))
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):
# When creating a new job and a job template is specified, populate any
# fields not provided in data from the job template.
diff --git a/awx/api/templates/api/_result_fields_common.md b/awx/api/templates/api/_result_fields_common.md
index 35fc3b55d1..43abefc534 100644
--- a/awx/api/templates/api/_result_fields_common.md
+++ b/awx/api/templates/api/_result_fields_common.md
@@ -1,6 +1,6 @@
{% for fn, fm in serializer_fields.items %}{% spaceless %}
{% 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 %}
{% endspaceless %}
{% endfor %}
diff --git a/awx/api/views.py b/awx/api/views.py
index 986a8bc23b..0c406dc610 100644
--- a/awx/api/views.py
+++ b/awx/api/views.py
@@ -134,6 +134,7 @@ class ApiV1RootView(APIView):
data['roles'] = reverse('api:role_list')
data['notifiers'] = reverse('api:notifier_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_jobs'] = reverse('api:unified_job_list')
data['activity_stream'] = reverse('api:activity_stream_list')
diff --git a/awx/main/access.py b/awx/main/access.py
index 999962bd99..15237a0ea3 100644
--- a/awx/main/access.py
+++ b/awx/main/access.py
@@ -1054,12 +1054,16 @@ class UnifiedJobTemplateAccess(BaseAccess):
'last_job',
'current_job',
)
- qs = qs.prefetch_related(
- #'project',
- 'inventory',
- 'credential',
- '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',
+ # 'cloud_credential',
+ #)
return qs.all()
@@ -1089,20 +1093,26 @@ class UnifiedJobAccess(BaseAccess):
)
qs = qs.prefetch_related(
'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()
class ScheduleAccess(BaseAccess):
diff --git a/awx/main/constants.py b/awx/main/constants.py
index a6bdafdf5a..64f6265569 100644
--- a/awx/main/constants.py
+++ b/awx/main/constants.py
@@ -1,5 +1,5 @@
# Copyright (c) 2015 Ansible, Inc.
# 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',)
diff --git a/awx/main/migrations/0001_initial.py b/awx/main/migrations/0001_initial.py
index 6d2c78e454..bdc98cace2 100644
--- a/awx/main/migrations/0001_initial.py
+++ b/awx/main/migrations/0001_initial.py
@@ -381,7 +381,7 @@ class Migration(migrations.Migration):
name='AdHocCommand',
fields=[
('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)),
('module_name', models.CharField(default=b'', max_length=1024, blank=True)),
('module_args', models.TextField(default=b'', blank=True)),
diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py
index c97c484c5e..6a780da38a 100644
--- a/awx/main/models/ad_hoc_commands.py
+++ b/awx/main/models/ad_hoc_commands.py
@@ -36,7 +36,7 @@ class AdHocCommand(UnifiedJob):
job_type = models.CharField(
max_length=64,
- choices=JOB_TYPE_CHOICES,
+ choices=AD_HOC_JOB_TYPE_CHOICES,
default='run',
)
inventory = models.ForeignKey(
diff --git a/awx/main/models/base.py b/awx/main/models/base.py
index e0f4b72c1a..b97edae8ee 100644
--- a/awx/main/models/base.py
+++ b/awx/main/models/base.py
@@ -29,7 +29,7 @@ __all__ = ['VarsDictProperty', 'BaseModel', 'CreatedModifiedModel',
'PERM_INVENTORY_ADMIN', 'PERM_INVENTORY_READ',
'PERM_INVENTORY_WRITE', 'PERM_INVENTORY_DEPLOY', 'PERM_INVENTORY_SCAN',
'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']
PERM_INVENTORY_ADMIN = 'admin'
@@ -46,6 +46,11 @@ JOB_TYPE_CHOICES = [
(PERM_INVENTORY_SCAN, _('Scan')),
]
+AD_HOC_JOB_TYPE_CHOICES = [
+ (PERM_INVENTORY_DEPLOY, _('Run')),
+ (PERM_INVENTORY_CHECK, _('Check')),
+]
+
PERMISSION_TYPE_CHOICES = [
(PERM_INVENTORY_READ, _('Read Inventory')),
(PERM_INVENTORY_WRITE, _('Edit Inventory')),
@@ -56,7 +61,7 @@ PERMISSION_TYPE_CHOICES = [
(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 = [
(0, '0 (Normal)'),
diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py
index 298560e97e..a4f31c7071 100644
--- a/awx/main/models/credential.py
+++ b/awx/main/models/credential.py
@@ -40,7 +40,6 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
('gce', _('Google Compute Engine')),
('azure', _('Microsoft Azure')),
('openstack', _('OpenStack')),
- ('openstack_v3', _('OpenStack V3')),
]
BECOME_METHOD_CHOICES = [
@@ -237,18 +236,12 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
host = self.host or ''
if not host and self.kind == 'vmware':
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.')
return host
def clean_domain(self):
- """For case of Keystone v3 identity service that requires a
- `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
+ return self.domain or ''
def clean_username(self):
username = self.username or ''
@@ -259,7 +252,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
'credential.')
if not username and self.kind == 'vmware':
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.')
return username
@@ -271,13 +264,13 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
raise ValidationError('API key required for Rackspace credential.')
if not password and self.kind == 'vmware':
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.')
return password
def clean_project(self):
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.')
return project
diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py
index 1174c33fd6..0283a5c70c 100644
--- a/awx/main/models/inventory.py
+++ b/awx/main/models/inventory.py
@@ -733,7 +733,6 @@ class InventorySourceOptions(BaseModel):
('azure', _('Microsoft Azure')),
('vmware', _('VMware vCenter')),
('openstack', _('OpenStack')),
- ('openstack_v3', _('OpenStack V3')),
('custom', _('Custom Script')),
]
@@ -962,11 +961,6 @@ class InventorySourceOptions(BaseModel):
"""I don't think openstack has regions"""
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):
if not self.source:
return None
diff --git a/awx/main/tasks.py b/awx/main/tasks.py
index de912b0a05..0fbd322199 100644
--- a/awx/main/tasks.py
+++ b/awx/main/tasks.py
@@ -378,6 +378,10 @@ class BaseTask(Task):
if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported:
raise RuntimeError(OPENSSH_KEY_ERROR)
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
# 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:
@@ -695,7 +699,7 @@ class RunJob(BaseTask):
if credential.ssh_key_data not in (None, ''):
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
openstack_auth = dict(auth_url=credential.host,
username=credential.username,
@@ -787,7 +791,7 @@ class RunJob(BaseTask):
env['VMWARE_USER'] = cloud_cred.username
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
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', '')
# Set environment variables related to scan jobs
@@ -1136,7 +1140,7 @@ class RunInventoryUpdate(BaseTask):
credential = inventory_update.credential
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
openstack_auth = dict(auth_url=credential.host,
username=credential.username,
@@ -1291,7 +1295,7 @@ class RunInventoryUpdate(BaseTask):
env['GCE_PROJECT'] = passwords.get('source_project', '')
env['GCE_PEM_FILE_PATH'] = cloud_credential
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
elif inventory_update.source == 'file':
# FIXME: Parse source_env to dict, update env.
@@ -1334,11 +1338,6 @@ class RunInventoryUpdate(BaseTask):
# to a shorter variable. :)
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
# arguments.
plugin_path = self.get_path_to('..', 'plugins', 'inventory',
diff --git a/awx/main/tests/data/ssh.py b/awx/main/tests/data/ssh.py
index ff5592358e..c2a9a29223 100644
--- a/awx/main/tests/data/ssh.py
+++ b/awx/main/tests/data/ssh.py
@@ -84,8 +84,7 @@ HPUhg3adAmIJ9z9u/VmTErbVklcKWlyZuTUkxeQ/BJmSIRUQAAAIEA3oKAzdDURjy8zxLX
gBLCPdi8AxCiqQJBCsGxXCgKtZewset1XJHIN9ryfb4QSZFkSOlm/LgdeGtS8Or0GNPRYd
hgnUCF0LkEsDQ7HzPZYujLrAwjumvGQH6ORp5vRh0tQb93o4e1/A2vpdSKeH7gCe/jfUSY
h7dFGNoAI4cF7/0AAAAUcm9vdEBwaWxsb3cuaXhtbS5uZXQBAgMEBQYH
------END OPENSSH PRIVATE KEY-----
-'''
+-----END OPENSSH PRIVATE KEY-----'''
TEST_OPENSSH_KEY_DATA_LOCKED = '''-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABALaWMfjc
@@ -114,8 +113,7 @@ C6Oxl1Wsp3gPkK2yiuy8qcrvoEoJ25TeEhUGEAPWx2OuQJO/Lpq9aF/JJoqGwnBaXdCsi+
5ig+ZMq5GKQtyydzyXImjlNEUH1w2prRDiGVEufANA5LSLCtqOLgDzXS62WUBjJBrQJVAM
YpWz1tiZQoyv1RT3Y0O0Vwe2Z5AK3fVM0I5jWdiLrIErtcR4ULa6T56QtA52DufhKzINTR
Vg9TtUBqfKIpRQikPSjm7vpY/Xnbc=
------END OPENSSH PRIVATE KEY-----
-'''
+-----END OPENSSH PRIVATE KEY-----'''
TEST_SSH_CERT_KEY = """-----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIBATALBgkqhkiG9w0BAQswSTEWMBQGA1UEAwwNV2luZG93
diff --git a/awx/main/tests/functional/api/test_organization_counts.py b/awx/main/tests/functional/api/test_organization_counts.py
index e3787aa1a1..550068da63 100644
--- a/awx/main/tests/functional/api/test_organization_counts.py
+++ b/awx/main/tests/functional/api/test_organization_counts.py
@@ -128,6 +128,7 @@ def test_two_organizations(resourced_organization, organizations, user, get):
'teams': 0
}
+@pytest.mark.skip(reason="resolution planned for after RBAC merge")
@pytest.mark.django_db
@pytest.mark.skipif("True") # XXX: This needs to be implemented
def test_JT_associated_with_project(organizations, project, user, get):
diff --git a/awx/main/tests/old/inventory.py b/awx/main/tests/old/inventory.py
index fbe765f659..73e1bd5eb5 100644
--- a/awx/main/tests/old/inventory.py
+++ b/awx/main/tests/old/inventory.py
@@ -1970,7 +1970,7 @@ class InventoryUpdatesTest(BaseTransactionTest):
self.check_inventory_source(inventory_source)
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
api_url = getattr(settings, 'TEST_OPENSTACK_HOST_V3', '')
api_user = getattr(settings, 'TEST_OPENSTACK_USER', '')
@@ -1978,15 +1978,15 @@ class InventoryUpdatesTest(BaseTransactionTest):
api_project = getattr(settings, 'TEST_OPENSTACK_PROJECT', '')
api_domain = getattr(settings, 'TEST_OPENSTACK_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()
- credential = Credential.objects.create(kind='openstack_v3',
+ credential = Credential.objects.create(kind='openstack',
host=api_url,
username=api_user,
password=api_password,
project=api_project,
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.assertFalse(self.group.all_hosts.filter(instance_id='').exists())
@@ -2034,27 +2034,3 @@ class InventoryCredentialTest(BaseTest):
self.assertIn('password', response)
self.assertIn('host', 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)
diff --git a/awx/static/api/api.js b/awx/static/api/api.js
index 177770fb8f..67053ae2f6 100644
--- a/awx/static/api/api.js
+++ b/awx/static/api/api.js
@@ -43,11 +43,13 @@ $(function() {
$('.description').addClass('prettyprint').parent().css('float', 'none');
$('.hidden a.hide-description').prependTo('.description');
$('a.hide-description').click(function() {
+ $(this).tooltip('hide');
$('.description').slideUp('fast');
return false;
});
$('.hidden a.toggle-description').appendTo('.page-header h1');
$('a.toggle-description').click(function() {
+ $(this).tooltip('hide');
$('.description').slideToggle('fast');
return false;
});
@@ -68,6 +70,7 @@ $(function() {
});
$('a.resize').click(function() {
+ $(this).tooltip('hide');
if ($(this).find('span.glyphicon-resize-full').size()) {
$(this).find('span.glyphicon').addClass('glyphicon-resize-small').removeClass('glyphicon-resize-full');
$('.container').addClass('container-fluid').removeClass('container');
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 1fe48ad12c..87d3b6b06b 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -26,6 +26,7 @@ import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Cr
import {JobsListController} from './controllers/Jobs';
import {PortalController} from './controllers/Portal';
import systemTracking from './system-tracking/main';
+import inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/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 OrganizationsAdd from './organizations/add/organizations-add.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 {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
@@ -88,6 +89,7 @@ var tower = angular.module('Tower', [
RestServices.name,
browserData.name,
systemTracking.name,
+ inventories.name,
inventoryScripts.name,
organizations.name,
permissions.name,
@@ -182,7 +184,6 @@ var tower = angular.module('Tower', [
'LogViewerStatusDefinition',
'StandardOutHelper',
'LogViewerOptionsDefinition',
- 'EventViewerHelper',
'JobDetailHelper',
'SocketIO',
'lrInfiniteScroll',
@@ -213,6 +214,8 @@ var tower = angular.module('Tower', [
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(function($injector){
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', {
url: '/organizations/:organization_id/admins',
templateUrl: urlPrefix + 'partials/organizations.html',
diff --git a/awx/ui/client/src/controllers/Inventories.js b/awx/ui/client/src/controllers/Inventories.js
deleted file mode 100644
index 62dfb5b03b..0000000000
--- a/awx/ui/client/src/controllers/Inventories.js
+++ /dev/null
@@ -1,1296 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-/**
- * @ngdoc function
- * @name controllers.function:Inventories
- * @description This controller's for the Inventory page
-*/
-
-import '../job-templates/main';
-
-export function InventoriesList($scope, $rootScope, $location, $log,
- $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
- generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
- ClearScope, ProcessErrors, GetBasePath, Wait,
- EditInventoryProperties, 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
. 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 = "
\n";
- html += "\n";
- html += "";
- html += "Status ";
- html += "Finished ";
- html += "Name ";
- html += " \n";
- html += " \n";
- html += "\n";
-
- data.results.forEach(function(row) {
- html += "\n";
- html += " \n";
- html += "" + ($filter('longDate')(row.finished)).replace(/ /,' ') + " ";
- html += "" + ellipsis(row.name) + " ";
- html += " \n";
- });
- html += " \n";
- html += "
\n";
- }
- else {
- html = "
No recent job data available for this inventory.
\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 = "
\n";
- html += "\n";
- html += "";
- html += "Status ";
- html += "Last Sync ";
- html += "Group ";
- html += " ";
- html += " \n";
- html += "\n";
- data.results.forEach( function(row) {
- if (row.related.last_update) {
- html += "";
- html += " ";
- html += "" + ($filter('longDate')(row.last_updated)).replace(/ /,' ') + " ";
- html += "" + ellipsis(row.summary_fields.group.name) + " ";
- html += " \n";
- }
- else {
- html += "";
- html += " ";
- html += "NA ";
- html += "" + ellipsis(row.summary_fields.group.name) + " ";
- html += " \n";
- }
- });
- html += " \n";
- html += "
\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.editInventoryProperties = function (inventory_id) {
- EditInventoryProperties({ scope: $scope, inventory_id: inventory_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: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
',
- 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');
- };
-}
-
-InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', 'generateList',
- 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
- 'GetBasePath', 'Wait', 'EditInventoryProperties', 'Find', 'Empty', '$state'
-];
-
-
-export 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.well = true;
- 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');
- };
-}
-
-InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location',
- '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
- 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
- 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
- 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state'
-];
-
-export 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.well = true;
- 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: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
',
- action: action,
- actionText: 'DELETE'
- });
-
- };
-
-}
-
-InventoriesEdit.$inject = ['$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'
-];
-
-
-
-export 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) {
-
- 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');
- GroupsEdit({
- scope: $scope,
- inventory_id: $scope.inventory.id,
- group_id: $scope.selected_group_id,
- mode: 'add'
- });
- };
-
- $scope.editGroup = function (id) {
- PreviousSearchParams = Store('group_current_search_params');
- GroupsEdit({
- scope: $scope,
- inventory_id: $scope.inventory.id,
- group_id: id,
- mode: 'edit'
- });
- };
-
- // 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
' +
- group.name + ' Click the
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 () {
- HostsEdit({
- host_scope: hostScope,
- group_scope: $scope,
- mode: 'add',
- host_id: null,
- selected_group_id: $scope.selected_group_id,
- inventory_id: $scope.inventory.id
- });
- };
-
- hostScope.editHost = function (host_id) {
- HostsEdit({
- host_scope: hostScope,
- group_scope: $scope,
- mode: 'edit',
- host_id: host_id,
- inventory_id: $scope.inventory.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.restoreSearch = function() {
- SearchInit({
- scope: hostScope,
- set: PreviousSearchParams.set,
- list: PreviousSearchParams.list,
- url: PreviousSearchParams.defaultUrl,
- iterator: PreviousSearchParams.iterator,
- sort_order: PreviousSearchParams.sort_order,
- setWidgets: false
- });
- hostScope.search('host');
- };*/
-
- 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();
- }
- );
-}
-
-
-InventoriesManage.$inject = ['$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'
-];
diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js
index 84aaf804e8..4b40179f54 100644
--- a/awx/ui/client/src/forms/Credentials.js
+++ b/awx/ui/client/src/forms/Credentials.js
@@ -169,7 +169,7 @@ export default
"host": {
labelBind: 'hostLabel',
type: 'text',
- ngShow: "kind.value == 'vmware' || kind.value == 'openstack' || kind.value === 'openstack_v3'",
+ ngShow: "kind.value == 'vmware' || kind.value == 'openstack'",
awPopOverWatch: "hostPopOver",
awPopOver: "set in helpers/credentials",
dataTitle: 'Host',
@@ -243,7 +243,7 @@ export default
"password": {
labelBind: 'passwordLabel',
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,
editRequired: false,
ask: false,
@@ -338,10 +338,10 @@ export default
"project": {
labelBind: 'projectLabel',
type: 'text',
- ngShow: "kind.value == 'gce' || kind.value == 'openstack' || kind.value == 'openstack_v3'",
+ ngShow: "kind.value == 'gce' || kind.value == 'openstack'",
awPopOverWatch: "projectPopOver",
awPopOver: "set in helpers/credentials",
- dataTitle: 'Project ID',
+ dataTitle: 'Project Name',
dataPlacement: 'right',
dataContainer: "body",
addRequired: false,
@@ -355,18 +355,17 @@ export default
"domain": {
labelBind: 'domainLabel',
type: 'text',
- ngShow: "kind.value == 'openstack_v3'",
- awPopOverWatch: "domainPopOver",
- awPopOver: "set in helpers/credentials",
+ ngShow: "kind.value == 'openstack'",
+ awPopOver: "
OpenStack domains define administrative " +
+ "boundaries. It is only needed for Keystone v3 authentication URLs. " +
+ "Common scenarios include:
v2 URLs - leave blank " +
+ "v3 default - set to 'default' " +
+ "v3 multi-domain - your domain name",
dataTitle: 'Domain Name',
dataPlacement: 'right',
dataContainer: "body",
addRequired: false,
editRequired: false,
- awRequiredWhen: {
- variable: 'domain_required',
- init: false
- },
subForm: 'credentialSubForm'
},
"vault_password": {
diff --git a/awx/ui/client/src/forms/Source.js b/awx/ui/client/src/forms/Source.js
index 86e6db5477..1eee07b344 100644
--- a/awx/ui/client/src/forms/Source.js
+++ b/awx/ui/client/src/forms/Source.js
@@ -169,8 +169,7 @@ export default
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && (source.value == 'vmware' || " +
- "source.value == 'openstack' || " +
- "source.value == 'openstack_v3')",
+ "source.value == 'openstack')",
type: 'textarea',
addRequired: false,
class: 'Form-textAreaLabel',
diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js
index e8190ea50e..aae8a17225 100644
--- a/awx/ui/client/src/helpers.js
+++ b/awx/ui/client/src/helpers.js
@@ -9,7 +9,6 @@ import './lists';
import Children from "./helpers/Children";
import Credentials from "./helpers/Credentials";
-import EventViewer from "./helpers/EventViewer";
import Events from "./helpers/Events";
import Groups from "./helpers/Groups";
import Hosts from "./helpers/Hosts";
@@ -42,7 +41,6 @@ import ActivityStreamHelper from "./helpers/ActivityStream";
export
{ Children,
Credentials,
- EventViewer,
Events,
Groups,
Hosts,
diff --git a/awx/ui/client/src/helpers/Credentials.js b/awx/ui/client/src/helpers/Credentials.js
index f1af37a011..653ad6b4bf 100644
--- a/awx/ui/client/src/helpers/Credentials.js
+++ b/awx/ui/client/src/helpers/Credentials.js
@@ -74,7 +74,6 @@ angular.module('CredentialsHelper', ['Utilities'])
scope.project_required = false;
scope.passwordLabel = 'Password (API Key)';
scope.projectPopOver = "
The project value
";
- scope.domainPopOver = "
The domain name
";
scope.hostPopOver = "
The host value
";
if (!Empty(scope.kind)) {
@@ -126,32 +125,17 @@ angular.module('CredentialsHelper', ['Utilities'])
break;
case 'openstack':
scope.hostLabel = "Host (Authentication URL)";
- scope.projectLabel = "Project (Tenet Name/ID)";
- scope.password_required = true;
- scope.project_required = true;
- scope.host_required = true;
- scope.username_required = true;
- scope.projectPopOver = "
This is the tenant name " +
- "or tenant id. This value is usually the same " +
- " as the username.
";
- scope.hostPopOver = "
The host to authenticate with." +
- " For example, https://openstack.business.com/v2.0/";
- case 'openstack_v3':
- scope.hostLabel = "Host (Authentication URL)";
- scope.projectLabel = "Project (Tenet Name/ID)";
+ scope.projectLabel = "Project (Tenant Name)";
scope.domainLabel = "Domain Name";
scope.password_required = true;
scope.project_required = true;
- scope.domain_required = true;
scope.host_required = true;
scope.username_required = true;
- scope.projectPopOver = "
This is the tenant name " +
- "or tenant id. This value is usually the same " +
+ scope.projectPopOver = "
This is the tenant name. " +
+ " This value is usually the same " +
" as the username.
";
scope.hostPopOver = "
The host to authenticate with." +
- " For example, https://openstack.business.com/v3
";
- scope.domainPopOver = "
Domain used for Keystone v3 " +
- " identity service.
";
+ "
For example, https://openstack.business.com/v2.0/";
break;
}
}
diff --git a/awx/ui/client/src/helpers/EventViewer.js b/awx/ui/client/src/helpers/EventViewer.js
deleted file mode 100644
index cb075fa5e9..0000000000
--- a/awx/ui/client/src/helpers/EventViewer.js
+++ /dev/null
@@ -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('
');
- }
-
- 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 += "
" + key + ": " + obj[key] + " ";
- }
- else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
- html += "
" + key + ": [";
- for (i = 0; i < obj[key].length; i++) {
- html += obj[key][i] + ",";
- }
- html = html.replace(/,$/,'');
- html += "] \n";
- }
- else if (typeof obj[key] === "object") {
- html += "
" + key + ": \n\n" + parseObject(obj[key]) + " \n
\n\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 += "
" + label + ": ";
- if (key === "status") {
- html += " " + 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 += "" + itm + " ";
- }
- else {
- if( typeof itm === "string"){
- if(itm.indexOf('<') > -1 || itm.indexOf('>') > -1){
- itm = $filter('sanitize')(itm);
- }
- }
- html += "" + itm + " ";
- }
-
- html += " \n";
- }
- else if (typeof itm === "object" && Array.isArray(itm)) {
- html += "
" + label + ": [";
- for (i = 0; i < itm.length; i++) {
- html += itm[i] + ",";
- }
- html = html.replace(/,$/,'');
- html += "] \n";
- }
- else if (typeof itm === "object") {
- html += "
" + label + ": \n\n" + parseObject(itm) + " \n
\n\n";
- }
- return html;
- }
-
- function parseJSON(obj) {
- var h, html = '', key, keys, found = false, string_warnings = "", string_cmd = "";
- if (typeof obj === "object") {
- html += "
\n";
- html += "\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 += " \n";
- html += "
\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 = "
\n" +
- "" +
- "
\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 = "
" + val + " \n";
- $('#' + id).empty().html(html);
- };
- }]);
diff --git a/awx/ui/client/src/helpers/Groups.js b/awx/ui/client/src/helpers/Groups.js
index 4e95d96857..eeebb9d8bf 100644
--- a/awx/ui/client/src/helpers/Groups.js
+++ b/awx/ui/client/src/helpers/Groups.js
@@ -305,8 +305,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
field_id: 'source_extra_vars', onReady: callback });
}
if(scope.source.value==="vmware" ||
- scope.source.value==="openstack" ||
- scope.source.value==="openstack_v3"){
+ scope.source.value==="openstack"){
scope.inventory_variables = (Empty(scope.source_vars)) ? "---" : scope.source_vars;
ParseTypeChange({ scope: scope, variable: 'inventory_variables', parse_variable: form.fields.inventory_variables.parseTypeName,
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 === 'azure' ||
scope.source.value === 'vmware' ||
- scope.source.value === 'openstack' ||
- scope.source.value === 'openstack_v3') {
+ scope.source.value === 'openstack') {
if (scope.source.value === 'ec2') {
kind = 'aws';
} 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,
field_id: 'source_source_vars', onReady: waitStop });
} else if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
- sources_scope.source.value === 'openstack' ||
- sources_scope.source.value === 'openstack_v3')) {
+ sources_scope.source.value === 'openstack')) {
Wait('start');
ParseTypeChange({ scope: sources_scope, variable: 'inventory_variables', parse_variable: SourceForm.fields.inventory_variables.parseTypeName,
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' ||
- sources_scope.source.value === 'openstack' ||
- sources_scope.source.value === 'openstack_v3')) {
+ sources_scope.source.value === 'openstack')) {
data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
}
diff --git a/awx/ui/client/src/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js
index f55b1199d2..6a7b864e02 100644
--- a/awx/ui/client/src/helpers/Hosts.js
+++ b/awx/ui/client/src/helpers/Hosts.js
@@ -437,10 +437,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'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,
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON,
- ParseVariableString, CreateDialog, TextareaResize) {
+ ParseVariableString, CreateDialog, TextareaResize, ParamPass) {
return function(params) {
var parent_scope = params.host_scope,
diff --git a/awx/ui/client/src/inventories/add/inventory-add.controller.js b/awx/ui/client/src/inventories/add/inventory-add.controller.js
new file mode 100644
index 0000000000..bd3cde3041
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/inventory-add.controller.js
@@ -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]
diff --git a/awx/ui/client/src/inventories/add/inventory-add.route.js b/awx/ui/client/src/inventories/add/inventory-add.route.js
new file mode 100644
index 0000000000..50ba5b26a6
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/inventory-add.route.js
@@ -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();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/add/main.js b/awx/ui/client/src/inventories/add/main.js
new file mode 100644
index 0000000000..e12ff940ac
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js
new file mode 100644
index 0000000000..f7cb6f2601
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js
@@ -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: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
',
+ 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,
+];
diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.route.js b/awx/ui/client/src/inventories/edit/inventory-edit.route.js
new file mode 100644
index 0000000000..d721ba92a4
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/inventory-edit.route.js
@@ -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();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/edit/main.js b/awx/ui/client/src/inventories/edit/main.js
new file mode 100644
index 0000000000..28c99819b7
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/partials/inventories.html b/awx/ui/client/src/inventories/inventories.partial.html
similarity index 100%
rename from awx/ui/client/src/partials/inventories.html
rename to awx/ui/client/src/inventories/inventories.partial.html
diff --git a/awx/ui/client/src/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js
new file mode 100644
index 0000000000..947b1c0341
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js
@@ -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
. 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 = "
\n";
+ html += "\n";
+ html += "";
+ html += "Status ";
+ html += "Finished ";
+ html += "Name ";
+ html += " \n";
+ html += " \n";
+ html += "\n";
+
+ data.results.forEach(function(row) {
+ html += "\n";
+ html += " \n";
+ html += "" + ($filter('longDate')(row.finished)).replace(/ /,' ') + " ";
+ html += "" + ellipsis(row.name) + " ";
+ html += " \n";
+ });
+ html += " \n";
+ html += "
\n";
+ }
+ else {
+ html = "
No recent job data available for this inventory.
\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 = "
\n";
+ html += "\n";
+ html += "";
+ html += "Status ";
+ html += "Last Sync ";
+ html += "Group ";
+ html += " ";
+ html += " \n";
+ html += "\n";
+ data.results.forEach( function(row) {
+ if (row.related.last_update) {
+ html += "";
+ html += " ";
+ html += "" + ($filter('longDate')(row.last_updated)).replace(/ /,' ') + " ";
+ html += "" + ellipsis(row.summary_fields.group.name) + " ";
+ html += " \n";
+ }
+ else {
+ html += "";
+ html += " ";
+ html += "NA ";
+ html += "" + ellipsis(row.summary_fields.group.name) + " ";
+ html += " \n";
+ }
+ });
+ html += " \n";
+ html += "
\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: '
Are you sure you want to delete the inventory below?
' + $filter('sanitize')(name) + '
',
+ 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];
diff --git a/awx/ui/client/src/inventories/list/inventory-list.route.js b/awx/ui/client/src/inventories/list/inventory-list.route.js
new file mode 100644
index 0000000000..2804370249
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/inventory-list.route.js
@@ -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();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/list/main.js b/awx/ui/client/src/inventories/list/main.js
new file mode 100644
index 0000000000..4d67816cd7
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js
new file mode 100644
index 0000000000..52f9986ef3
--- /dev/null
+++ b/awx/ui/client/src/inventories/main.js
@@ -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,
+]);
diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js
new file mode 100644
index 0000000000..508a74d4c2
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js
@@ -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
' +
+ group.name + ' Click the
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,
+];
diff --git a/awx/ui/client/src/partials/inventory-manage.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html
similarity index 99%
rename from awx/ui/client/src/partials/inventory-manage.html
rename to awx/ui/client/src/inventories/manage/inventory-manage.partial.html
index ecae801c20..f465ef47c0 100644
--- a/awx/ui/client/src/partials/inventory-manage.html
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html
@@ -10,9 +10,6 @@
-
-
1. Copy or move ?
diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js
new file mode 100644
index 0000000000..62306cd91b
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js
@@ -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();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/manage/main.js b/awx/ui/client/src/inventories/manage/main.js
new file mode 100644
index 0000000000..075ba36887
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js
new file mode 100644
index 0000000000..158d6653ec
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js
@@ -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
' +
+ $filter('sanitize')(sources_scope.summary_fields.group.name) + ' . 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
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js
new file mode 100644
index 0000000000..b4c3fbff73
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js
@@ -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
+ };
+ }
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html
new file mode 100644
index 0000000000..eacbf795c9
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+ Cancel
+
+
+
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/main.js b/awx/ui/client/src/inventories/manage/manage-groups/main.js
new file mode 100644
index 0000000000..b015b64628
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html
new file mode 100644
index 0000000000..e39e0984ac
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js
new file mode 100644
index 0000000000..e83c934ea9
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js
@@ -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();
+ }]
+ }
+ },
+
+};
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js
new file mode 100644
index 0000000000..90190143f3
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js
@@ -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
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js
new file mode 100644
index 0000000000..ac10ea1dfd
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js
@@ -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
+ };
+ }
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html
new file mode 100644
index 0000000000..31c2249234
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+ Cancel
+
+
+
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/main.js b/awx/ui/client/src/inventories/manage/manage-hosts/main.js
new file mode 100644
index 0000000000..e90954f2de
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/main.js
@@ -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);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html
new file mode 100644
index 0000000000..f145702511
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js
new file mode 100644
index 0000000000..8e97578aa5
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js
@@ -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();
+ }]
+ }
+ },
+
+};
diff --git a/awx/ui/client/src/inventories/manage/manage.block.less b/awx/ui/client/src/inventories/manage/manage.block.less
new file mode 100644
index 0000000000..772423c2e4
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage.block.less
@@ -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%;
+ }
+}
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html
new file mode 100644
index 0000000000..66a60fcec9
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event-details.partial.html
@@ -0,0 +1,49 @@
+
+
+
+
+
STATUS
+
+
+
+
+ {{event.status || "No result found"}}
+
+
+
+ ID
+ {{event.id || "No result found"}}
+
+
+ CREATED
+ {{event.created || "No result found"}}
+
+
+ PLAY
+ {{event.play || "No result found"}}
+
+
+ TASK
+ {{event.task || "No result found"}}
+
+
+ MODULE
+ {{event.event_data.res.invocation.module_name || "No result found"}}
+
+
+
+
RESULTS
+
+
+ {{key}}
+ {{value}}
+
+
+
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html
new file mode 100644
index 0000000000..a574043dbd
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event-json.partial.html
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html
new file mode 100644
index 0000000000..db106deb1b
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event-modal.partial.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+ Details
+ JSON
+ Standard Out
+ Timing
+
+
+
+
+
+
+ Prev
+ Next
+ Close
+
+
+
+
+
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html
new file mode 100644
index 0000000000..436c25262a
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event-stdout.partial.html
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html
new file mode 100644
index 0000000000..06171bd1c5
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event-timing.partial.html
@@ -0,0 +1 @@
+
timing
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less
new file mode 100644
index 0000000000..04b25f7419
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less
@@ -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;
+ }
+}
diff --git a/awx/ui/client/src/job-detail/host-event/host-event.controller.js b/awx/ui/client/src/job-detail/host-event/host-event.controller.js
new file mode 100644
index 0000000000..ac46279d0f
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event.controller.js
@@ -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();
+ }];
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/host-event.route.js b/awx/ui/client/src/job-detail/host-event/host-event.route.js
new file mode 100644
index 0000000000..7d4f1cc011
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/host-event.route.js
@@ -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}
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-event/main.js b/awx/ui/client/src/job-detail/host-event/main.js
new file mode 100644
index 0000000000..c2b82530a1
--- /dev/null
+++ b/awx/ui/client/src/job-detail/host-event/main.js
@@ -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);
+ }]);
\ No newline at end of file
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.block.less b/awx/ui/client/src/job-detail/host-events/host-events.block.less
index 17d318dc89..1a92e8933c 100644
--- a/awx/ui/client/src/job-detail/host-events/host-events.block.less
+++ b/awx/ui/client/src/job-detail/host-events/host-events.block.less
@@ -16,7 +16,7 @@
color: @changed;
}
.HostEvents-status--failed{
- color: @warning;
+ color: @default-err;
}
.HostEvents-status--skipped{
color: @skipped;
@@ -47,6 +47,7 @@
padding-bottom: 15px;
}
.HostEvents-title{
+ text-transform: uppercase;
color: @default-interface-txt;
font-weight: 600;
}
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.controller.js b/awx/ui/client/src/job-detail/host-events/host-events.controller.js
index a3a1c8faaf..c97da5eb62 100644
--- a/awx/ui/client/src/job-detail/host-events/host-events.controller.js
+++ b/awx/ui/client/src/job-detail/host-events/host-events.controller.js
@@ -6,13 +6,14 @@
export default
['$stateParams', '$scope', '$rootScope', '$state', 'Wait',
- 'JobDetailService', 'CreateSelect2',
+ 'JobDetailService', 'CreateSelect2', 'hosts',
function($stateParams, $scope, $rootScope, $state, Wait,
- JobDetailService, CreateSelect2){
+ JobDetailService, CreateSelect2, hosts){
// pagination not implemented yet, but it'll depend on this
$scope.page_size = $stateParams.page_size;
+ $scope.processEventStatus = JobDetailService.processEventStatus;
$scope.activeFilter = $stateParams.filter || null;
$scope.search = function(){
@@ -39,6 +40,7 @@
var filter = function(filter){
Wait('start');
+
if (filter == 'all'){
return JobDetailService.getRelatedJobEvents($stateParams.id, {
host_name: $stateParams.hostName,
@@ -104,47 +106,17 @@
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(){
+ $scope.hostName = $stateParams.hostName;
// create filter dropdown
+ console.log($stateParams)
CreateSelect2({
element: '.HostEvents-select',
multiple: false
});
// process the filter if one was passed
if ($stateParams.filter){
+ Wait('start');
filter($stateParams.filter).success(function(res){
$scope.results = res.results;
Wait('stop');
@@ -152,25 +124,11 @@
});;
}
else{
- Wait('start');
- JobDetailService.getRelatedJobEvents($stateParams.id, {
- 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.results = hosts.data.results;
+ $('#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();
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.partial.html b/awx/ui/client/src/job-detail/host-events/host-events.partial.html
index ff2d21714a..9d0ccca40f 100644
--- a/awx/ui/client/src/job-detail/host-events/host-events.partial.html
+++ b/awx/ui/client/src/job-detail/host-events/host-events.partial.html
@@ -3,9 +3,9 @@
@@ -29,7 +29,6 @@
-
@@ -37,11 +36,10 @@
-
+
{{event.status}}
- {{event.host_name}}
{{event.play}}
{{event.task}}
@@ -56,7 +54,7 @@
diff --git a/awx/ui/client/src/job-detail/host-events/host-events.route.js b/awx/ui/client/src/job-detail/host-events/host-events.route.js
index ebb2bb7bdd..4e2c6d4e93 100644
--- a/awx/ui/client/src/job-detail/host-events/host-events.route.js
+++ b/awx/ui/client/src/job-detail/host-events/host-events.route.js
@@ -1,5 +1,5 @@
/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
+ * Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
@@ -25,6 +25,11 @@ export default {
resolve: {
features: ['FeaturesService', function(FeaturesService) {
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]})
+ }]
}
};
diff --git a/awx/ui/client/src/job-detail/job-detail.controller.js b/awx/ui/client/src/job-detail/job-detail.controller.js
index 1383f04c35..d06fa5881f 100644
--- a/awx/ui/client/src/job-detail/job-detail.controller.js
+++ b/awx/ui/client/src/job-detail/job-detail.controller.js
@@ -18,7 +18,7 @@ export default
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
- 'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EventViewer',
+ 'EditSchedule', 'ParseTypeChange', 'JobDetailService',
function(
$location, $rootScope, $filter, $scope, $compile, $stateParams,
$log, ClearScope, GetBasePath, Wait, ProcessErrors,
@@ -27,7 +27,7 @@ export default
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
PlaybookRun, LoadPlays, LoadTasks, LoadHosts,
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
- fieldLabels, EditSchedule, ParseTypeChange, JobDetailService, EventViewer
+ fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
) {
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) {
scope.removeDeleteFinished();
}
diff --git a/awx/ui/client/src/job-detail/job-detail.partial.html b/awx/ui/client/src/job-detail/job-detail.partial.html
index 8daba354b5..2ced4bf551 100644
--- a/awx/ui/client/src/job-detail/job-detail.partial.html
+++ b/awx/ui/client/src/job-detail/job-detail.partial.html
@@ -343,7 +343,9 @@
- {{ result.name }} {{ result.name }}
+
+ {{ result.name }} {{ result.name }}
+
{{ result.item }}
{{ result.msg }}
@@ -422,7 +424,7 @@
- {{ host.name }}
+ {{ host.name }}
{{ host.ok }}
@@ -480,8 +482,6 @@
-
-
diff --git a/awx/ui/client/src/job-detail/job-detail.service.js b/awx/ui/client/src/job-detail/job-detail.service.js
index 8597fff9f1..381ff56c18 100644
--- a/awx/ui/client/src/job-detail/job-detail.service.js
+++ b/awx/ui/client/src/job-detail/job-detail.service.js
@@ -2,13 +2,85 @@ export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
- /*
+ /*
For ES6
it might be useful to set some default params here, e.g.
getJobHostSummaries: function(id, page_size=200, order='host_name'){}
without ES6, we'd have to supply defaults like this:
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
// e.g.
diff --git a/awx/ui/client/src/job-detail/main.js b/awx/ui/client/src/job-detail/main.js
index 8a9fc30aff..f497b76677 100644
--- a/awx/ui/client/src/job-detail/main.js
+++ b/awx/ui/client/src/job-detail/main.js
@@ -8,10 +8,12 @@ import route from './job-detail.route';
import controller from './job-detail.controller';
import service from './job-detail.service';
import hostEvents from './host-events/main';
+import hostEvent from './host-event/main';
export default
angular.module('jobDetail', [
- hostEvents.name
+ hostEvents.name,
+ hostEvent.name
])
.controller('JobDetailController', controller)
.service('JobDetailService', service)
diff --git a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js
index d43fbff3b0..f949d142ad 100644
--- a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js
+++ b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js
@@ -23,7 +23,8 @@
set: function(data){
var defaultUrl = GetBasePath('job_templates');
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])
.success(function(res){
return res
@@ -32,6 +33,10 @@
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
});
+ },
+ buildName: function(name){
+ var result = name.split('@')[0];
+ return result
}
}
}
diff --git a/awx/ui/client/src/lists/HomeGroups.js b/awx/ui/client/src/lists/HomeGroups.js
index 1c21fb6268..ad7180dff0 100644
--- a/awx/ui/client/src/lists/HomeGroups.js
+++ b/awx/ui/client/src/lists/HomeGroups.js
@@ -76,9 +76,6 @@ export default
},{
name: "OpenStack",
value: "openstack"
- },{
- name: "OpenStack V3",
- value: "openstack_v3"
}],
sourceModel: 'inventory_source',
sourceField: 'source',
@@ -87,7 +84,7 @@ export default
has_external_source: {
label: 'Has external source?',
searchType: 'in',
- searchValue: 'ec2,rax,vmware,azure,gce,openstack,openstack_v3',
+ searchValue: 'ec2,rax,vmware,azure,gce,openstack',
searchOnly: true,
sourceModel: 'inventory_source',
sourceField: 'source'
diff --git a/awx/ui/client/src/lists/InventoryGroups.js b/awx/ui/client/src/lists/InventoryGroups.js
index 3b221e54e0..53881f3d7c 100644
--- a/awx/ui/client/src/lists/InventoryGroups.js
+++ b/awx/ui/client/src/lists/InventoryGroups.js
@@ -51,9 +51,6 @@ export default
},{
name: "OpenStack",
value: "openstack"
- },{
- name: "OpenStack V3",
- value: "openstack_v3"
}],
sourceModel: 'inventory_source',
sourceField: 'source',
@@ -62,7 +59,7 @@ export default
has_external_source: {
label: 'Has external source?',
searchType: 'in',
- searchValue: 'ec2,rax,vmware,azure,gce,openstack,openstack_v3',
+ searchValue: 'ec2,rax,vmware,azure,gce,openstack',
searchOnly: true,
sourceModel: 'inventory_source',
sourceField: 'source'
diff --git a/awx/ui/client/src/partials/eventviewer.html b/awx/ui/client/src/partials/eventviewer.html
deleted file mode 100644
index 941e9d80d6..0000000000
--- a/awx/ui/client/src/partials/eventviewer.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js
index 7aeae22b6b..e8015b4cc5 100644
--- a/awx/ui/client/src/shared/Utilities.js
+++ b/awx/ui/client/src/shared/Utilities.js
@@ -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
+ }
+});
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js
index 8525ea7997..0f3ea7445a 100644
--- a/awx/ui/client/src/shared/form-generator.js
+++ b/awx/ui/client/src/shared/form-generator.js
@@ -1394,13 +1394,18 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
"ng-if=is_superuser>Admin";
}
html += "\n";
+ if(options.cancelButton !== undefined && options.cancelButton === false) {
+ html += "";
+ html += "
";
+ } else {
+ html += "";
+ html += "";
+ html += " ";
+ html += "
\n";
+ }
+ html += "\n"; //end of Form-header
- html += "";
- html += "";
- html += " ";
- html += "
\n";
- html += "\n"; //end of Form-header
if (this.form.tabs) {
var collection;
diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less
index 87c44fe056..a296af7127 100644
--- a/awx/ui/client/src/shared/layouts/one-plus-two.less
+++ b/awx/ui/client/src/shared/layouts/one-plus-two.less
@@ -61,7 +61,8 @@
}
.OnePlusTwo-left--detailsLabel {
- width: 140px;
+ word-wrap: break-word;
+ width: 170px;
display: inline-block;
color: @default-interface-txt;
text-transform: uppercase;
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index c5cdc2a393..1ad77767cb 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -936,6 +936,1162 @@
}
}
},
+ "broccoli-babel-transpiler": {
+ "version": "5.5.0",
+ "from": "broccoli-babel-transpiler@*",
+ "resolved": "https://registry.npmjs.org/broccoli-babel-transpiler/-/broccoli-babel-transpiler-5.5.0.tgz",
+ "dependencies": {
+ "babel-core": {
+ "version": "5.8.38",
+ "from": "babel-core@>=5.0.0 <6.0.0",
+ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.38.tgz",
+ "dependencies": {
+ "babel-plugin-constant-folding": {
+ "version": "1.0.1",
+ "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz"
+ },
+ "babel-plugin-dead-code-elimination": {
+ "version": "1.0.2",
+ "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz"
+ },
+ "babel-plugin-eval": {
+ "version": "1.0.1",
+ "from": "babel-plugin-eval@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz"
+ },
+ "babel-plugin-inline-environment-variables": {
+ "version": "1.0.1",
+ "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz"
+ },
+ "babel-plugin-jscript": {
+ "version": "1.0.4",
+ "from": "babel-plugin-jscript@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz"
+ },
+ "babel-plugin-member-expression-literals": {
+ "version": "1.0.1",
+ "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz"
+ },
+ "babel-plugin-property-literals": {
+ "version": "1.0.1",
+ "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz"
+ },
+ "babel-plugin-proto-to-assign": {
+ "version": "1.0.4",
+ "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz"
+ },
+ "babel-plugin-react-constant-elements": {
+ "version": "1.0.3",
+ "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz"
+ },
+ "babel-plugin-react-display-name": {
+ "version": "1.0.3",
+ "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz"
+ },
+ "babel-plugin-remove-console": {
+ "version": "1.0.1",
+ "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz"
+ },
+ "babel-plugin-remove-debugger": {
+ "version": "1.0.1",
+ "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz"
+ },
+ "babel-plugin-runtime": {
+ "version": "1.0.7",
+ "from": "babel-plugin-runtime@>=1.0.7 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz"
+ },
+ "babel-plugin-undeclared-variables-check": {
+ "version": "1.0.2",
+ "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz",
+ "dependencies": {
+ "leven": {
+ "version": "1.0.2",
+ "from": "leven@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz"
+ }
+ }
+ },
+ "babel-plugin-undefined-to-void": {
+ "version": "1.1.6",
+ "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz"
+ },
+ "babylon": {
+ "version": "5.8.38",
+ "from": "babylon@>=5.8.38 <6.0.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz"
+ },
+ "bluebird": {
+ "version": "2.10.2",
+ "from": "bluebird@>=2.9.33 <3.0.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz"
+ },
+ "convert-source-map": {
+ "version": "1.2.0",
+ "from": "convert-source-map@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.2.0.tgz"
+ },
+ "core-js": {
+ "version": "1.2.6",
+ "from": "core-js@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
+ },
+ "detect-indent": {
+ "version": "3.0.1",
+ "from": "detect-indent@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz",
+ "dependencies": {
+ "get-stdin": {
+ "version": "4.0.1",
+ "from": "get-stdin@>=4.0.1 <5.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
+ }
+ }
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "from": "esutils@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+ },
+ "fs-readdir-recursive": {
+ "version": "0.1.2",
+ "from": "fs-readdir-recursive@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz"
+ },
+ "globals": {
+ "version": "6.4.1",
+ "from": "globals@>=6.4.0 <7.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz"
+ },
+ "home-or-tmp": {
+ "version": "1.0.0",
+ "from": "home-or-tmp@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz",
+ "dependencies": {
+ "os-tmpdir": {
+ "version": "1.0.1",
+ "from": "os-tmpdir@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"
+ },
+ "user-home": {
+ "version": "1.1.1",
+ "from": "user-home@>=1.1.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz"
+ }
+ }
+ },
+ "is-integer": {
+ "version": "1.0.6",
+ "from": "is-integer@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz",
+ "dependencies": {
+ "is-finite": {
+ "version": "1.0.1",
+ "from": "is-finite@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
+ "dependencies": {
+ "number-is-nan": {
+ "version": "1.0.0",
+ "from": "number-is-nan@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "js-tokens": {
+ "version": "1.0.1",
+ "from": "js-tokens@1.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz"
+ },
+ "json5": {
+ "version": "0.4.0",
+ "from": "json5@>=0.4.0 <0.5.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz"
+ },
+ "minimatch": {
+ "version": "2.0.10",
+ "from": "minimatch@>=2.0.3 <3.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.3",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "output-file-sync": {
+ "version": "1.1.1",
+ "from": "output-file-sync@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.1.tgz",
+ "dependencies": {
+ "xtend": {
+ "version": "4.0.1",
+ "from": "xtend@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
+ }
+ }
+ },
+ "path-exists": {
+ "version": "1.0.0",
+ "from": "path-exists@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz"
+ },
+ "path-is-absolute": {
+ "version": "1.0.0",
+ "from": "path-is-absolute@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+ },
+ "private": {
+ "version": "0.1.6",
+ "from": "private@>=0.1.6 <0.2.0",
+ "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
+ },
+ "regenerator": {
+ "version": "0.8.40",
+ "from": "regenerator@0.8.40",
+ "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
+ "dependencies": {
+ "commoner": {
+ "version": "0.10.4",
+ "from": "commoner@>=0.10.3 <0.11.0",
+ "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.4.tgz",
+ "dependencies": {
+ "detective": {
+ "version": "4.3.1",
+ "from": "detective@>=4.3.1 <5.0.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-4.3.1.tgz",
+ "dependencies": {
+ "acorn": {
+ "version": "1.2.2",
+ "from": "acorn@>=1.0.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz"
+ },
+ "defined": {
+ "version": "1.0.0",
+ "from": "defined@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz"
+ }
+ }
+ },
+ "glob": {
+ "version": "5.0.15",
+ "from": "glob@>=5.0.15 <6.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "dependencies": {
+ "inflight": {
+ "version": "1.0.4",
+ "from": "inflight@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ },
+ "inherits": {
+ "version": "2.0.1",
+ "from": "inherits@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+ },
+ "once": {
+ "version": "1.3.3",
+ "from": "once@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.3",
+ "from": "graceful-fs@>=4.1.2 <5.0.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz"
+ },
+ "iconv-lite": {
+ "version": "0.4.13",
+ "from": "iconv-lite@>=0.4.5 <0.5.0",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
+ },
+ "q": {
+ "version": "1.4.1",
+ "from": "q@>=1.1.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz"
+ }
+ }
+ },
+ "defs": {
+ "version": "1.1.1",
+ "from": "defs@>=1.1.0 <1.2.0",
+ "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz",
+ "dependencies": {
+ "alter": {
+ "version": "0.2.0",
+ "from": "alter@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz",
+ "dependencies": {
+ "stable": {
+ "version": "0.1.5",
+ "from": "stable@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz"
+ }
+ }
+ },
+ "ast-traverse": {
+ "version": "0.1.1",
+ "from": "ast-traverse@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz"
+ },
+ "breakable": {
+ "version": "1.0.0",
+ "from": "breakable@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz"
+ },
+ "simple-fmt": {
+ "version": "0.1.0",
+ "from": "simple-fmt@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz"
+ },
+ "simple-is": {
+ "version": "0.2.0",
+ "from": "simple-is@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz"
+ },
+ "stringmap": {
+ "version": "0.2.2",
+ "from": "stringmap@>=0.2.2 <0.3.0",
+ "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz"
+ },
+ "stringset": {
+ "version": "0.2.1",
+ "from": "stringset@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz"
+ },
+ "tryor": {
+ "version": "0.1.2",
+ "from": "tryor@>=0.1.2 <0.2.0",
+ "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz"
+ },
+ "yargs": {
+ "version": "3.27.0",
+ "from": "yargs@>=3.27.0 <3.28.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz",
+ "dependencies": {
+ "camelcase": {
+ "version": "1.2.1",
+ "from": "camelcase@>=1.2.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "from": "cliui@>=2.1.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+ "dependencies": {
+ "center-align": {
+ "version": "0.1.3",
+ "from": "center-align@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+ "dependencies": {
+ "align-text": {
+ "version": "0.1.4",
+ "from": "align-text@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "dependencies": {
+ "kind-of": {
+ "version": "3.0.2",
+ "from": "kind-of@>=3.0.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.2.tgz",
+ "dependencies": {
+ "is-buffer": {
+ "version": "1.1.3",
+ "from": "is-buffer@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz"
+ }
+ }
+ },
+ "longest": {
+ "version": "1.0.1",
+ "from": "longest@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
+ },
+ "repeat-string": {
+ "version": "1.5.4",
+ "from": "repeat-string@>=1.5.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz"
+ }
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.3",
+ "from": "lazy-cache@>=1.0.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.3.tgz"
+ }
+ }
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "from": "right-align@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+ "dependencies": {
+ "align-text": {
+ "version": "0.1.4",
+ "from": "align-text@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "dependencies": {
+ "kind-of": {
+ "version": "3.0.2",
+ "from": "kind-of@>=3.0.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.2.tgz",
+ "dependencies": {
+ "is-buffer": {
+ "version": "1.1.3",
+ "from": "is-buffer@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz"
+ }
+ }
+ },
+ "longest": {
+ "version": "1.0.1",
+ "from": "longest@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
+ },
+ "repeat-string": {
+ "version": "1.5.4",
+ "from": "repeat-string@>=1.5.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz"
+ }
+ }
+ }
+ }
+ },
+ "wordwrap": {
+ "version": "0.0.2",
+ "from": "wordwrap@0.0.2",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
+ }
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "from": "decamelize@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "from": "os-locale@>=1.4.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "dependencies": {
+ "lcid": {
+ "version": "1.0.0",
+ "from": "lcid@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "dependencies": {
+ "invert-kv": {
+ "version": "1.0.0",
+ "from": "invert-kv@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "window-size": {
+ "version": "0.1.4",
+ "from": "window-size@>=0.1.2 <0.2.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz"
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "from": "y18n@>=3.2.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "esprima-fb": {
+ "version": "15001.1001.0-dev-harmony-fb",
+ "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0",
+ "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
+ },
+ "recast": {
+ "version": "0.10.33",
+ "from": "recast@0.10.33",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz",
+ "dependencies": {
+ "ast-types": {
+ "version": "0.8.12",
+ "from": "ast-types@0.8.12",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
+ }
+ }
+ },
+ "through": {
+ "version": "2.3.8",
+ "from": "through@>=2.3.8 <2.4.0",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+ }
+ }
+ },
+ "regexpu": {
+ "version": "1.3.0",
+ "from": "regexpu@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz",
+ "dependencies": {
+ "esprima": {
+ "version": "2.7.2",
+ "from": "esprima@>=2.6.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
+ },
+ "recast": {
+ "version": "0.10.43",
+ "from": "recast@>=0.10.10 <0.11.0",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz",
+ "dependencies": {
+ "esprima-fb": {
+ "version": "15001.1001.0-dev-harmony-fb",
+ "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0",
+ "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
+ },
+ "ast-types": {
+ "version": "0.8.15",
+ "from": "ast-types@0.8.15",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz"
+ }
+ }
+ },
+ "regenerate": {
+ "version": "1.2.1",
+ "from": "regenerate@>=1.2.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz"
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "from": "regjsgen@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz"
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "from": "regjsparser@>=0.1.4 <0.2.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "from": "jsesc@>=0.5.0 <0.6.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "repeating": {
+ "version": "1.1.3",
+ "from": "repeating@>=1.1.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz",
+ "dependencies": {
+ "is-finite": {
+ "version": "1.0.1",
+ "from": "is-finite@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
+ "dependencies": {
+ "number-is-nan": {
+ "version": "1.0.0",
+ "from": "number-is-nan@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "from": "resolve@>=1.1.6 <2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz"
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "from": "shebang-regex@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
+ },
+ "slash": {
+ "version": "1.0.0",
+ "from": "slash@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz"
+ },
+ "source-map": {
+ "version": "0.5.3",
+ "from": "source-map@>=0.5.0 <0.6.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
+ },
+ "source-map-support": {
+ "version": "0.2.10",
+ "from": "source-map-support@>=0.2.10 <0.3.0",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz",
+ "dependencies": {
+ "source-map": {
+ "version": "0.1.32",
+ "from": "source-map@0.1.32",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz",
+ "dependencies": {
+ "amdefine": {
+ "version": "1.0.0",
+ "from": "amdefine@>=0.0.4",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "to-fast-properties": {
+ "version": "1.0.2",
+ "from": "to-fast-properties@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz"
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "from": "trim-right@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz"
+ },
+ "try-resolve": {
+ "version": "1.0.1",
+ "from": "try-resolve@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz"
+ }
+ }
+ },
+ "broccoli-persistent-filter": {
+ "version": "1.2.0",
+ "from": "broccoli-persistent-filter@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-persistent-filter/-/broccoli-persistent-filter-1.2.0.tgz",
+ "dependencies": {
+ "async-disk-cache": {
+ "version": "1.0.3",
+ "from": "async-disk-cache@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/async-disk-cache/-/async-disk-cache-1.0.3.tgz"
+ },
+ "blank-object": {
+ "version": "1.0.1",
+ "from": "blank-object@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/blank-object/-/blank-object-1.0.1.tgz"
+ },
+ "broccoli-kitchen-sink-helpers": {
+ "version": "0.3.1",
+ "from": "broccoli-kitchen-sink-helpers@>=0.3.1 <0.4.0",
+ "resolved": "https://registry.npmjs.org/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.3.1.tgz",
+ "dependencies": {
+ "glob": {
+ "version": "5.0.15",
+ "from": "glob@>=5.0.10 <6.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "dependencies": {
+ "inflight": {
+ "version": "1.0.4",
+ "from": "inflight@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ },
+ "inherits": {
+ "version": "2.0.1",
+ "from": "inherits@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+ },
+ "minimatch": {
+ "version": "3.0.0",
+ "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.3",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "once": {
+ "version": "1.3.3",
+ "from": "once@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.0",
+ "from": "path-is-absolute@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "broccoli-plugin": {
+ "version": "1.2.1",
+ "from": "broccoli-plugin@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-1.2.1.tgz",
+ "dependencies": {
+ "quick-temp": {
+ "version": "0.1.5",
+ "from": "quick-temp@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.5.tgz",
+ "dependencies": {
+ "rimraf": {
+ "version": "2.2.8",
+ "from": "rimraf@>=2.2.6 <2.3.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+ },
+ "mktemp": {
+ "version": "0.3.5",
+ "from": "mktemp@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.3.5.tgz"
+ },
+ "underscore.string": {
+ "version": "2.3.3",
+ "from": "underscore.string@>=2.3.3 <2.4.0",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
+ }
+ }
+ }
+ }
+ },
+ "fs-tree-diff": {
+ "version": "0.4.4",
+ "from": "fs-tree-diff@>=0.4.4 <0.5.0",
+ "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.4.4.tgz",
+ "dependencies": {
+ "fast-ordered-set": {
+ "version": "1.0.2",
+ "from": "fast-ordered-set@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-ordered-set/-/fast-ordered-set-1.0.2.tgz"
+ }
+ }
+ },
+ "hash-for-dep": {
+ "version": "1.0.2",
+ "from": "hash-for-dep@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/hash-for-dep/-/hash-for-dep-1.0.2.tgz",
+ "dependencies": {
+ "broccoli-kitchen-sink-helpers": {
+ "version": "0.2.9",
+ "from": "broccoli-kitchen-sink-helpers@>=0.2.6 <0.3.0",
+ "resolved": "https://registry.npmjs.org/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.2.9.tgz",
+ "dependencies": {
+ "glob": {
+ "version": "5.0.15",
+ "from": "glob@>=5.0.10 <6.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "dependencies": {
+ "inflight": {
+ "version": "1.0.4",
+ "from": "inflight@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ },
+ "inherits": {
+ "version": "2.0.1",
+ "from": "inherits@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+ },
+ "minimatch": {
+ "version": "3.0.0",
+ "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.3",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "once": {
+ "version": "1.3.3",
+ "from": "once@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
+ "dependencies": {
+ "wrappy": {
+ "version": "1.0.1",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+ }
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.0",
+ "from": "path-is-absolute@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "from": "resolve@>=1.1.6 <2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz"
+ }
+ }
+ },
+ "md5-hex": {
+ "version": "1.2.1",
+ "from": "md5-hex@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.2.1.tgz",
+ "dependencies": {
+ "md5-o-matic": {
+ "version": "0.1.1",
+ "from": "md5-o-matic@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz"
+ }
+ }
+ },
+ "promise-map-series": {
+ "version": "0.2.2",
+ "from": "promise-map-series@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.2.2.tgz"
+ },
+ "rsvp": {
+ "version": "3.2.1",
+ "from": "rsvp@>=3.0.18 <4.0.0",
+ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz"
+ },
+ "symlink-or-copy": {
+ "version": "1.0.1",
+ "from": "symlink-or-copy@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.0.1.tgz"
+ },
+ "walk-sync": {
+ "version": "0.2.6",
+ "from": "walk-sync@>=0.2.6 <0.3.0",
+ "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.2.6.tgz",
+ "dependencies": {
+ "matcher-collection": {
+ "version": "1.0.1",
+ "from": "matcher-collection@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-1.0.1.tgz",
+ "dependencies": {
+ "minimatch": {
+ "version": "2.0.10",
+ "from": "minimatch@>=2.0.10 <3.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.3",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "broccoli-funnel": {
+ "version": "1.0.1",
+ "from": "broccoli-funnel@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-funnel/-/broccoli-funnel-1.0.1.tgz",
+ "dependencies": {
+ "array-equal": {
+ "version": "1.0.0",
+ "from": "array-equal@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz"
+ },
+ "blank-object": {
+ "version": "1.0.1",
+ "from": "blank-object@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/blank-object/-/blank-object-1.0.1.tgz"
+ },
+ "broccoli-plugin": {
+ "version": "1.2.1",
+ "from": "broccoli-plugin@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-1.2.1.tgz",
+ "dependencies": {
+ "promise-map-series": {
+ "version": "0.2.2",
+ "from": "promise-map-series@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.2.2.tgz",
+ "dependencies": {
+ "rsvp": {
+ "version": "3.2.1",
+ "from": "rsvp@>=3.0.14 <4.0.0",
+ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz"
+ }
+ }
+ },
+ "quick-temp": {
+ "version": "0.1.5",
+ "from": "quick-temp@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.5.tgz",
+ "dependencies": {
+ "rimraf": {
+ "version": "2.2.8",
+ "from": "rimraf@>=2.2.6 <2.3.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+ },
+ "mktemp": {
+ "version": "0.3.5",
+ "from": "mktemp@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.3.5.tgz"
+ },
+ "underscore.string": {
+ "version": "2.3.3",
+ "from": "underscore.string@>=2.3.3 <2.4.0",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
+ }
+ }
+ }
+ }
+ },
+ "fast-ordered-set": {
+ "version": "1.0.2",
+ "from": "fast-ordered-set@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-ordered-set/-/fast-ordered-set-1.0.2.tgz"
+ },
+ "fs-tree-diff": {
+ "version": "0.3.1",
+ "from": "fs-tree-diff@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.3.1.tgz"
+ },
+ "minimatch": {
+ "version": "2.0.10",
+ "from": "minimatch@>=2.0.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.3",
+ "from": "brace-expansion@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
+ "dependencies": {
+ "balanced-match": {
+ "version": "0.3.0",
+ "from": "balanced-match@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "from": "concat-map@0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "path-posix": {
+ "version": "1.0.0",
+ "from": "path-posix@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz"
+ },
+ "symlink-or-copy": {
+ "version": "1.0.1",
+ "from": "symlink-or-copy@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.0.1.tgz"
+ },
+ "walk-sync": {
+ "version": "0.2.6",
+ "from": "walk-sync@>=0.2.6 <0.3.0",
+ "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.2.6.tgz",
+ "dependencies": {
+ "matcher-collection": {
+ "version": "1.0.1",
+ "from": "matcher-collection@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-1.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "broccoli-merge-trees": {
+ "version": "1.1.1",
+ "from": "broccoli-merge-trees@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-merge-trees/-/broccoli-merge-trees-1.1.1.tgz",
+ "dependencies": {
+ "broccoli-plugin": {
+ "version": "1.2.1",
+ "from": "broccoli-plugin@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-1.2.1.tgz",
+ "dependencies": {
+ "promise-map-series": {
+ "version": "0.2.2",
+ "from": "promise-map-series@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.2.2.tgz",
+ "dependencies": {
+ "rsvp": {
+ "version": "3.2.1",
+ "from": "rsvp@>=3.0.14 <4.0.0",
+ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.2.1.tgz"
+ }
+ }
+ },
+ "quick-temp": {
+ "version": "0.1.5",
+ "from": "quick-temp@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.5.tgz",
+ "dependencies": {
+ "rimraf": {
+ "version": "2.2.8",
+ "from": "rimraf@>=2.2.6 <2.3.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+ },
+ "mktemp": {
+ "version": "0.3.5",
+ "from": "mktemp@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.3.5.tgz"
+ },
+ "underscore.string": {
+ "version": "2.3.3",
+ "from": "underscore.string@>=2.3.3 <2.4.0",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz"
+ }
+ }
+ }
+ }
+ },
+ "can-symlink": {
+ "version": "1.0.0",
+ "from": "can-symlink@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/can-symlink/-/can-symlink-1.0.0.tgz",
+ "dependencies": {
+ "tmp": {
+ "version": "0.0.28",
+ "from": "tmp@0.0.28",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz",
+ "dependencies": {
+ "os-tmpdir": {
+ "version": "1.0.1",
+ "from": "os-tmpdir@>=1.0.1 <1.1.0",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"
+ }
+ }
+ }
+ }
+ },
+ "fast-ordered-set": {
+ "version": "1.0.2",
+ "from": "fast-ordered-set@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-ordered-set/-/fast-ordered-set-1.0.2.tgz",
+ "dependencies": {
+ "blank-object": {
+ "version": "1.0.1",
+ "from": "blank-object@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/blank-object/-/blank-object-1.0.1.tgz"
+ }
+ }
+ },
+ "fs-tree-diff": {
+ "version": "0.4.4",
+ "from": "fs-tree-diff@>=0.4.3 <0.5.0",
+ "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.4.4.tgz"
+ },
+ "symlink-or-copy": {
+ "version": "1.0.1",
+ "from": "symlink-or-copy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.0.1.tgz"
+ }
+ }
+ },
+ "clone": {
+ "version": "0.2.0",
+ "from": "clone@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
+ },
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "from": "json-stable-stringify@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "dependencies": {
+ "jsonify": {
+ "version": "0.0.0",
+ "from": "jsonify@>=0.0.0 <0.1.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
+ }
+ }
+ }
+ }
+ },
"broccoli-cli": {
"version": "0.0.1",
"from": "https://registry.npmjs.org/broccoli-cli/-/broccoli-cli-0.0.1.tgz",
@@ -4130,16 +5286,16 @@
"from": "abbrev@1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
},
- "ansi": {
- "version": "0.3.0",
- "from": "ansi@~0.3.0",
- "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
- },
"ansi-regex": {
"version": "2.0.0",
"from": "ansi-regex@^2.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
},
+ "ansi": {
+ "version": "0.3.0",
+ "from": "ansi@~0.3.0",
+ "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
+ },
"ansi-styles": {
"version": "2.1.0",
"from": "ansi-styles@^2.1.0",
@@ -4155,16 +5311,16 @@
"from": "asn1@0.1.11",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
},
- "async": {
- "version": "1.5.0",
- "from": "async@^1.4.0",
- "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
- },
"assert-plus": {
"version": "0.1.5",
"from": "assert-plus@^0.1.5",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
},
+ "async": {
+ "version": "1.5.0",
+ "from": "async@^1.4.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
+ },
"aws-sign2": {
"version": "0.6.0",
"from": "aws-sign2@~0.6.0",
@@ -4175,31 +5331,31 @@
"from": "balanced-match@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
},
- "block-stream": {
- "version": "0.0.8",
- "from": "block-stream@*",
- "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
- },
"boom": {
"version": "2.10.1",
"from": "boom@^2.8.x",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
},
+ "block-stream": {
+ "version": "0.0.8",
+ "from": "block-stream@*",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
+ },
"brace-expansion": {
"version": "1.1.1",
"from": "brace-expansion@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
},
- "caseless": {
- "version": "0.11.0",
- "from": "caseless@~0.11.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
- },
"chalk": {
"version": "1.1.1",
"from": "chalk@^1.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
},
+ "caseless": {
+ "version": "0.11.0",
+ "from": "caseless@~0.11.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
+ },
"combined-stream": {
"version": "1.0.5",
"from": "combined-stream@~1.0.5",
@@ -4225,26 +5381,26 @@
"from": "cryptiles@2.x.x",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
},
- "ctype": {
- "version": "0.5.3",
- "from": "ctype@0.5.3",
- "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
- },
"debug": {
"version": "0.7.4",
"from": "debug@~0.7.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
},
- "delayed-stream": {
- "version": "1.0.0",
- "from": "delayed-stream@~1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ "ctype": {
+ "version": "0.5.3",
+ "from": "ctype@0.5.3",
+ "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
},
"deep-extend": {
"version": "0.2.11",
"from": "deep-extend@~0.2.5",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
},
+ "delayed-stream": {
+ "version": "1.0.0",
+ "from": "delayed-stream@~1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ },
"delegates": {
"version": "0.1.0",
"from": "delegates@^0.1.0",
@@ -4260,11 +5416,6 @@
"from": "extend@~3.0.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
},
- "fstream": {
- "version": "1.0.8",
- "from": "fstream@^1.0.2",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz"
- },
"forever-agent": {
"version": "0.6.1",
"from": "forever-agent@~0.6.1",
@@ -4275,6 +5426,11 @@
"from": "form-data@~1.0.0-rc3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz"
},
+ "fstream": {
+ "version": "1.0.8",
+ "from": "fstream@^1.0.2",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz"
+ },
"gauge": {
"version": "1.2.2",
"from": "gauge@~1.2.0",
@@ -4360,26 +5516,26 @@
"from": "isstream@~0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
},
- "json-stringify-safe": {
- "version": "5.0.1",
- "from": "json-stringify-safe@~5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
- },
"jsonpointer": {
"version": "2.0.0",
"from": "jsonpointer@2.0.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
},
- "lodash._createpadding": {
- "version": "3.6.1",
- "from": "lodash._createpadding@^3.0.0",
- "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz"
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "from": "json-stringify-safe@~5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"lodash._basetostring": {
"version": "3.0.1",
"from": "lodash._basetostring@^3.0.0",
"resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
},
+ "lodash._createpadding": {
+ "version": "3.6.1",
+ "from": "lodash._createpadding@^3.0.0",
+ "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz"
+ },
"lodash.pad": {
"version": "3.1.1",
"from": "lodash.pad@^3.0.0",
@@ -4490,16 +5646,16 @@
"from": "stringstream@~0.0.4",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
},
- "strip-ansi": {
- "version": "3.0.0",
- "from": "strip-ansi@^3.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
- },
"strip-json-comments": {
"version": "0.1.3",
"from": "strip-json-comments@0.1.x",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
},
+ "strip-ansi": {
+ "version": "3.0.0",
+ "from": "strip-ansi@^3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+ },
"supports-color": {
"version": "2.0.0",
"from": "supports-color@^2.0.0",
diff --git a/tools/scripts/ansible-tower-service b/tools/scripts/ansible-tower-service
index f366f61e8f..1d2f2beec4 100755
--- a/tools/scripts/ansible-tower-service
+++ b/tools/scripts/ansible-tower-service
@@ -13,29 +13,8 @@ fi
service_action() {
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
- # 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
this_return=$?
if [ $this_return -gt $worst_return ]; then
@@ -43,7 +22,7 @@ service_action() {
fi
# Allow supervisor time to cleanup child pids (ubuntu only)
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}"
if [ "${S_PID}" ]; then
i=0
@@ -76,22 +55,22 @@ case "$1" in
usage
;;
start)
- echo "Starting Tower"
- service_action start
- ;;
+ echo "Starting Tower"
+ service_action start
+ ;;
stop)
- echo "Stopping Tower"
- service_action stop
- ;;
+ echo "Stopping Tower"
+ service_action stop
+ ;;
restart)
- echo "Restarting Tower"
- service_action stop
- service_action start
- ;;
+ echo "Restarting Tower"
+ service_action stop
+ service_action start
+ ;;
status)
- echo "Showing Tower Status"
- service_action status
- ;;
+ echo "Showing Tower Status"
+ service_action status
+ ;;
*)
usage
worst_return=1