From 62beb24d62319b6d72a7ba5199a5bddd71355893 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 8 Aug 2016 12:53:04 -0400 Subject: [PATCH 01/48] adding initial testing consumers/routes for channels --- awx/main/consumers.py | 13 +++++++++++++ awx/main/routing.py | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 awx/main/consumers.py create mode 100644 awx/main/routing.py diff --git a/awx/main/consumers.py b/awx/main/consumers.py new file mode 100644 index 0000000000..de196eb866 --- /dev/null +++ b/awx/main/consumers.py @@ -0,0 +1,13 @@ +from channels import Group +from channels.sessions import channel_session + + +@channel_session +def job_event_connect(message): + job_id = message.content['path'].strip('/') + message.channel_session['job_id'] = job_id + Group("job_events-%s" % job_id).add(message.reply_channel) + +def emit_channel_notification(event, payload): + Group(event).send(payload) + diff --git a/awx/main/routing.py b/awx/main/routing.py new file mode 100644 index 0000000000..6156a295a9 --- /dev/null +++ b/awx/main/routing.py @@ -0,0 +1,6 @@ +from channels.routing import route + + +channel_routing = [ + route("websocket.connect", "awx.main.consumers.job_event_connect", path=r'^/job_event/(?P[a-zA-Z0-9_]+)/$'), +] From 74f9372c69321a84ada763b7313529f490b8e095 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 8 Aug 2016 16:42:10 -0400 Subject: [PATCH 02/48] updating pyOpenSSL and cffi versions for django-channels --- requirements/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 433ae22e00..bbcb0c55e7 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -84,7 +84,8 @@ pycparser==2.14 pygerduty==0.32.1 PyJWT==1.4.0 pymongo==2.8 -pyOpenSSL==0.15.1 +pyOpenSSL==16.0.0 +cffi==1.7.0 pyparsing==2.0.7 pyrad==2.0 pyrax==1.9.7 From 42aab8ab8358ccd4ab6c9c3ff59fc97c43122b55 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 11 Aug 2016 15:04:27 -0400 Subject: [PATCH 03/48] removing websocket notification and service --- Makefile | 1 - awx/main/utils.py | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/Makefile b/Makefile index fd9d87cd2e..46e81d5686 100644 --- a/Makefile +++ b/Makefile @@ -361,7 +361,6 @@ server_noattach: tmux new-window 'exec make receiver' tmux select-window -t tower:1 tmux rename-window 'Extra Services' - tmux split-window -v 'exec make socketservice' tmux split-window -h 'exec make factcacher' server: server_noattach diff --git a/awx/main/utils.py b/awx/main/utils.py index 0603e05997..b9bce10fc0 100644 --- a/awx/main/utils.py +++ b/awx/main/utils.py @@ -490,19 +490,6 @@ def get_system_task_capacity(): return 50 + ((int(total_mem_value) / 1024) - 2) * 75 -def emit_websocket_notification(endpoint, event, payload, token_key=None): - from awx.main.socket_queue import Socket - - try: - with Socket('websocket', 'w', nowait=True, logger=logger) as websocket: - if token_key: - payload['token_key'] = token_key - payload['event'] = event - payload['endpoint'] = endpoint - websocket.publish(payload) - except Exception: - pass - _inventory_updates = threading.local() From 4c8aaf1aed000cc280f3b7d0f4b03b94a47e88c1 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 11 Aug 2016 15:06:07 -0400 Subject: [PATCH 04/48] converting from socketio to channels websocket --- awx/api/views.py | 23 ++---------- awx/main/consumers.py | 36 +++++++++++++++---- .../management/commands/run_task_system.py | 6 ++-- awx/main/models/inventory.py | 2 +- awx/main/models/jobs.py | 10 +++--- awx/main/models/projects.py | 2 +- awx/main/models/schedules.py | 5 +-- awx/main/models/unified_jobs.py | 19 +++++----- awx/main/routing.py | 3 +- awx/main/signals.py | 9 +++-- awx/main/tasks.py | 32 ++++++++++------- 11 files changed, 83 insertions(+), 64 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 551bb814e9..6019ce62a2 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -72,8 +72,8 @@ from awx.api.permissions import * # noqa from awx.api.renderers import * # noqa from awx.api.serializers import * # noqa from awx.api.metadata import RoleMetadata -from awx.main.utils import emit_websocket_notification from awx.main.conf import tower_settings +from awx.main.consumers import emit_channel_notification logger = logging.getLogger('awx.api.views') @@ -544,11 +544,7 @@ class AuthTokenView(APIView): # Mark them as invalid and inform the user invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user']) for t in invalid_tokens: - # TODO: send socket notification - emit_websocket_notification('/socket.io/control', - 'limit_reached', - dict(reason=force_text(AuthToken.reason_long('limit_reached'))), - token_key=t.key) + emit_channel_notification('control-limit_reached', dict(reason=force_text(AuthToken.reason_long('limit_reached')), token_key=t.key)) t.invalidate(reason='limit_reached') # Note: This header is normally added in the middleware whenever an @@ -3183,21 +3179,8 @@ class JobJobTasksList(BaseJobEventsList): return ({'detail': 'Parent event not found.'}, -1, status.HTTP_404_NOT_FOUND) parent_task = parent_task[0] - # Some events correspond to a playbook or task starting up, - # and these are what we're interested in here. STARTING_EVENTS = ('playbook_on_task_start', 'playbook_on_setup') - - # We need to pull information about each start event. - # - # This is super tricky, because this table has a one-to-many - # relationship with itself (parent-child), and we're getting - # information for an arbitrary number of children. This means we - # need stats on grandchildren, sorted by child. - queryset = (JobEvent.objects.filter(parent__parent=parent_task, - parent__event__in=STARTING_EVENTS) - .values('parent__id', 'event', 'changed') - .annotate(num=Count('event')) - .order_by('parent__id')) + queryset = JobEvent.get_startevent_queryset(parent_task, STARTING_EVENTS) # The data above will come back in a list, but we are going to # want to access it based on the parent id, so map it into a diff --git a/awx/main/consumers.py b/awx/main/consumers.py index de196eb866..23ca874ed0 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -1,13 +1,37 @@ +import json + from channels import Group from channels.sessions import channel_session @channel_session -def job_event_connect(message): - job_id = message.content['path'].strip('/') - message.channel_session['job_id'] = job_id - Group("job_events-%s" % job_id).add(message.reply_channel) +def ws_disconnect(message): + for group in message.channel_session['groups']: + print("removing from group: {}".format(group)) + Group(group).discard(message.reply_channel) -def emit_channel_notification(event, payload): - Group(event).send(payload) +@channel_session +def ws_receive(message): + raw_data = message.content['text'] + data = json.loads(raw_data) + if 'groups' in data: + groups = data['groups'] + current_groups = message.channel_session.pop('groups') if 'groups' in message.channel_session else [] + for group_name,v in groups.items(): + if type(v) is list: + for oid in v: + name = '{}-{}'.format(group_name, oid) + print("listening to group: {}".format(name)) + current_groups.append(name) + Group(name).add(message.reply_channel) + else: + print("listening to group: {}".format(group_name)) + current_groups.append(name) + Group(group_name).add(message.reply_channel) + message.channel_session['groups'] = current_groups + + +def emit_channel_notification(group, payload): + print("sending message to group {}".format(group)) + Group(group).send({"text": json.dumps(payload)}) diff --git a/awx/main/management/commands/run_task_system.py b/awx/main/management/commands/run_task_system.py index 855491f08c..f68f35bad7 100644 --- a/awx/main/management/commands/run_task_system.py +++ b/awx/main/management/commands/run_task_system.py @@ -318,7 +318,7 @@ def rebuild_graph(message): logger.debug("Active celery tasks: " + str(active_tasks)) for task in list(running_tasks): if (task.celery_task_id not in active_tasks and not hasattr(settings, 'IGNORE_CELERY_INSPECTOR')): - # NOTE: Pull status again and make sure it didn't finish in + # NOTE: Pull status again and make sure it didn't finish in # the meantime? task.status = 'failed' task.job_explanation += ' '.join(( @@ -326,7 +326,7 @@ def rebuild_graph(message): 'Celery, so it has been marked as failed.', )) task.save() - task.socketio_emit_status("failed") + task.websocket_emit_status("failed") running_tasks.pop(running_tasks.index(task)) logger.error("Task %s appears orphaned... marking as failed" % task) @@ -340,7 +340,7 @@ def rebuild_graph(message): task.status = 'failed' task.job_explanation += 'Task failed to generate dependencies: {}'.format(e) task.save() - task.socketio_emit_status("failed") + task.websocket_emit_status("failed") continue logger.debug("New dependencies: %s" % str(task_dependencies)) for dep in task_dependencies: diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 0955a28667..576840f8cf 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1223,7 +1223,7 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): from awx.main.tasks import RunInventoryUpdate return RunInventoryUpdate - def socketio_emit_data(self): + def websocket_emit_data(self): if self.inventory_source.group is not None: return dict(group_id=self.inventory_source.group.id) return {} diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 34adcb73a4..002b4d7e15 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -29,12 +29,13 @@ from awx.main.models.notifications import ( JobNotificationMixin, ) from awx.main.utils import decrypt_field, ignore_inventory_computed_fields -from awx.main.utils import emit_websocket_notification from awx.main.redact import PlainTextCleaner from awx.main.conf import tower_settings from awx.main.fields import ImplicitRoleField from awx.main.models.mixins import ResourceMixin +from awx.main.consumers import emit_channel_notification + logger = logging.getLogger('awx.main.models.jobs') @@ -1259,11 +1260,10 @@ class JobEvent(CreatedModifiedModel): if update_fields: host_summary.save(update_fields=update_fields) job.inventory.update_computed_fields() - emit_websocket_notification('/socket.io/jobs', 'summary_complete', dict(unified_job_id=job.id)) - + emit_channel_notification('jobs-summary', dict(unified_job_id=job.id)) @classmethod - def start_event_queryset(cls, parent_task, starting_events, ordering=None): + def get_startevent_queryset(cls, parent_task, starting_events, ordering=None): ''' We need to pull information about each start event. @@ -1369,7 +1369,7 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin): from awx.main.tasks import RunSystemJob return RunSystemJob - def socketio_emit_data(self): + def websocket_emit_data(self): return {} def get_absolute_url(self): diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 85ca3ab2aa..c1349ed38b 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -408,7 +408,7 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin): return True return False - def socketio_emit_data(self): + def websocket_emit_data(self): return dict(project_id=self.project.id) @property diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index d9de8b394e..330b39f7b6 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -16,7 +16,8 @@ from jsonfield import JSONField # AWX from awx.main.models.base import * # noqa -from awx.main.utils import ignore_inventory_computed_fields, emit_websocket_notification +from awx.main.utils import ignore_inventory_computed_fields +from awx.main.consumers import emit_channel_notification from django.core.urlresolvers import reverse logger = logging.getLogger('awx.main.models.schedule') @@ -112,7 +113,7 @@ class Schedule(CommonModel): self.dtend = make_aware(datetime.datetime.strptime(until_date, "%Y%m%dT%H%M%SZ"), get_default_timezone()) if 'count' in self.rrule.lower(): self.dtend = future_rs[-1] - emit_websocket_notification('/socket.io/schedules', 'schedule_changed', dict(id=self.id)) + emit_channel_notification('schedules-changed', dict(id=self.id)) with ignore_inventory_computed_fields(): self.unified_job_template.update_computed_fields() diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 950b6fc99b..bdcedb2aab 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -32,8 +32,9 @@ from djcelery.models import TaskMeta # AWX from awx.main.models.base import * # noqa from awx.main.models.schedules import Schedule -from awx.main.utils import decrypt_field, emit_websocket_notification, _inventory_updates -from awx.main.redact import UriCleaner, REPLACE_STR +from awx.main.utils import decrypt_field, _inventory_updates +from awx.main.redact import UriCleaner +from awx.main.consumers import emit_channel_notification __all__ = ['UnifiedJobTemplate', 'UnifiedJob'] @@ -774,14 +775,14 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique ''' Given another task object determine if this task would be blocked by it ''' raise NotImplementedError # Implement in subclass. - def socketio_emit_data(self): + def websocket_emit_data(self): ''' Return extra data that should be included when submitting data to the browser over the websocket connection ''' return {} - def socketio_emit_status(self, status): + def websocket_emit_status(self, status): status_data = dict(unified_job_id=self.id, status=status) - status_data.update(self.socketio_emit_data()) - emit_websocket_notification('/socket.io/jobs', 'status_changed', status_data) + status_data.update(self.websocket_emit_data()) + emit_channel_notification('jobs-status_changed', status_data) def generate_dependencies(self, active_tasks): ''' Generate any tasks that the current task might be dependent on given a list of active @@ -850,7 +851,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique # Save the pending status, and inform the SocketIO listener. self.update_fields(start_args=json.dumps(kwargs), status='pending') - self.socketio_emit_status("pending") + self.websocket_emit_status("pending") # Each type of unified job has a different Task class; get the # appropirate one. @@ -900,7 +901,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique instance.job_explanation = 'Forced cancel' update_fields.append('job_explanation') instance.save(update_fields=update_fields) - self.socketio_emit_status("canceled") + self.websocket_emit_status("canceled") except: # FIXME: Log this exception! if settings.DEBUG: raise @@ -914,7 +915,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique self.status = 'canceled' cancel_fields.append('status') self.save(update_fields=cancel_fields) - self.socketio_emit_status("canceled") + self.websocket_emit_status("canceled") if settings.BROKER_URL.startswith('amqp://'): self._force_cancel() return self.cancel_flag diff --git a/awx/main/routing.py b/awx/main/routing.py index 6156a295a9..67a08ff1bd 100644 --- a/awx/main/routing.py +++ b/awx/main/routing.py @@ -2,5 +2,6 @@ from channels.routing import route channel_routing = [ - route("websocket.connect", "awx.main.consumers.job_event_connect", path=r'^/job_event/(?P[a-zA-Z0-9_]+)/$'), + route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'), + route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'), ] diff --git a/awx/main/signals.py b/awx/main/signals.py index 7389f01763..fe31082a88 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -18,11 +18,13 @@ from crum.signals import current_user_getter # AWX from awx.main.models import * # noqa from awx.api.serializers import * # noqa -from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore, emit_websocket_notification +from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore from awx.main.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates from awx.main.tasks import update_inventory_computed_fields from awx.main.conf import tower_settings +from awx.main.consumers import emit_channel_notification + __all__ = [] logger = logging.getLogger('awx.main.signals') @@ -33,13 +35,14 @@ logger = logging.getLogger('awx.main.signals') def emit_job_event_detail(sender, **kwargs): instance = kwargs['instance'] created = kwargs['created'] + print("before created job_event_detail") if created: event_serialized = JobEventSerializer(instance).data event_serialized['id'] = instance.id event_serialized["created"] = event_serialized["created"].isoformat() event_serialized["modified"] = event_serialized["modified"].isoformat() event_serialized["event_name"] = instance.event - emit_websocket_notification('/socket.io/job_events', 'job_events-' + str(instance.job.id), event_serialized) + emit_channel_notification('job_events-' + str(instance.job.id), event_serialized) def emit_ad_hoc_command_event_detail(sender, **kwargs): instance = kwargs['instance'] @@ -50,7 +53,7 @@ def emit_ad_hoc_command_event_detail(sender, **kwargs): event_serialized["created"] = event_serialized["created"].isoformat() event_serialized["modified"] = event_serialized["modified"].isoformat() event_serialized["event_name"] = instance.event - emit_websocket_notification('/socket.io/ad_hoc_command_events', 'ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized) + emit_channel_notification('ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized) def emit_update_inventory_computed_fields(sender, **kwargs): logger.debug("In update inventory computed fields") diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 097dca517d..830804f217 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -51,13 +51,13 @@ from awx.main.queue import FifoQueue from awx.main.conf import tower_settings from awx.main.task_engine import TaskSerializer, TASK_TIMEOUT_INTERVAL from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url, - emit_websocket_notification, check_proot_installed, build_proot_temp_dir, wrap_args_with_proot) +from awx.main.consumers import emit_channel_notification __all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate', - 'RunAdHocCommand', 'RunWorkflowJob', 'handle_work_error', - 'handle_work_success', 'update_inventory_computed_fields', - 'send_notifications', 'run_administrative_checks', + 'RunAdHocCommand', 'RunWorkflowJob', 'handle_work_error', + 'handle_work_success', 'update_inventory_computed_fields', + 'send_notifications', 'run_administrative_checks', 'run_workflow_job'] HIDDEN_PASSWORD = '**********' @@ -176,8 +176,8 @@ def tower_periodic_scheduler(self): new_unified_job.status = 'failed' new_unified_job.job_explanation = "Scheduled job could not start because it was not in the right state or required manual credentials" new_unified_job.save(update_fields=['status', 'job_explanation']) - new_unified_job.socketio_emit_status("failed") - emit_websocket_notification('/socket.io/schedules', 'schedule_changed', dict(id=schedule.id)) + new_unified_job.websocket_emit_status("failed") + emit_channel_notification('schedules-changed', dict(id=schedule.id)) @task(queue='default') def notify_task_runner(metadata_dict): @@ -234,10 +234,16 @@ def handle_work_error(self, task_id, subtasks=None): instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \ (first_instance_type, first_instance.name, first_instance.id) instance.save() - instance.socketio_emit_status("failed") - - if first_instance: - _send_notification_templates(first_instance, 'failed') + instance.websocket_emit_status("failed") + notification_body = first_task.notification_data() + notification_subject = "{} #{} '{}' failed on Ansible Tower: {}".format(first_task_friendly_name, + first_task_id, + smart_str(first_task_name), + notification_body['url']) + notification_body['friendly_name'] = first_task_friendly_name + send_notifications.delay([n.generate_notification(notification_subject, notification_body).id + for n in set(notification_templates.get('error', []) + notification_templates.get('any', []))], + job_id=first_task_id) @task(queue='default') def update_inventory_computed_fields(inventory_id, should_update_hosts=True): @@ -578,7 +584,7 @@ class BaseTask(Task): ''' instance = self.update_model(pk, status='running', celery_task_id=self.request.id) - instance.socketio_emit_status("running") + instance.websocket_emit_status("running") status, rc, tb = 'error', None, '' output_replacements = [] try: @@ -647,7 +653,7 @@ class BaseTask(Task): instance = self.update_model(pk, status=status, result_traceback=tb, output_replacements=output_replacements) self.post_run_hook(instance, **kwargs) - instance.socketio_emit_status(status) + instance.websocket_emit_status(status) if status != 'successful' and not hasattr(settings, 'CELERY_UNIT_TEST'): # Raising an exception will mark the job as 'failed' in celery # and will stop a task chain from continuing to execute @@ -1665,7 +1671,7 @@ class RunSystemJob(BaseTask): return settings.BASE_DIR class RunWorkflowJob(BaseTask): - + name = 'awx.main.tasks.run_workflow_job' model = WorkflowJob From 850934c89d3627b1bf7a0e4f0428eac45a904200 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Wed, 24 Aug 2016 10:52:47 -0400 Subject: [PATCH 05/48] remove reply_channel from groups --- Procfile | 1 - awx/main/consumers.py | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Procfile b/Procfile index 433417f70b..6af685e88f 100644 --- a/Procfile +++ b/Procfile @@ -2,6 +2,5 @@ runserver: make runserver celeryd: make celeryd taskmanager: make taskmanager receiver: make receiver -socketservice: make socketservice factcacher: make factcacher flower: make flower \ No newline at end of file diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 23ca874ed0..8b02f53d87 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -4,18 +4,22 @@ from channels import Group from channels.sessions import channel_session -@channel_session -def ws_disconnect(message): +def discard_groups(message): for group in message.channel_session['groups']: print("removing from group: {}".format(group)) Group(group).discard(message.reply_channel) +@channel_session +def ws_disconnect(message): + discard_groups(message) + @channel_session def ws_receive(message): raw_data = message.content['text'] data = json.loads(raw_data) if 'groups' in data: + discard_groups(message) groups = data['groups'] current_groups = message.channel_session.pop('groups') if 'groups' in message.channel_session else [] for group_name,v in groups.items(): @@ -27,7 +31,7 @@ def ws_receive(message): Group(name).add(message.reply_channel) else: print("listening to group: {}".format(group_name)) - current_groups.append(name) + current_groups.append(group_name) Group(group_name).add(message.reply_channel) message.channel_session['groups'] = current_groups From ee09d881a4db6b148b4f8e8e1b24a4964d79d0a4 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 29 Aug 2016 16:24:35 -0400 Subject: [PATCH 06/48] only discard if groups have been created previously --- awx/main/consumers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 8b02f53d87..990e197cd2 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -5,9 +5,10 @@ from channels.sessions import channel_session def discard_groups(message): - for group in message.channel_session['groups']: - print("removing from group: {}".format(group)) - Group(group).discard(message.reply_channel) + if 'groups' in message.channel_session: + for group in message.channel_session['groups']: + print("removing from group: {}".format(group)) + Group(group).discard(message.reply_channel) @channel_session def ws_disconnect(message): From 5faa21b72db1da9bda39cb2a7f9a8cfde3242e6c Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 29 Aug 2016 18:15:11 -0400 Subject: [PATCH 07/48] add group_name to channel data --- awx/api/views.py | 4 +++- awx/main/models/jobs.py | 2 +- awx/main/models/schedules.py | 2 +- awx/main/models/unified_jobs.py | 1 + awx/main/signals.py | 2 ++ awx/main/tasks.py | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 6019ce62a2..7bfbb9b3b0 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -544,7 +544,9 @@ class AuthTokenView(APIView): # Mark them as invalid and inform the user invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user']) for t in invalid_tokens: - emit_channel_notification('control-limit_reached', dict(reason=force_text(AuthToken.reason_long('limit_reached')), token_key=t.key)) + emit_channel_notification('control-limit_reached', dict(group_name='control', + reason=force_text(AuthToken.reason_long('limit_reached')), + token_key=t.key)) t.invalidate(reason='limit_reached') # Note: This header is normally added in the middleware whenever an diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 002b4d7e15..a4571641bd 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -1260,7 +1260,7 @@ class JobEvent(CreatedModifiedModel): if update_fields: host_summary.save(update_fields=update_fields) job.inventory.update_computed_fields() - emit_channel_notification('jobs-summary', dict(unified_job_id=job.id)) + emit_channel_notification('jobs-summary', dict(group_name='jobs', unified_job_id=job.id)) @classmethod def get_startevent_queryset(cls, parent_task, starting_events, ordering=None): diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 330b39f7b6..8fe83d832b 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -113,7 +113,7 @@ class Schedule(CommonModel): self.dtend = make_aware(datetime.datetime.strptime(until_date, "%Y%m%dT%H%M%SZ"), get_default_timezone()) if 'count' in self.rrule.lower(): self.dtend = future_rs[-1] - emit_channel_notification('schedules-changed', dict(id=self.id)) + emit_channel_notification('schedules-changed', dict(id=self.id, group_name='schedules')) with ignore_inventory_computed_fields(): self.unified_job_template.update_computed_fields() diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index bdcedb2aab..5142b5ccd3 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -782,6 +782,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique def websocket_emit_status(self, status): status_data = dict(unified_job_id=self.id, status=status) status_data.update(self.websocket_emit_data()) + status_data['group_name'] = 'jobs' emit_channel_notification('jobs-status_changed', status_data) def generate_dependencies(self, active_tasks): diff --git a/awx/main/signals.py b/awx/main/signals.py index fe31082a88..ebba144774 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -42,6 +42,7 @@ def emit_job_event_detail(sender, **kwargs): event_serialized["created"] = event_serialized["created"].isoformat() event_serialized["modified"] = event_serialized["modified"].isoformat() event_serialized["event_name"] = instance.event + event_serialized["group_name"] = "job_events" emit_channel_notification('job_events-' + str(instance.job.id), event_serialized) def emit_ad_hoc_command_event_detail(sender, **kwargs): @@ -53,6 +54,7 @@ def emit_ad_hoc_command_event_detail(sender, **kwargs): event_serialized["created"] = event_serialized["created"].isoformat() event_serialized["modified"] = event_serialized["modified"].isoformat() event_serialized["event_name"] = instance.event + event_serialized["group_name"] = "ad_hoc_command_events" emit_channel_notification('ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized) def emit_update_inventory_computed_fields(sender, **kwargs): diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 830804f217..065ac80724 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -177,7 +177,7 @@ def tower_periodic_scheduler(self): new_unified_job.job_explanation = "Scheduled job could not start because it was not in the right state or required manual credentials" new_unified_job.save(update_fields=['status', 'job_explanation']) new_unified_job.websocket_emit_status("failed") - emit_channel_notification('schedules-changed', dict(id=schedule.id)) + emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules")) @task(queue='default') def notify_task_runner(metadata_dict): From b448621387f92ddb82eb0ed0cd0e9b37dc44d320 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 20 Sep 2016 11:00:37 -0400 Subject: [PATCH 08/48] adding asgi.py --- awx/asgi.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 awx/asgi.py diff --git a/awx/asgi.py b/awx/asgi.py new file mode 100644 index 0000000000..42d800d939 --- /dev/null +++ b/awx/asgi.py @@ -0,0 +1,37 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. +import os +import logging +from awx import __version__ as tower_version + +# Prepare the AWX environment. +from awx import prepare_env, MODE +prepare_env() + +from django.core.wsgi import get_wsgi_application # NOQA + +""" +ASGI config for AWX project. + +It exposes the ASGI callable as a module-level variable named ``channel_layer``. + +For more information on this file, see +https://channels.readthedocs.io/en/latest/deploying.html +""" + +if MODE == 'production': + logger = logging.getLogger('awx.main.models.jobs') + try: + fd = open("/var/lib/awx/.tower_version", "r") + if fd.read().strip() != tower_version: + raise Exception() + except Exception: + logger.error("Missing or incorrect metadata for Tower version. Ensure Tower was installed using the setup playbook.") + raise Exception("Missing or incorrect metadata for Tower version. Ensure Tower was installed using the setup playbook.") + +from channels.asgi import get_channel_layer + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") + + +channel_layer = get_channel_layer() From 258ddba3333ae925310cfd74bf30367e8622fe7f Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 27 Sep 2016 10:19:35 -0400 Subject: [PATCH 09/48] add channels to reqs and defaults --- awx/settings/defaults.py | 1 + awx/settings/local_settings.py.docker_compose | 6 ++++++ requirements/requirements.txt | 1 + 3 files changed, 8 insertions(+) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 31c8b3b8f3..747d2e155f 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -187,6 +187,7 @@ INSTALLED_APPS = ( 'django_extensions', 'djcelery', 'kombu.transport.django', + 'channels', 'polymorphic', 'taggit', 'social.apps.django_app.default', diff --git a/awx/settings/local_settings.py.docker_compose b/awx/settings/local_settings.py.docker_compose index 4c20102746..f27acafacb 100644 --- a/awx/settings/local_settings.py.docker_compose +++ b/awx/settings/local_settings.py.docker_compose @@ -84,6 +84,12 @@ BROKER_URL = "amqp://{}:{}@{}/{}".format(os.environ.get("RABBITMQ_USER"), os.environ.get("RABBITMQ_HOST"), os.environ.get("RABBITMQ_VHOST")) +CHANNEL_LAYERS = { + 'default': {'BACKEND': 'asgi_amqp.AMQPChannelLayer', + 'ROUTING': 'awx.main.routing.channel_routing', + 'CONFIG': {'url': BROKER_URL}} +} + # Mongo host configuration MONGO_HOST = NotImplemented diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bbcb0c55e7..38e901f3e2 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -7,6 +7,7 @@ Babel==2.2.0 billiard==3.3.0.16 boto==2.40.0 celery==3.1.23 +channels==0.17.2 cliff==1.15.0 cmd2==0.6.8 d2to1==0.2.11 # TODO: Still needed? From 3fab2b63b93eb2864cc149207e9b503491253eae Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 29 Sep 2016 15:56:36 -0400 Subject: [PATCH 10/48] dev non-clustering rabbitmq with lvc enabled --- tools/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index 2e5b18460d..a930f1569d 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -30,7 +30,7 @@ services: memcached: image: memcached:alpine rabbitmq: - image: rabbitmq:3-management + image: gcr.io/ansible-tower-engineering/rabbitmq:latest ports: - "15672:15672" From 0088c5a4104e02a2052b9e4e816aa483c1642d52 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 3 Oct 2016 10:27:08 -0400 Subject: [PATCH 11/48] add asgi_amqp req --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 0332dfcc34..2ee1c5b9b8 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -9,6 +9,7 @@ billiard==3.3.0.16 boto==2.40.0 celery==3.1.23 channels==0.17.2 +asgi_amqp==0.2.1 cliff==1.15.0 cmd2==0.6.8 d2to1==0.2.11 # TODO: Still needed? From 1a9843d5d100184ad472eb92c4089759550e60bb Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Mon, 8 Aug 2016 12:52:34 -0400 Subject: [PATCH 12/48] adding initial channels integration --- awx/settings/defaults.py | 3 +-- awx/settings/local_settings.py.docker_compose | 2 +- requirements/requirements.txt | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index dbb0351221..2ecad1f572 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -373,7 +373,7 @@ CELERY_ROUTES = ({'awx.main.tasks.run_job': {'queue': 'jobs', 'routing_key': 'scheduler.job.launch'}, 'awx.main.scheduler.tasks.run_job_complete': {'queue': 'scheduler', 'routing_key': 'scheduler.job.complete'},}) - + CELERYBEAT_SCHEDULE = { 'tower_scheduler': { 'task': 'awx.main.tasks.tower_periodic_scheduler', @@ -970,4 +970,3 @@ LOGGING = { }, } } - diff --git a/awx/settings/local_settings.py.docker_compose b/awx/settings/local_settings.py.docker_compose index 5ba486ee96..088f651e77 100644 --- a/awx/settings/local_settings.py.docker_compose +++ b/awx/settings/local_settings.py.docker_compose @@ -75,7 +75,7 @@ if is_testing(sys.argv): }, } } - + MONGO_DB = 'system_tracking_test' # Celery AMQP configuration. diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 2ee1c5b9b8..5f0beba4b7 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -134,3 +134,5 @@ wheel==0.24.0 wrapt==1.10.6 wsgiref==0.1.2 xmltodict==0.9.2 +channels==0.17.1 +asgi_redis==0.14.0 From 6d4a2a8f8e94f677a05f0dc591148d3919ed09ad Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 11 Aug 2016 15:06:07 -0400 Subject: [PATCH 13/48] converting from socketio to channels websocket --- awx/main/models/jobs.py | 1 - awx/main/models/unified_jobs.py | 1 - awx/main/scheduler/__init__.py | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 269c987aeb..c47ddfd59a 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -1409,4 +1409,3 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin): def get_notification_friendly_name(self): return "System Job" - diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index a4ae651eb7..3a30906e02 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -932,4 +932,3 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique if settings.BROKER_URL.startswith('amqp://'): self._force_cancel() return self.cancel_flag - diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 03c87d8ddb..63bf006bbe 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -122,7 +122,7 @@ def rebuild_graph(): logger.debug("Active celery tasks: " + str(active_tasks)) for task in list(running_celery_tasks): if (task.celery_task_id not in active_tasks and not hasattr(settings, 'IGNORE_CELERY_INSPECTOR')): - # NOTE: Pull status again and make sure it didn't finish in + # NOTE: Pull status again and make sure it didn't finish in # the meantime? task.status = 'failed' task.job_explanation += ' '.join(( @@ -204,7 +204,7 @@ def process_graph(graph, task_capacity): node_type = graph.get_node_type(node_obj) if node_type == 'job': - # clear dependencies because a job can block (not necessarily + # clear dependencies because a job can block (not necessarily # depend) on other jobs that share the same job template node_dependencies = [] @@ -217,7 +217,7 @@ def process_graph(graph, task_capacity): node_obj.start() spawn_workflow_graph_jobs([node_obj]) return process_graph(graph, task_capacity) - + dependent_nodes = [{'type': graph.get_node_type(node_obj), 'id': node_obj.id}] + \ [{'type': graph.get_node_type(n['node_object']), 'id': n['node_object'].id} for n in node_dependencies] From e9b54dcd3cabc8f3fef0bf99a505d2ce356dada3 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Sun, 28 Aug 2016 13:01:59 -0700 Subject: [PATCH 14/48] switching socket.io-client for ReconnectingWebSocket library --- awx/ui/client/src/app.js | 101 ++++++-------- awx/ui/client/src/config.js | 2 +- .../src/job-detail/job-detail.controller.js | 8 +- .../client/src/job-detail/job-detail.route.js | 32 ++--- awx/ui/client/src/shared/Socket.js | 125 ++++-------------- .../log/standard-out-log.controller.js | 14 +- awx/ui/package.json | 5 +- awx/ui/webpack.config.js | 2 +- 8 files changed, 94 insertions(+), 195 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 0f925812f6..76506bf43e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -713,80 +713,53 @@ var tower = angular.module('Tower', [ $rootScope.removeOpenSocket(); } $rootScope.removeOpenSocket = $rootScope.$on('OpenSocket', function() { - // Listen for job changes and issue callbacks to initiate - // DOM updates function openSocket() { - var schedule_socket, control_socket; - - sock = Socket({ scope: $rootScope, endpoint: "jobs" }); - sock.init(); - sock.on("status_changed", function(data) { - $log.debug('Job ' + data.unified_job_id + - ' status changed to ' + data.status + - ' send to ' + $location.$$url); - - // this acts as a router...it emits the proper - // value based on what URL the user is currently - // accessing. - if ($state.is('jobs')) { - $rootScope.$emit('JobStatusChange-jobs', data); - } else if ($state.includes('jobDetail') || - $state.is('adHocJobStdout') || - $state.is('inventorySyncStdout') || - $state.is('managementJobStdout') || - $state.is('scmUpdateStdout')) { - - $log.debug("sending status to standard out"); - $rootScope.$emit('JobStatusChange-jobStdout', data); - } - if ($state.includes('jobDetail')) { - $rootScope.$emit('JobStatusChange-jobDetails', data); - } else if ($state.is('dashboard')) { - $rootScope.$emit('JobStatusChange-home', data); - } else if ($state.is('portalMode')) { - $rootScope.$emit('JobStatusChange-portal', data); - } else if ($state.is('projects')) { - $rootScope.$emit('JobStatusChange-projects', data); - } else if ($state.is('inventoryManage')) { - $rootScope.$emit('JobStatusChange-inventory', data); - } - }); - sock.on("summary_complete", function(data) { - $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('JobSummaryComplete', data); - }); - - schedule_socket = Socket({ - scope: $rootScope, - endpoint: "schedules" - }); - schedule_socket.init(); - schedule_socket.on("schedule_changed", function(data) { - $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); - $rootScope.$emit('ScheduleStatusChange', data); - }); - - control_socket = Socket({ - scope: $rootScope, - endpoint: "control" - }); - control_socket.init(); - control_socket.on("limit_reached", function(data) { - $log.debug(data.reason); - $rootScope.sessionTimer.expireSession('session_limit'); - $state.go('signOut'); - }); + $rootScope.socket = Socket({ scope: $rootScope}); + $rootScope.socket.init(); } openSocket(); setTimeout(function() { $rootScope.$apply(function() { - sock.checkStatus(); + $rootScope.socket.checkStatus(); $log.debug('socket status: ' + $rootScope.socketStatus); }); }, 2000); }); - + // {'groups': + // {'jobs': ['status_changed', 'summary'], + // 'schedules': ['changed'], + // 'ad_hoc_command_events': [ids,], + // 'job_events': [ids,], + // 'control': ['limit_reached'], + // } + // } + $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { + if(toState.name === 'dashboard'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + } + else if(toState.name === 'jobDetail'){ + $rootScope.socket.emit(`{"groups":{"jobs": ["status_changed", "summary"]},{"job_events":[${toParams.id}]}}`); + } + else if(toState.name === 'jobStdout'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + } + else if(toState.name === 'jobs'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}, {"schedules": ["changed"]}}'); + } + else if(toState.name === 'portalMode'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + } + else if(toState.name === 'projects'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + } + else if(toState.name === 'inventory'){ + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + } + else if(toState.name === 'adHocJobStdout'){ + $rootScope.socket.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); + } + }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) { diff --git a/awx/ui/client/src/config.js b/awx/ui/client/src/config.js index 55956732a6..0aa7dbaf17 100644 --- a/awx/ui/client/src/config.js +++ b/awx/ui/client/src/config.js @@ -28,7 +28,7 @@ // custom_login_info: "example notice" // have a notice displayed in the login modal for users. note that, as a security measure, custom html is not supported and will be escaped. tooltip_delay: { show: 500, hide: 100 }, // Default number of milliseconds to delay displaying/hiding tooltips - debug_mode: false, // Enable console logging messages + debug_mode: true, // Enable console logging messages password_length: 8, // Minimum user password length. Set to 0 to not set a limit password_hasLowercase: true, // require a lowercase letter in the password 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 9181b21b20..d883464938 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -198,7 +198,7 @@ export default "

Unreachable

\n" + "

Failed

\n"; function openSocket() { - $rootScope.event_socket.on("job_events-" + job_id, function(data) { + $rootScope.socket.on("job_events-" + job_id, function(data) { // update elapsed time on each event received scope.job_status.elapsed = GetElapsed({ start: scope.job.created, @@ -213,9 +213,9 @@ export default }); // Unbind $rootScope socket event binding(s) so that they don't get triggered // in another instance of this controller - scope.$on('$destroy', function() { - $rootScope.event_socket.removeAllListeners("job_events-" + job_id); - }); + // scope.$on('$destroy', function() { + // $rootScope.socket.removeAllListeners("job_events-" + job_id); + // }); } openSocket(); diff --git a/awx/ui/client/src/job-detail/job-detail.route.js b/awx/ui/client/src/job-detail/job-detail.route.js index dda2722511..d08bcfecf8 100644 --- a/awx/ui/client/src/job-detail/job-detail.route.js +++ b/awx/ui/client/src/job-detail/job-detail.route.js @@ -13,22 +13,22 @@ export default { parent: 'jobs', label: "{{ job.id }} - {{ job.name }}" }, - resolve: { - jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { - if (!$rootScope.event_socket) { - $rootScope.event_socket = Socket({ - scope: $rootScope, - endpoint: "job_events" - }); - $rootScope.event_socket.init(); - // returns should really be providing $rootScope.event_socket - // otherwise, we have to inject the entire $rootScope into the controller - return true; - } else { - return true; - } - }] - }, + // resolve: { + // jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { + // if (!$rootScope.event_socket) { + // $rootScope.event_socket = Socket({ + // scope: $rootScope, + // endpoint: "job_events" + // }); + // $rootScope.event_socket.init(); + // // returns should really be providing $rootScope.event_socket + // // otherwise, we have to inject the entire $rootScope into the controller + // return true; + // } else { + // return true; + // } + // }] + // }, templateUrl: templateUrl('job-detail/job-detail'), controller: 'JobDetailController' }; diff --git a/awx/ui/client/src/shared/Socket.js b/awx/ui/client/src/shared/Socket.js index 886b51d4a3..5bc01d6879 100644 --- a/awx/ui/client/src/shared/Socket.js +++ b/awx/ui/client/src/shared/Socket.js @@ -22,16 +22,16 @@ * @methodOf shared.function:Socket * @description */ +import ReconnectingWebSocket from 'reconnectingwebsocket' export default angular.module('SocketIO', ['Utilities']) .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', 'Store', function ($rootScope, $location, $log, Authorization, Store) { return function(params) { var scope = params.scope, - host = $location.host(), + host = window.location.host, endpoint = params.endpoint, protocol = $location.protocol(), - io = require('socket.io-client'), config, socketPort, url; @@ -45,7 +45,9 @@ angular.module('SocketIO', ['Utilities']) config = Store('AnsibleConfig'); socketPort = config.websocket_port; } - url = protocol + '://' + host + ':' + socketPort + '/socket.io/' + endpoint; + + url = "ws://" + host + "/websocket/"; + // url = protocol + '://' + host + ':' + socketPort + '/socket.io/' + endpoint; $log.debug('opening socket connection to: ' + url); function getSocketTip(status) { @@ -75,86 +77,10 @@ angular.module('SocketIO', ['Utilities']) // We have a valid session token, so attempt socket connection $log.debug('Socket connecting to: ' + url); self.scope.socket_url = url; - self.socket = io.connect(url, { - query: "Token="+token, - headers: - { - 'Authorization': 'Token ' + token, // i don't think these are actually inserted into the header--jt - 'X-Auth-Token': 'Token ' + token - }, - 'connect timeout': 3000, - 'try multiple transports': false, - 'max reconnection attempts': 10, - 'reconnection limit': 2000, - 'force new connection': true - }); - - self.socket.on('connection', function() { - $log.debug('Socket connecting...'); - self.scope.$apply(function () { - self.scope.socketStatus = 'connecting'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('connect', function() { - $log.debug('Socket connection established'); - self.scope.$apply(function () { - self.scope.socketStatus = 'ok'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('connect_failed', function(reason) { - var r = reason || 'connection refused by host', - token_actual = Authorization.getToken(); - - $log.debug('Socket connection failed: ' + r); - - if (token_actual === token) { - self.socket.socket.disconnect(); - } - - self.scope.$apply(function () { - self.scope.socketStatus = 'error'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - - }); - self.socket.on('diconnect', function() { - $log.debug('Socket disconnected'); - self.scope.$apply(function() { - self.scope.socketStatus = 'error'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('error', function(reason) { - var r = reason || 'connection refused by host'; - $log.debug('Socket error: ' + r); - $log.error('Socket error: ' + r); - self.scope.$apply(function() { - self.scope.socketStatus = 'error'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('reconnecting', function() { - $log.debug('Socket attempting reconnect...'); - self.scope.$apply(function() { - self.scope.socketStatus = 'connecting'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('reconnect', function() { - $log.debug('Socket reconnected'); - self.scope.$apply(function() { - self.scope.socketStatus = 'ok'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); - }); - self.socket.on('reconnect_failed', function(reason) { - $log.error('Socket reconnect failed: ' + reason); - self.scope.$apply(function() { - self.scope.socketStatus = 'error'; - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - }); + self.socket = new ReconnectingWebSocket(url, null, { + debug: true, + timeoutInterval: 3000, + maxReconnectAttempts: 10 }); } else { @@ -167,21 +93,19 @@ angular.module('SocketIO', ['Utilities']) // Check connection status var self = this; if(self){ - if(self.socket){ - if(self.socket.socket){ - if (self.socket.socket.connected) { - self.scope.socketStatus = 'ok'; - } - else if (self.socket.socket.connecting || self.socket.reconnecting) { - self.scope.socketStatus = 'connecting'; - } - else { - self.scope.socketStatus = 'error'; - } - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - return self.scope.socketStatus; + if(self.socket){ + if (self.socket.readyState === 0 ) { + self.scope.socketStatus = 'connecting'; + } + else if (self.socket.readyState === 1){ + self.scope.socketStatus = 'ok'; + } + else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ + self.scope.socketStatus = 'error'; + } + self.scope.socketTip = getSocketTip(self.scope.socketStatus); + return self.scope.socketStatus; } - } } }, @@ -189,7 +113,8 @@ angular.module('SocketIO', ['Utilities']) var self = this; if(self){ if(self.socket){ - self.socket.on(eventName, function () { + self.socket.onmessage(eventName, function (e) { + console.log('Received From Server: ' + e.data); var args = arguments; self.scope.$apply(function () { callback.apply(self.socket, args); @@ -201,7 +126,7 @@ angular.module('SocketIO', ['Utilities']) }, emit: function (eventName, data, callback) { var self = this; - self.socket.emit(eventName, data, function () { + self.socket.send(eventName, data, function () { var args = arguments; self.scope.$apply(function () { if (callback) { @@ -217,7 +142,7 @@ angular.module('SocketIO', ['Utilities']) var self = this; if(self){ if(self.socket){ - self.socket.removeAllListeners(eventName); + self.socket.removeEventListener(eventName); } } }, diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 06e9cd7b64..51d1a8ea7b 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.event_socket.on("job_events-" + job_id, function() { + $rootScope.socket.on("job_events-" + job_id, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; @@ -30,9 +30,9 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce }); // Unbind $rootScope socket event binding(s) so that they don't get triggered // in another instance of this controller - $scope.$on('$destroy', function() { - $rootScope.event_socket.removeAllListeners("job_events-" + job_id); - }); + // $scope.$on('$destroy', function() { + // $rootScope.socket.removeAllListeners("job_events-" + job_id); + // }); } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); @@ -44,9 +44,9 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce }); // Unbind $rootScope socket event binding(s) so that they don't get triggered // in another instance of this controller - $scope.$on('$destroy', function() { - $rootScope.adhoc_event_socket.removeAllListeners("ad_hoc_command_events-" + job_id); - }); + // $scope.$on('$destroy', function() { + // $rootScope.adhoc_event_socket.removeAllListeners("ad_hoc_command_events-" + job_id); + // }); } } diff --git a/awx/ui/package.json b/awx/ui/package.json index 414150fa49..5e5d474a6f 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -99,7 +99,8 @@ "moment": "^2.10.2", "ng-toast": "leigh-johnson/ngToast#2.0.1", "nvd3": "leigh-johnson/nvd3#1.7.1", - "select2": "^4.0.2", - "socket.io-client": "^0.9.17" + "reconnectingwebsocket": "^1.0.0", + "rrule": "jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", + "select2": "^4.0.2" } } diff --git a/awx/ui/webpack.config.js b/awx/ui/webpack.config.js index a5e631aa9b..57d8dd880c 100644 --- a/awx/ui/webpack.config.js +++ b/awx/ui/webpack.config.js @@ -27,7 +27,7 @@ var vendorPkgs = [ 'ng-toast', 'nvd3', 'select2', - 'socket.io-client', + 'reconnectingwebsocket' ]; var dev = { From 3d979bb6618dbcc6c5a7d686676e80682d89980a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 29 Aug 2016 16:52:52 -0700 Subject: [PATCH 15/48] draft 1 of socket refactoring --- awx/main/consumers.py | 2 + awx/ui/client/src/app.js | 68 +++++++++++- .../src/job-detail/job-detail.controller.js | 5 +- awx/ui/client/src/shared/Socket.js | 102 ++++++++++++++---- 4 files changed, 151 insertions(+), 26 deletions(-) diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 990e197cd2..67fe0e428d 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -16,7 +16,9 @@ def ws_disconnect(message): @channel_session def ws_receive(message): + print(message) raw_data = message.content['text'] + print(raw_data) data = json.loads(raw_data) if 'groups' in data: diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 76506bf43e..5048c69572 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -716,6 +716,62 @@ var tower = angular.module('Tower', [ function openSocket() { $rootScope.socket = Socket({ scope: $rootScope}); $rootScope.socket.init(); + // $rootScope.socket.on("status_changed", function(data) { + // $log.debug('Job ' + data.unified_job_id + + // ' status changed to ' + data.status + + // ' send to ' + $location.$$url); + // + // // this acts as a router...it emits the proper + // // value based on what URL the user is currently + // // accessing. + // if ($state.is('jobs')) { + // $rootScope.$emit('JobStatusChange-jobs', data); + // } else if ($state.includes('jobDetail') || + // $state.is('adHocJobStdout') || + // $state.is('inventorySyncStdout') || + // $state.is('managementJobStdout') || + // $state.is('scmUpdateStdout')) { + // + // $log.debug("sending status to standard out"); + // $rootScope.$emit('JobStatusChange-jobStdout', data); + // } + // if ($state.includes('jobDetail')) { + // $rootScope.$emit('JobStatusChange-jobDetails', data); + // } else if ($state.is('dashboard')) { + // $rootScope.$emit('JobStatusChange-home', data); + // } else if ($state.is('portalMode')) { + // $rootScope.$emit('JobStatusChange-portal', data); + // } else if ($state.is('projects')) { + // $rootScope.$emit('JobStatusChange-projects', data); + // } else if ($state.is('inventoryManage')) { + // $rootScope.$emit('JobStatusChange-inventory', data); + // } + // }); + // $rootScope.socket.on("summary_complete", function(data) { + // $log.debug('Job summary_complete ' + data.unified_job_id); + // $rootScope.$emit('JobSummaryComplete', data); + // }); + + // schedule_socket = Socket({ + // scope: $rootScope, + // endpoint: "schedules" + // }); + // schedule_socket.init(); + // $rootScope.socket.on("schedule_changed", function(data) { + // $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); + // $rootScope.$emit('ScheduleStatusChange', data); + // }); + + // control_socket = Socket({ + // scope: $rootScope, + // endpoint: "control" + // }); + // control_socket.init(); + // $rootScope.socket.on("limit_reached", function(data) { + // $log.debug(data.reason); + // $rootScope.sessionTimer.expireSession('session_limit'); + // $state.go('signOut'); + // }); } openSocket(); @@ -737,27 +793,35 @@ var tower = angular.module('Tower', [ $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { if(toState.name === 'dashboard'){ $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + console.log(toState.name); } else if(toState.name === 'jobDetail'){ - $rootScope.socket.emit(`{"groups":{"jobs": ["status_changed", "summary"]},{"job_events":[${toParams.id}]}}`); + $rootScope.socket.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); + console.log(toState.name); } else if(toState.name === 'jobStdout'){ $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + console.log(toState.name); } else if(toState.name === 'jobs'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}, {"schedules": ["changed"]}}'); + $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); + console.log(toState.name); } else if(toState.name === 'portalMode'){ $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + console.log(toState.name); } else if(toState.name === 'projects'){ $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + console.log(toState.name); } else if(toState.name === 'inventory'){ $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + console.log(toState.name); } else if(toState.name === 'adHocJobStdout'){ $rootScope.socket.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); + console.log(toState.name); } }); 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 d883464938..71940a22f9 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -198,7 +198,10 @@ export default "

Unreachable

\n" + "

Failed

\n"; function openSocket() { - $rootScope.socket.on("job_events-" + job_id, function(data) { + if ($rootScope.removeJobEventChange) { + $rootScope.removeJobEventChange(); + } + $rootScope.removeJobEventChange = $rootScope.$on("job_events-" + job_id, function(e, data) { // update elapsed time on each event received scope.job_status.elapsed = GetElapsed({ start: scope.job.created, diff --git a/awx/ui/client/src/shared/Socket.js b/awx/ui/client/src/shared/Socket.js index 5bc01d6879..ddc0d74fdc 100644 --- a/awx/ui/client/src/shared/Socket.js +++ b/awx/ui/client/src/shared/Socket.js @@ -26,28 +26,15 @@ import ReconnectingWebSocket from 'reconnectingwebsocket' export default angular.module('SocketIO', ['Utilities']) - .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', 'Store', function ($rootScope, $location, $log, Authorization, Store) { + .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', + '$state', + function ($rootScope, $location, $log, Authorization, $state) { return function(params) { var scope = params.scope, host = window.location.host, - endpoint = params.endpoint, - protocol = $location.protocol(), - config, socketPort, url; - // Since some pages are opened in a new tab, we might get here before AnsibleConfig is available. - // In that case, load from local storage. - if ($AnsibleConfig) { - socketPort = $AnsibleConfig.websocket_port; - } - else { - $log.debug('getting web socket port from local storage'); - config = Store('AnsibleConfig'); - socketPort = config.websocket_port; - } - url = "ws://" + host + "/websocket/"; - // url = protocol + '://' + host + ':' + socketPort + '/socket.io/' + endpoint; $log.debug('opening socket connection to: ' + url); function getSocketTip(status) { @@ -82,6 +69,75 @@ angular.module('SocketIO', ['Utilities']) timeoutInterval: 3000, maxReconnectAttempts: 10 }); + self.socket.onopen = function () { + console.log('websocket connected'); //log errors + }; + + self.socket.onerror = function (error) { + console.log('Error Logged: ' + error); //log errors + }; + self.socket.onmessage = function (e) { + console.log('Received From Server: ' + e.data); + var data = JSON.parse(e.data); + // {'groups': + // {'jobs': ['status_changed', 'summary'], + // 'schedules': ['changed'], + // 'ad_hoc_command_events': [ids,], + // 'job_events': [ids,], + // 'control': ['limit_reached'], + // } + // } + if(data.group_name==="jobs"){ + + if (!('status' in data)){ + // we know that this must have been a + // summary complete message + $log.debug('Job summary_complete ' + data.unified_job_id); + $rootScope.$emit('JobSummaryComplete', data); + } + if ($state.is('jobs')) { + $rootScope.$emit('JobStatusChange-jobs', data); + } else if ($state.includes('jobDetail') || + $state.is('adHocJobStdout') || + $state.is('inventorySyncStdout') || + $state.is('managementJobStdout') || + $state.is('scmUpdateStdout')) { + + $log.debug("sending status to standard out"); + $rootScope.$emit('JobStatusChange-jobStdout', data); + } + if ($state.includes('jobDetail')) { + $rootScope.$emit('JobStatusChange-jobDetails', data); + } else if ($state.is('dashboard')) { + $rootScope.$emit('JobStatusChange-home', data); + } else if ($state.is('portalMode')) { + $rootScope.$emit('JobStatusChange-portal', data); + } else if ($state.is('projects')) { + $rootScope.$emit('JobStatusChange-projects', data); + } else if ($state.is('inventoryManage')) { + $rootScope.$emit('JobStatusChange-inventory', data); + } + } + if(data.group_name==="job_events"){ + $rootScope.$emit('job_events-'+data.job, data); + } + if(data.group_name==="schedules"){ + $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); + $rootScope.$emit('ScheduleStatusChange', data); + } + if(data.group_name==="ad_hoc_command_events"){ + + } + if(data.group_name==="control"){ + $log.debug(data.reason); + $rootScope.sessionTimer.expireSession('session_limit'); + $state.go('signOut'); + } + + + + }; + } else { // encountered expired token, redirect to login page @@ -113,19 +169,19 @@ angular.module('SocketIO', ['Utilities']) var self = this; if(self){ if(self.socket){ - self.socket.onmessage(eventName, function (e) { - console.log('Received From Server: ' + e.data); - var args = arguments; - self.scope.$apply(function () { - callback.apply(self.socket, args); - }); - }); + // self.socket.onmessage(function (e) { + // var args = arguments; + // self.scope.$apply(function () { + // callback.apply(self.socket, args); + // }); + // }); } } }, emit: function (eventName, data, callback) { var self = this; + // console.log(eventName) self.socket.send(eventName, data, function () { var args = arguments; self.scope.$apply(function () { From a091069d9de94a02db5b715d99e6b61166f5aa2f Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 30 Aug 2016 14:27:20 -0700 Subject: [PATCH 16/48] Move socket helper to a service and remove old code --- awx/ui/client/src/app.js | 41 ++--- awx/ui/client/src/help.js | 3 - awx/ui/client/src/help/ChromeSocketHelp.js | 47 ----- awx/ui/client/src/help/FirefoxSocketHelp.js | 78 -------- awx/ui/client/src/help/InventoryGroups.js | 86 --------- awx/ui/client/src/help/SafariSocketHelp.js | 50 ----- awx/ui/client/src/helpers.js | 2 - awx/ui/client/src/helpers/SocketHelper.js | 39 ---- awx/ui/client/src/shared/socket/main.js | 13 ++ .../src/shared/socket/socket.service.js | 173 ++++++++++++++++++ .../log/standard-out-log.controller.js | 2 +- 11 files changed, 205 insertions(+), 329 deletions(-) delete mode 100644 awx/ui/client/src/help/ChromeSocketHelp.js delete mode 100644 awx/ui/client/src/help/FirefoxSocketHelp.js delete mode 100644 awx/ui/client/src/help/InventoryGroups.js delete mode 100644 awx/ui/client/src/help/SafariSocketHelp.js delete mode 100644 awx/ui/client/src/helpers/SocketHelper.js create mode 100644 awx/ui/client/src/shared/socket/main.js create mode 100644 awx/ui/client/src/shared/socket/socket.service.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 5048c69572..f1441c9216 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -75,7 +75,7 @@ import './shared/Modal'; import './shared/prompt-dialog'; import './shared/directives'; import './shared/filters'; -import './shared/Socket'; +import socket from './shared/socket/main'; import './shared/features/main'; import config from './shared/config/main'; import './login/authenticationServices/pendo/ng-pendo'; @@ -183,7 +183,6 @@ var tower = angular.module('Tower', [ 'HostGroupsFormDefinition', 'StreamWidget', 'JobsHelper', - 'InventoryGroupsHelpDefinition', 'CredentialsHelper', 'StreamListDefinition', 'ActivityDetailDefinition', @@ -197,10 +196,9 @@ var tower = angular.module('Tower', [ 'StandardOutHelper', 'LogViewerOptionsDefinition', 'JobDetailHelper', - 'SocketIO', + 'socket', 'lrInfiniteScroll', 'LoadConfigHelper', - 'SocketHelper', 'PortalJobsListDefinition', 'features', 'longDateFilter', @@ -528,15 +526,15 @@ var tower = angular.module('Tower', [ .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', - 'ClearScope', 'Socket', 'LoadConfig', 'Store', - 'ShowSocketHelp', 'pendoService', 'Prompt', 'Rest', 'Wait', + 'ClearScope', 'LoadConfig', 'Store', + 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', - 'FeaturesService', '$filter', + 'FeaturesService', '$filter', 'SocketService', function($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, - $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, - LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait, + $location, Authorization, LoadBasePaths, Timer, ClearScope, + LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, - $filter) { + $filter, SocketService) { var sock; $rootScope.addPermission = function(scope) { $compile("")(scope); @@ -792,35 +790,35 @@ var tower = angular.module('Tower', [ // } $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { if(toState.name === 'dashboard'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'jobDetail'){ - $rootScope.socket.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); + SocketService.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); console.log(toState.name); } else if(toState.name === 'jobStdout'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'jobs'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); console.log(toState.name); } else if(toState.name === 'portalMode'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'projects'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'inventory'){ - $rootScope.socket.emit('{"groups":{"jobs": ["status_changed"]}}'); + SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); console.log(toState.name); } else if(toState.name === 'adHocJobStdout'){ - $rootScope.socket.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); + SocketService.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); console.log(toState.name); } }); @@ -882,7 +880,8 @@ var tower = angular.module('Tower', [ ConfigService.getConfig().then(function() { Timer.init().then(function(timer) { $rootScope.sessionTimer = timer; - $rootScope.$emit('OpenSocket'); + // $rootScope.$emit('OpenSocket'); + SocketService.init(); pendoService.issuePendoIdentity(); CheckLicense.test(); FeaturesService.get(); @@ -910,10 +909,6 @@ var tower = angular.module('Tower', [ $('#' + tabs + ' #' + tab).tab('show'); }; - $rootScope.socketHelp = function() { - ShowSocketHelp(); - }; - $rootScope.leavePortal = function() { $rootScope.portalMode = false; $location.path('/home/'); diff --git a/awx/ui/client/src/help.js b/awx/ui/client/src/help.js index 0c526d541f..563ce5d460 100644 --- a/awx/ui/client/src/help.js +++ b/awx/ui/client/src/help.js @@ -4,7 +4,4 @@ * All Rights Reserved *************************************************/ -import "./help/ChromeSocketHelp"; -import "./help/FirefoxSocketHelp"; import "./help/InventoryGroups"; -import "./help/SafariSocketHelp"; diff --git a/awx/ui/client/src/help/ChromeSocketHelp.js b/awx/ui/client/src/help/ChromeSocketHelp.js deleted file mode 100644 index d2ea6ef44a..0000000000 --- a/awx/ui/client/src/help/ChromeSocketHelp.js +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc overview - * @name help - * @description These are the modal windows that are shown to the user to give additional guidance on certain tasks that might not be straightforward. -*/ - - /** - * @ngdoc function - * @name help.function:ChromeSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Chrome. -*/ - - -angular.module('ChromeSocketHelpDefinition', []) - .value('ChromeSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall." - }] - } - }); diff --git a/awx/ui/client/src/help/FirefoxSocketHelp.js b/awx/ui/client/src/help/FirefoxSocketHelp.js deleted file mode 100644 index 989bb2fb87..0000000000 --- a/awx/ui/client/src/help/FirefoxSocketHelp.js +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:FirefoxSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Firefox. -*/ - - -angular.module('FFSocketHelpDefinition', []) - .value('FFSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall.

" - }, { - intro: 'Self signed certificate:', - icon: { - "class": "fa fa-5x fa-check ok-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

If the Tower web server is using a self signed security certificate, Firefox needs to accept the certificate and allow the " + - "connection.

Click Next for help accepting a self signed certificate.

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'understand_the_risk.png', - maxWidth: 440 - }, - box: "

Navigate to {{ socketURL }} The above warning will appear.

Click I Understand the Risks

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'add_exception.png', - maxWidth: 440 - }, - box: "

Click the Add Exception button." - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'confirm_exception.png', - maxWidth: 340 - }, - box: "

Click the Confirm the Security Exception button. This will add the self signed certificate from the Tower server to Firefox's list of trusted certificates.

" - }, { - intro: 'Accepting a self-signed certificate:', - img: { - src: 'refresh_firefox.png', - maxWidth: 480 - }, - box: "

Now that Firefox has accepted the security certificate the live event connection status indicator should turn green. If it does not, reload Tower by clicking the " + - "Firefox refresh button." - }] - } - }); diff --git a/awx/ui/client/src/help/InventoryGroups.js b/awx/ui/client/src/help/InventoryGroups.js deleted file mode 100644 index c9ee3b7432..0000000000 --- a/awx/ui/client/src/help/InventoryGroups.js +++ /dev/null @@ -1,86 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:InventoryGroups - * @description This help modal walks the user how to add groups to an inventory or a subgroup to an existing group. -*/ - - -angular.module('InventoryGroupsHelpDefinition', []) - .value('InventoryGroupsHelp', { - story: { - hdr: 'Inventory Setup', - width: 510, - height: 560, - steps: [{ - intro: 'Start by creating a group:', - img: { - src: 'groups001.png', - maxWidth: 257, - maxHeight: 114 - }, - box: "Click on the groups list (the left side of the page) to add a new group.", - autoOffNotice: true - }, { - intro: 'Enter group properties:', - img: { - src: 'groups002.png', - maxWidth: 443, - maxHeight: 251 - }, - box: 'Enter the group name, a description and any inventory variables. Variables can be entered using either JSON or YAML syntax. ' + - 'For more on inventory variables, see ' + - 'docs.ansible.com/intro_inventory.html' - }, { - intro: 'Cloud inventory: select cloud source', - img: { - src: 'groups003.png', - maxWidth: 412, - maxHeight: 215 - }, - box: "For a cloud inventory, choose the cloud provider from the list and select your credentials. If you have not already setup " + - "credentials for the provider, you will need to do that first on the Credentials tab." - }, { - intro: 'Cloud inventory: synchronize Tower with the cloud', - img: { - src: 'groups004.png', - maxWidth: 187, - maxHeight: 175 - }, - box: "To import a cloud inventory into Tower, initiate an inventory sync by clicking ." - }, { - intro: "Add subgroups:", - img: { - src: 'groups008.png', - maxWidth: 469, - maxHeight: 243 - }, - box: "

First, select an existing group.
" - }, { - intro: "Add subgroups:", - img: { - src: 'groups009.png', - maxWidth: 475, - maxHeight: 198 - }, - box: "
Then click to create a new group. The new group " + - "will be added to the selected group.
" - }, { - intro: 'Add hosts:', - img: { - src: 'groups010.png', - maxWidth: 475, - maxHeight: 122 - }, - box: "

First, select a Group. " + - "Then click on the hosts list (the right side of the page) to create a host. " + - "The new host will be part of the selected group.

" - }] - } - }); diff --git a/awx/ui/client/src/help/SafariSocketHelp.js b/awx/ui/client/src/help/SafariSocketHelp.js deleted file mode 100644 index 46b9cf48b0..0000000000 --- a/awx/ui/client/src/help/SafariSocketHelp.js +++ /dev/null @@ -1,50 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name help.function:SafariSocketHelp - * @description This help modal gives instructions on what the user should do if not connected to the web sockets while using Safari. Safari does not support websockets. -*/ - - -angular.module('SafariSocketHelpDefinition', []) - .value('SafariSocketHelp', { - story: { - hdr: 'Live Events', - width: 510, - height: 560, - steps: [{ - intro: 'Connection status indicator:', - img: { - src: 'socket_indicator.png', - maxWidth: 360 - }, - box: "

indicates live events are streaming and the browser is connected to the live events server.

If the indicator continually shows " + - "or , then live events are not streaming, and the browser is having difficulty connecting to the live events server. In this case click Next for troubleshooting help.

" - }, { - intro: 'Live events connection:', - icon: { - "class": "fa fa-5x fa-rss {{ socketStatus }}-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

{{ browserName }} is connecting to the live events server on port {{ socketPort }}. The current connection status is " + - " {{ socketStatus }}.

If the connection status indicator is not green, have the " + - "system administrator verify this is the correct port and that access to the port is not blocked by a firewall.

" - }, { - intro: 'Self signed certificate:', - icon: { - "class": "fa fa-5x fa-check ok-color", - style: "margin-top: 75px;", - containerHeight: 200 - }, - box: "

Safari will not connect to the live event port when the Tower web server is configured with a self signed certificate. Check with a system administrator to " + - "determine if Tower is using a self signed certificate. Installing a signed certificate will fix the problem.

" + - "

Switching browsers to either Chrome or Firefox will work as well.

" - }] - } - }); diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index aae8a17225..cf5f65e601 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -23,7 +23,6 @@ import ProjectPath from "./helpers/ProjectPath"; import Projects from "./helpers/Projects"; import Schedules from "./helpers/Schedules"; import Selection from "./helpers/Selection"; -import SocketHelper from "./helpers/SocketHelper"; import Users from "./helpers/Users"; import Variables from "./helpers/Variables"; import ApiDefaults from "./helpers/api-defaults"; @@ -55,7 +54,6 @@ export Projects, Schedules, Selection, - SocketHelper, Users, Variables, ApiDefaults, diff --git a/awx/ui/client/src/helpers/SocketHelper.js b/awx/ui/client/src/helpers/SocketHelper.js deleted file mode 100644 index ea0110cf01..0000000000 --- a/awx/ui/client/src/helpers/SocketHelper.js +++ /dev/null @@ -1,39 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:SocketHelper - * @description - * SocketHelper.js - * - * Show web socket troubleshooting help - * - */ - - -export default - angular.module('SocketHelper', ['Utilities', 'FFSocketHelpDefinition', 'SafariSocketHelpDefinition' , 'ChromeSocketHelpDefinition']) - - .factory('ShowSocketHelp', ['$location', '$rootScope', 'FFSocketHelp', 'SafariSocketHelp', 'ChromeSocketHelp', 'HelpDialog', 'browserData', - function($location, $rootScope, FFSocketHelp, SafariSocketHelp, ChromeSocketHelp, HelpDialog, browserData) { - return function() { - var scope = $rootScope.$new(); - scope.socketPort = $AnsibleConfig.websocket_port; - scope.socketURL = 'https://' + $location.host() + ':' + scope.socketPort + '/'; - scope.browserName = browserData.name; - - if (browserData.name === 'Firefox') { - HelpDialog({ defn: FFSocketHelp, scope: scope }); - } - else if (browserData.name === 'Safari') { - HelpDialog({ defn: SafariSocketHelp, scope: scope }); - } - else { - HelpDialog({ defn: ChromeSocketHelp, scope: scope }); - } - }; - }]); diff --git a/awx/ui/client/src/shared/socket/main.js b/awx/ui/client/src/shared/socket/main.js new file mode 100644 index 0000000000..3b38053762 --- /dev/null +++ b/awx/ui/client/src/shared/socket/main.js @@ -0,0 +1,13 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +// import awFeatureDirective from './features.directive'; +import socketService from './socket.service'; + +export default + angular.module('socket', []) + // .directive('awFeature', awFeatureDirective) + .service('SocketService', socketService); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js new file mode 100644 index 0000000000..ca7ddb2aee --- /dev/null +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -0,0 +1,173 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ +import ReconnectingWebSocket from 'reconnectingwebsocket' +export default +['$rootScope', '$location', '$log', 'Authorization','$state', + function ($rootScope, $location, $log, Authorization, $state) { + return { + init: function() { + var self = this, + token = Authorization.getToken(), + host = window.location.host, + url = "ws://" + host + "/websocket/"; + if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { + // We have a valid session token, so attempt socket connection + $log.debug('Socket connecting to: ' + url); + self.socket = new ReconnectingWebSocket(url, null, { + debug: true, + timeoutInterval: 3000, + maxReconnectAttempts: 10 + }); + self.socket.onopen = function () { + console.log('websocket connected'); //log errors + }; + + self.socket.onerror = function (error) { + console.log('Error Logged: ' + error); //log errors + }; + self.socket.onmessage = function (e) { + console.log('Received From Server: ' + e.data); + var data = JSON.parse(e.data); + // {'groups': + // {'jobs': ['status_changed', 'summary'], + // 'schedules': ['changed'], + // 'ad_hoc_command_events': [ids,], + // 'job_events': [ids,], + // 'control': ['limit_reached'], + // } + // } + if(data.group_name==="jobs"){ + + if (!('status' in data)){ + // we know that this must have been a + // summary complete message + $log.debug('Job summary_complete ' + data.unified_job_id); + $rootScope.$emit('JobSummaryComplete', data); + } + if ($state.is('jobs')) { + $rootScope.$emit('JobStatusChange-jobs', data); + } else if ($state.includes('jobDetail') || + $state.is('adHocJobStdout') || + $state.is('inventorySyncStdout') || + $state.is('managementJobStdout') || + $state.is('scmUpdateStdout')) { + + $log.debug("sending status to standard out"); + $rootScope.$emit('JobStatusChange-jobStdout', data); + } + if ($state.includes('jobDetail')) { + $rootScope.$emit('JobStatusChange-jobDetails', data); + } else if ($state.is('dashboard')) { + $rootScope.$emit('JobStatusChange-home', data); + } else if ($state.is('portalMode')) { + $rootScope.$emit('JobStatusChange-portal', data); + } else if ($state.is('projects')) { + $rootScope.$emit('JobStatusChange-projects', data); + } else if ($state.is('inventoryManage')) { + $rootScope.$emit('JobStatusChange-inventory', data); + } + } + if(data.group_name==="job_events"){ + $rootScope.$emit('job_events-'+data.job, data); + } + if(data.group_name==="schedules"){ + $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); + $rootScope.$emit('ScheduleStatusChange', data); + } + if(data.group_name==="ad_hoc_command_events"){ + + } + if(data.group_name==="control"){ + $log.debug(data.reason); + $rootScope.sessionTimer.expireSession('session_limit'); + $state.go('signOut'); + } + + return self.socket; + + }; + + } + else { + // encountered expired token, redirect to login page + $rootScope.sessionTimer.expireSession('idle'); + $location.url('/login'); + } + }, + checkStatus: function() { + + function getSocketTip(status) { + var result = ''; + switch(status) { + case 'error': + result = "Live events: error connecting to the Tower server."; + break; + case 'connecting': + result = "Live events: attempting to connect to the Tower server."; + break; + case "ok": + result = "Live events: connected. Pages containing job status information will automatically update in real-time."; + } + return result; + } + // Check connection status + var self = this; + if(self){ + if(self.socket){ + if (self.socket.readyState === 0 ) { + self.scope.socketStatus = 'connecting'; + } + else if (self.socket.readyState === 1){ + self.scope.socketStatus = 'ok'; + } + else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ + self.scope.socketStatus = 'error'; + } + self.scope.socketTip = getSocketTip(self.scope.socketStatus); + return self.scope.socketStatus; + } + } + + }, + on: function (eventName, callback) { + var self = this; + if(self){ + if(self.socket){ + // self.socket.onmessage(function (e) { + // var args = arguments; + // self.scope.$apply(function () { + // callback.apply(self.socket, args); + // }); + // }); + } + } + + }, + emit: function (eventName, data, callback) { + var self = this; + // console.log(eventName) + self.socket.send(eventName, data, function () { + var args = arguments; + self.scope.$apply(function () { + if (callback) { + callback.apply(self.socket, args); + } + }); + }); + }, + getUrl: function() { + return url; + }, + removeAllListeners: function (eventName) { + var self = this; + if(self){ + if(self.socket){ + self.socket.removeEventListener(eventName); + } + } + }, + }; + }]; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 51d1a8ea7b..5f05f9e4fc 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.socket.on("job_events-" + job_id, function() { + $rootScope.$on("job_events-" + job_id, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; From e35b5ef6fbbd2acd187f5370a5af59fad744eacb Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 30 Aug 2016 14:45:33 -0700 Subject: [PATCH 17/48] Adhoc stdout working with new websockets --- awx/ui/client/src/app.js | 2 +- awx/ui/client/src/shared/socket/socket.service.js | 2 +- .../standard-out/adhoc/standard-out-adhoc.route.js | 14 -------------- .../log/standard-out-log.controller.js | 2 +- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index f1441c9216..c21bfca869 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -818,7 +818,7 @@ var tower = angular.module('Tower', [ console.log(toState.name); } else if(toState.name === 'adHocJobStdout'){ - SocketService.emit(`{"groups":{"ad_hoc_command_events": [${toParams.id}]}}`); + SocketService.emit(`{"groups":{"jobs": ["status_changed"] , "ad_hoc_command_events": [${toParams.id}]}}`); console.log(toState.name); } }); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index ca7ddb2aee..eadadfc83d 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -78,7 +78,7 @@ export default $rootScope.$emit('ScheduleStatusChange', data); } if(data.group_name==="ad_hoc_command_events"){ - + $rootScope.$emit('ad_hoc_command_events-'+data.ad_hoc_command, data); } if(data.group_name==="control"){ $log.debug(data.reason); diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js index f878ef02f8..0df5cd54af 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js @@ -17,19 +17,5 @@ export default { }, data: { jobType: 'ad_hoc_commands' - }, - resolve: { - adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { - if (!$rootScope.adhoc_event_socket) { - $rootScope.adhoc_event_socket = Socket({ - scope: $rootScope, - endpoint: "ad_hoc_command_events" - }); - $rootScope.adhoc_event_socket.init(); - return true; - } else { - return true; - } - }] } }; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 5f05f9e4fc..74dfede3a0 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -36,7 +36,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() { + $rootScope.$on("ad_hoc_command_events-" + job_id, function() { $log.debug("socket fired on ad_hoc_command_events-" + job_id); if (api_complete) { event_queue++; From 813eeb9dd9c5fb6061ffc4665ffc730e46069e60 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 6 Sep 2016 18:01:31 -0700 Subject: [PATCH 18/48] fixing socketservice init on login --- awx/ui/client/src/app.js | 1 - awx/ui/client/src/help.js | 7 ------- .../client/src/login/loginModal/loginModal.controller.js | 8 +++++--- 3 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 awx/ui/client/src/help.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index c21bfca869..82279b427c 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -29,7 +29,6 @@ import './helpers'; import './forms'; import './lists'; import './widgets'; -import './help'; import './filters'; import { Home } from './controllers/Home'; import { SocketsController } from './controllers/Sockets'; diff --git a/awx/ui/client/src/help.js b/awx/ui/client/src/help.js deleted file mode 100644 index 563ce5d460..0000000000 --- a/awx/ui/client/src/help.js +++ /dev/null @@ -1,7 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import "./help/InventoryGroups"; diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index 9bd5132ab1..020c64b1ca 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -57,10 +57,11 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', '$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait', 'Timer', 'Empty', 'ClearScope', '$scope', 'pendoService', 'ConfigService', - 'CheckLicense', 'FeaturesService', + 'CheckLicense', 'FeaturesService', 'SocketService', function ($log, $cookieStore, $compile, $window, $rootScope, $location, Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope, - scope, pendoService, ConfigService, CheckLicense, FeaturesService) { + scope, pendoService, ConfigService, CheckLicense, FeaturesService, + SocketService) { var lastPath, lastUser, sessionExpired, loginAgain; loginAgain = function() { @@ -135,7 +136,8 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', Authorization.setUserInfo(data); Timer.init().then(function(timer){ $rootScope.sessionTimer = timer; - $rootScope.$emit('OpenSocket'); + // $rootScope.$emit('OpenSocket'); + SocketService.init(); $rootScope.user_is_superuser = data.results[0].is_superuser; $rootScope.user_is_system_auditor = data.results[0].is_system_auditor; scope.$emit('AuthorizationGetLicense'); From 0937866e2029420e74d0a7c9c17dd32c783680f7 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 31 Aug 2016 10:44:00 -0700 Subject: [PATCH 19/48] refactoring socket functions --- .../src/shared/socket/socket.service.js | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index eadadfc83d..6953574100 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -21,13 +21,13 @@ export default timeoutInterval: 3000, maxReconnectAttempts: 10 }); - self.socket.onopen = function () { - console.log('websocket connected'); //log errors - }; - - self.socket.onerror = function (error) { - console.log('Error Logged: ' + error); //log errors - }; + // self.socket.onopen = function () { + // console.log('websocket connected'); //log errors + // }; + // + // self.socket.onerror = function (error) { + // console.log('Error Logged: ' + error); //log errors + // }; self.socket.onmessage = function (e) { console.log('Received From Server: ' + e.data); var data = JSON.parse(e.data); @@ -96,6 +96,11 @@ export default $rootScope.sessionTimer.expireSession('idle'); $location.url('/login'); } + + setTimeout(function() { + self.checkStatus(); + $log.debug('socket status: ' + $rootScope.socketStatus); + }, 2000); }, checkStatus: function() { @@ -118,16 +123,16 @@ export default if(self){ if(self.socket){ if (self.socket.readyState === 0 ) { - self.scope.socketStatus = 'connecting'; + $rootScope.socketStatus = 'connecting'; } else if (self.socket.readyState === 1){ - self.scope.socketStatus = 'ok'; + $rootScope.socketStatus = 'ok'; } else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ - self.scope.socketStatus = 'error'; + $rootScope.socketStatus = 'error'; } - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - return self.scope.socketStatus; + self.socketTip = getSocketTip(self.socketStatus); + return $rootScope.socketStatus; } } From 9691e267dfb477248f22fa3e53491278ed34754a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 6 Sep 2016 18:48:23 -0700 Subject: [PATCH 20/48] adding promise for successful socket connection in order to prevent the a race condition: the socket was trying to emit messages to the API to subscribe to rooms before the socket connection was finshed connectig. --- awx/ui/client/src/app.js | 6 ++++- .../src/shared/socket/socket.service.js | 27 ++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 82279b427c..67d4d35a87 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -711,7 +711,7 @@ var tower = angular.module('Tower', [ } $rootScope.removeOpenSocket = $rootScope.$on('OpenSocket', function() { function openSocket() { - $rootScope.socket = Socket({ scope: $rootScope}); + // $rootScope.socket = Socket({ scope: $rootScope}); $rootScope.socket.init(); // $rootScope.socket.on("status_changed", function(data) { // $log.debug('Job ' + data.unified_job_id + @@ -924,6 +924,10 @@ var tower = angular.module('Tower', [ // create a promise that will resolve when features are loaded $rootScope.featuresConfigured = $q.defer(); } + if (!$rootScope.socketPromise) { + // create a promise that resolves when the socket connection is open + $rootScope.socketPromise = $q.defer(); + } $rootScope.licenseMissing = true; //the authorization controller redirects to the home page automatcially if there is no last path defined. in order to override // this, set the last path to /portal for instances where portal is visited for the first time. diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 6953574100..54aa4662ff 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -3,10 +3,10 @@ * * All Rights Reserved *************************************************/ -import ReconnectingWebSocket from 'reconnectingwebsocket' +import ReconnectingWebSocket from 'reconnectingwebsocket'; export default -['$rootScope', '$location', '$log', 'Authorization','$state', - function ($rootScope, $location, $log, Authorization, $state) { +['$rootScope', '$location', '$log', 'Authorization','$state', '$q', + function ($rootScope, $location, $log, Authorization, $state, $q) { return { init: function() { var self = this, @@ -21,9 +21,10 @@ export default timeoutInterval: 3000, maxReconnectAttempts: 10 }); - // self.socket.onopen = function () { - // console.log('websocket connected'); //log errors - // }; + self.socket.onopen = function () { + console.log('websocket connected'); //log errors + $rootScope.socketPromise.resolve(); + }; // // self.socket.onerror = function (error) { // console.log('Error Logged: ' + error); //log errors @@ -154,12 +155,14 @@ export default emit: function (eventName, data, callback) { var self = this; // console.log(eventName) - self.socket.send(eventName, data, function () { - var args = arguments; - self.scope.$apply(function () { - if (callback) { - callback.apply(self.socket, args); - } + $rootScope.socketPromise.promise.then(function(){ + self.socket.send(eventName, data, function () { + var args = arguments; + self.scope.$apply(function () { + if (callback) { + callback.apply(self.socket, args); + } + }); }); }); }, From 5b1d4f3d6786d79d5b69e10554c53801054cd759 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 8 Sep 2016 17:15:21 -0400 Subject: [PATCH 21/48] remove websocket proxy from build-docker-machine script, freeze ui deps --- awx/ui/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/ui/package.json b/awx/ui/package.json index 5e5d474a6f..0f212e163e 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -7,7 +7,6 @@ }, "config": { "django_port": "8013", - "websocket_port": "8080", "django_host": "0.0.0.0", "sauce_username": "leigh-johnson", "sauce_access_key": "f740c3ad-c706-4e10-bb95-46e2cc50c2ac" @@ -17,7 +16,7 @@ "npm": "^3.10.3" }, "scripts": { - "build-docker-machine": "docker-machine ssh $DOCKER_MACHINE_NAME -f -N -L ${npm_package_config_websocket_port}:localhost:${npm_package_config_websocket_port}; ip=$(docker-machine ip $DOCKER_MACHINE_NAME); npm set ansible-tower:django_host ${ip}; grunt dev", + "build-docker-machine": "ip=$(docker-machine ip $DOCKER_MACHINE_NAME); npm set ansible-tower:django_host ${ip}; grunt dev", "build-docker-cid": "ip=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' $DOCkER_CID` | npm set config ansible-tower:django_host ${ip}; grunt dev", "build-release": "grunt release", "pretest": "grunt clean:coverage", From aa4a8f608966aaa27a626a4dda064e4b13f4768a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 7 Sep 2016 15:48:56 -0700 Subject: [PATCH 22/48] Hooking SocketService.subscribe function to $stateExtender so that socket rooms can be configured on each route that needs sockets s --- awx/ui/client/src/app.js | 415 +++++++++--------- .../manage/inventory-manage.route.js | 5 + .../client/src/job-detail/job-detail.route.js | 22 +- awx/ui/client/src/shared/Socket.js | 207 --------- .../src/shared/socket/socket.service.js | 76 +++- .../src/shared/stateExtender.provider.js | 18 + 6 files changed, 295 insertions(+), 448 deletions(-) delete mode 100644 awx/ui/client/src/shared/Socket.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 67d4d35a87..db43657283 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -240,87 +240,98 @@ var tower = angular.module('Tower', [ }); $stateProvider. - state('dashboard', { - url: '/home', - templateUrl: urlPrefix + 'partials/home.html', - controller: Home, - params: { licenseMissing: null }, - data: { - activityStream: true, - refreshButton: true - }, - ncyBreadcrumb: { - label: "DASHBOARD" - }, - resolve: { - graphData: ['$q', 'jobStatusGraphData', '$rootScope', - function($q, jobStatusGraphData, $rootScope) { - return $rootScope.featuresConfigured.promise.then(function() { - return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all"), - }); - }); - } - ] - } - }). - - state('jobs', { - url: '/jobs', - templateUrl: urlPrefix + 'partials/jobs.html', - controller: JobsListController, - ncyBreadcrumb: { - label: "JOBS" - } - }). - - state('projects', { - url: '/projects?{status}', - templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsList, - data: { - activityStream: true, - activityStreamTarget: 'project' - }, - ncyBreadcrumb: { - label: "PROJECTS" - } - }). - - state('projects.add', { - url: '/add', - templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsAdd, - ncyBreadcrumb: { - parent: "projects", - label: "CREATE PROJECT" - } - }). - - state('projects.edit', { - url: '/:id', - templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsEdit, - data: { - activityStreamId: 'id' - }, - ncyBreadcrumb: { - parent: 'projects', - label: '{{name}}' - } - }). - - state('projectOrganizations', { - url: '/projects/:project_id/organizations', - templateUrl: urlPrefix + 'partials/projects.html', - controller: OrganizationsList - }). - - state('projectOrganizationAdd', { - url: '/projects/:project_id/organizations/add', - templateUrl: urlPrefix + 'partials/projects.html', - controller: OrganizationsAdd - }). + // state('dashboard', { + // url: '/home', + // templateUrl: urlPrefix + 'partials/home.html', + // controller: Home, + // params: { licenseMissing: null }, + // data: { + // activityStream: true, + // refreshButton: true + // }, + // ncyBreadcrumb: { + // label: "DASHBOARD" + // }, + // resolve: { + // graphData: ['$q', 'jobStatusGraphData', '$rootScope', + // function($q, jobStatusGraphData, $rootScope) { + // return $rootScope.featuresConfigured.promise.then(function() { + // return $q.all({ + // jobStatus: jobStatusGraphData.get("month", "all"), + // }); + // }); + // } + // ] + // } + // }). + // + // state('jobs', { + // url: '/jobs', + // templateUrl: urlPrefix + 'partials/jobs.html', + // controller: JobsListController, + // ncyBreadcrumb: { + // label: "JOBS" + // } + // }). + // + // state('projects', { + // url: '/projects?{status}', + // templateUrl: urlPrefix + 'partials/projects.html', + // controller: ProjectsList, + // data: { + // activityStream: true, + // activityStreamTarget: 'project' + // }, + // ncyBreadcrumb: { + // label: "PROJECTS" + // }, + // // socket: '{"groups":{"jobs": ["status_changed"]}}' + // // resolve: { + // // socket: ['SocketService', '$rootScope', + // // function(SocketService, $rootScope) { + // // var self = this; + // // $rootScope.socketPromise.promise.then(function(){ + // // SocketService.subscribe(self); + // // return true; + // // }); + // // }] + // // }, + // }). + // + // state('projects.add', { + // url: '/add', + // templateUrl: urlPrefix + 'partials/projects.html', + // controller: ProjectsAdd, + // ncyBreadcrumb: { + // parent: "projects", + // label: "CREATE PROJECT" + // } + // }). + // + // state('projects.edit', { + // url: '/:id', + // templateUrl: urlPrefix + 'partials/projects.html', + // controller: ProjectsEdit, + // data: { + // activityStreamId: 'id' + // }, + // ncyBreadcrumb: { + // parent: 'projects', + // label: '{{name}}' + // } + // }). + // + // state('projectOrganizations', { + // url: '/projects/:project_id/organizations', + // templateUrl: urlPrefix + 'partials/projects.html', + // controller: OrganizationsList + // }). + // + // state('projectOrganizationAdd', { + // url: '/projects/:project_id/organizations/add', + // templateUrl: urlPrefix + 'partials/projects.html', + // controller: OrganizationsAdd + // }). state('teams', { url: '/teams', @@ -523,18 +534,126 @@ var tower = angular.module('Tower', [ }]); }]) -.run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', +.run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', - 'ClearScope', 'LoadConfig', 'Store', - 'pendoService', 'Prompt', 'Rest', 'Wait', - 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', + 'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', + 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', 'FeaturesService', '$filter', 'SocketService', - function($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, - $location, Authorization, LoadBasePaths, Timer, ClearScope, - LoadConfig, Store, pendoService, Prompt, Rest, Wait, + function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, + CheckLicense, $location, Authorization, LoadBasePaths, Timer, + ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, $filter, SocketService) { - var sock; + + $stateExtender.addState({ + name: 'dashboard', + url: '/home', + templateUrl: urlPrefix + 'partials/home.html', + controller: Home, + params: { licenseMissing: null }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, + data: { + activityStream: true, + refreshButton: true + }, + ncyBreadcrumb: { + label: "DASHBOARD" + }, + resolve: { + graphData: ['$q', 'jobStatusGraphData', '$rootScope', + function($q, jobStatusGraphData, $rootScope) { + return $rootScope.featuresConfigured.promise.then(function() { + return $q.all({ + jobStatus: jobStatusGraphData.get("month", "all"), + }); + }); + } + ] + } + }); + + $stateExtender.addState({ + name: 'jobs', + url: '/jobs', + templateUrl: urlPrefix + 'partials/jobs.html', + controller: JobsListController, + ncyBreadcrumb: { + label: "JOBS" + }, + params: { + search: { + value: {order_by:'-finished'} + } + }, + socket: { + "groups":{ + "jobs": ["status_changed"], + "schedules": ["changed"] + } + } + }); + + $stateExtender.addState({ + name: 'projects', + url: '/projects?{status}', + templateUrl: urlPrefix + 'partials/projects.html', + controller: ProjectsList, + data: { + activityStream: true, + activityStreamTarget: 'project' + }, + ncyBreadcrumb: { + label: "PROJECTS" + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + } + }); + + $stateExtender.addState({ + name: 'projects.add', + url: '/add', + templateUrl: urlPrefix + 'partials/projects.html', + controller: ProjectsAdd, + ncyBreadcrumb: { + parent: "projects", + label: "CREATE PROJECT" + } + }); + + $stateExtender.addState({ + name: 'projects.edit', + url: '/:id', + templateUrl: urlPrefix + 'partials/projects.html', + controller: ProjectsEdit, + data: { + activityStreamId: 'id' + }, + ncyBreadcrumb: { + parent: 'projects', + label: '{{name}}' + } + }); + + $stateExtender.addState({ + name: 'projectOrganizations', + url: '/projects/:project_id/organizations', + templateUrl: urlPrefix + 'partials/projects.html', + controller: OrganizationsList + }); + + $stateExtender.addState({ + name: 'projectOrganizationAdd', + url: '/projects/:project_id/organizations/add', + templateUrl: urlPrefix + 'partials/projects.html', + controller: OrganizationsAdd + }); $rootScope.addPermission = function(scope) { $compile("")(scope); }; @@ -706,121 +825,9 @@ var tower = angular.module('Tower', [ $rootScope.crumbCache = []; - if ($rootScope.removeOpenSocket) { - $rootScope.removeOpenSocket(); - } - $rootScope.removeOpenSocket = $rootScope.$on('OpenSocket', function() { - function openSocket() { - // $rootScope.socket = Socket({ scope: $rootScope}); - $rootScope.socket.init(); - // $rootScope.socket.on("status_changed", function(data) { - // $log.debug('Job ' + data.unified_job_id + - // ' status changed to ' + data.status + - // ' send to ' + $location.$$url); - // - // // this acts as a router...it emits the proper - // // value based on what URL the user is currently - // // accessing. - // if ($state.is('jobs')) { - // $rootScope.$emit('JobStatusChange-jobs', data); - // } else if ($state.includes('jobDetail') || - // $state.is('adHocJobStdout') || - // $state.is('inventorySyncStdout') || - // $state.is('managementJobStdout') || - // $state.is('scmUpdateStdout')) { - // - // $log.debug("sending status to standard out"); - // $rootScope.$emit('JobStatusChange-jobStdout', data); - // } - // if ($state.includes('jobDetail')) { - // $rootScope.$emit('JobStatusChange-jobDetails', data); - // } else if ($state.is('dashboard')) { - // $rootScope.$emit('JobStatusChange-home', data); - // } else if ($state.is('portalMode')) { - // $rootScope.$emit('JobStatusChange-portal', data); - // } else if ($state.is('projects')) { - // $rootScope.$emit('JobStatusChange-projects', data); - // } else if ($state.is('inventoryManage')) { - // $rootScope.$emit('JobStatusChange-inventory', data); - // } - // }); - // $rootScope.socket.on("summary_complete", function(data) { - // $log.debug('Job summary_complete ' + data.unified_job_id); - // $rootScope.$emit('JobSummaryComplete', data); - // }); - - // schedule_socket = Socket({ - // scope: $rootScope, - // endpoint: "schedules" - // }); - // schedule_socket.init(); - // $rootScope.socket.on("schedule_changed", function(data) { - // $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); - // $rootScope.$emit('ScheduleStatusChange', data); - // }); - - // control_socket = Socket({ - // scope: $rootScope, - // endpoint: "control" - // }); - // control_socket.init(); - // $rootScope.socket.on("limit_reached", function(data) { - // $log.debug(data.reason); - // $rootScope.sessionTimer.expireSession('session_limit'); - // $state.go('signOut'); - // }); - } - openSocket(); - - setTimeout(function() { - $rootScope.$apply(function() { - $rootScope.socket.checkStatus(); - $log.debug('socket status: ' + $rootScope.socketStatus); - }); - }, 2000); - }); - // {'groups': - // {'jobs': ['status_changed', 'summary'], - // 'schedules': ['changed'], - // 'ad_hoc_command_events': [ids,], - // 'job_events': [ids,], - // 'control': ['limit_reached'], - // } - // } - $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { - if(toState.name === 'dashboard'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'jobDetail'){ - SocketService.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); - console.log(toState.name); - } - else if(toState.name === 'jobStdout'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'jobs'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'portalMode'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'projects'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'inventory'){ - SocketService.emit('{"groups":{"jobs": ["status_changed"]}}'); - console.log(toState.name); - } - else if(toState.name === 'adHocJobStdout'){ - SocketService.emit(`{"groups":{"jobs": ["status_changed"] , "ad_hoc_command_events": [${toParams.id}]}}`); - console.log(toState.name); - } - }); + // $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { + // SocketService.subscribe(toState, toParams); + // }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) { diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js index 719ce3bf15..65d86d6569 100644 --- a/awx/ui/client/src/inventories/manage/inventory-manage.route.js +++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js @@ -13,6 +13,11 @@ import GroupsListController from './groups/groups-list.controller'; export default { name: 'inventoryManage', url: '/inventories/:inventory_id/manage?{group:int}{failed}', + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, params:{ group:{ array: true diff --git a/awx/ui/client/src/job-detail/job-detail.route.js b/awx/ui/client/src/job-detail/job-detail.route.js index d08bcfecf8..274f8f46a4 100644 --- a/awx/ui/client/src/job-detail/job-detail.route.js +++ b/awx/ui/client/src/job-detail/job-detail.route.js @@ -13,22 +13,12 @@ export default { parent: 'jobs', label: "{{ job.id }} - {{ job.name }}" }, - // resolve: { - // jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) { - // if (!$rootScope.event_socket) { - // $rootScope.event_socket = Socket({ - // scope: $rootScope, - // endpoint: "job_events" - // }); - // $rootScope.event_socket.init(); - // // returns should really be providing $rootScope.event_socket - // // otherwise, we have to inject the entire $rootScope into the controller - // return true; - // } else { - // return true; - // } - // }] - // }, + socket: { + "groups":{ + "jobs": ["status_changed", "summary"] + // "job_events": `[${stateParams.id}]` + } + }, templateUrl: templateUrl('job-detail/job-detail'), controller: 'JobDetailController' }; diff --git a/awx/ui/client/src/shared/Socket.js b/awx/ui/client/src/shared/Socket.js deleted file mode 100644 index ddc0d74fdc..0000000000 --- a/awx/ui/client/src/shared/Socket.js +++ /dev/null @@ -1,207 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name shared.function:Socket - * @description - * Socket.js - * - * Wrapper for lib/socket.io-client/dist/socket.io.js. - */ - -/* global io */ - - -/** - * @ngdoc method - * @name shared.function:Socket#SocketIO - * @methodOf shared.function:Socket - * @description - */ -import ReconnectingWebSocket from 'reconnectingwebsocket' -export default -angular.module('SocketIO', ['Utilities']) - - .factory('Socket', ['$rootScope', '$location', '$log', 'Authorization', - '$state', - function ($rootScope, $location, $log, Authorization, $state) { - return function(params) { - var scope = params.scope, - host = window.location.host, - url; - - url = "ws://" + host + "/websocket/"; - $log.debug('opening socket connection to: ' + url); - - function getSocketTip(status) { - var result = ''; - switch(status) { - case 'error': - result = "Live events: error connecting to the Tower server."; - break; - case 'connecting': - result = "Live events: attempting to connect to the Tower server."; - break; - case "ok": - result = "Live events: connected. Pages containing job status information will automatically update in real-time."; - } - return result; - } - - return { - scope: scope, - url: url, - socket: null, - - init: function() { - var self = this, - token = Authorization.getToken(); - if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { - // We have a valid session token, so attempt socket connection - $log.debug('Socket connecting to: ' + url); - self.scope.socket_url = url; - self.socket = new ReconnectingWebSocket(url, null, { - debug: true, - timeoutInterval: 3000, - maxReconnectAttempts: 10 - }); - self.socket.onopen = function () { - console.log('websocket connected'); //log errors - }; - - self.socket.onerror = function (error) { - console.log('Error Logged: ' + error); //log errors - }; - self.socket.onmessage = function (e) { - console.log('Received From Server: ' + e.data); - var data = JSON.parse(e.data); - // {'groups': - // {'jobs': ['status_changed', 'summary'], - // 'schedules': ['changed'], - // 'ad_hoc_command_events': [ids,], - // 'job_events': [ids,], - // 'control': ['limit_reached'], - // } - // } - if(data.group_name==="jobs"){ - - if (!('status' in data)){ - // we know that this must have been a - // summary complete message - $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('JobSummaryComplete', data); - } - if ($state.is('jobs')) { - $rootScope.$emit('JobStatusChange-jobs', data); - } else if ($state.includes('jobDetail') || - $state.is('adHocJobStdout') || - $state.is('inventorySyncStdout') || - $state.is('managementJobStdout') || - $state.is('scmUpdateStdout')) { - - $log.debug("sending status to standard out"); - $rootScope.$emit('JobStatusChange-jobStdout', data); - } - if ($state.includes('jobDetail')) { - $rootScope.$emit('JobStatusChange-jobDetails', data); - } else if ($state.is('dashboard')) { - $rootScope.$emit('JobStatusChange-home', data); - } else if ($state.is('portalMode')) { - $rootScope.$emit('JobStatusChange-portal', data); - } else if ($state.is('projects')) { - $rootScope.$emit('JobStatusChange-projects', data); - } else if ($state.is('inventoryManage')) { - $rootScope.$emit('JobStatusChange-inventory', data); - } - } - if(data.group_name==="job_events"){ - $rootScope.$emit('job_events-'+data.job, data); - } - if(data.group_name==="schedules"){ - $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); - $rootScope.$emit('ScheduleStatusChange', data); - } - if(data.group_name==="ad_hoc_command_events"){ - - } - if(data.group_name==="control"){ - $log.debug(data.reason); - $rootScope.sessionTimer.expireSession('session_limit'); - $state.go('signOut'); - } - - - - }; - - } - else { - // encountered expired token, redirect to login page - $rootScope.sessionTimer.expireSession('idle'); - $location.url('/login'); - } - }, - checkStatus: function() { - // Check connection status - var self = this; - if(self){ - if(self.socket){ - if (self.socket.readyState === 0 ) { - self.scope.socketStatus = 'connecting'; - } - else if (self.socket.readyState === 1){ - self.scope.socketStatus = 'ok'; - } - else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ - self.scope.socketStatus = 'error'; - } - self.scope.socketTip = getSocketTip(self.scope.socketStatus); - return self.scope.socketStatus; - } - } - - }, - on: function (eventName, callback) { - var self = this; - if(self){ - if(self.socket){ - // self.socket.onmessage(function (e) { - // var args = arguments; - // self.scope.$apply(function () { - // callback.apply(self.socket, args); - // }); - // }); - } - } - - }, - emit: function (eventName, data, callback) { - var self = this; - // console.log(eventName) - self.socket.send(eventName, data, function () { - var args = arguments; - self.scope.$apply(function () { - if (callback) { - callback.apply(self.socket, args); - } - }); - }); - }, - getUrl: function() { - return url; - }, - removeAllListeners: function (eventName) { - var self = this; - if(self){ - if(self.socket){ - self.socket.removeEventListener(eventName); - } - } - }, - }; - }; - }]); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 54aa4662ff..7f26060f72 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -5,12 +5,12 @@ *************************************************/ import ReconnectingWebSocket from 'reconnectingwebsocket'; export default -['$rootScope', '$location', '$log', 'Authorization','$state', '$q', - function ($rootScope, $location, $log, Authorization, $state, $q) { +['$rootScope', '$location', '$log', 'Authorization','$state', + function ($rootScope, $location, $log, Authorization, $state) { return { init: function() { var self = this, - token = Authorization.getToken(), + // token = Authorization.getToken(), host = window.location.host, url = "ws://" + host + "/websocket/"; if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { @@ -103,6 +103,44 @@ export default $log.debug('socket status: ' + $rootScope.socketStatus); }, 2000); }, + subscribe2: function(state){ + console.log(state.name); + this.emit(JSON.stringify(state.socket)); + }, + // subscribe: function(toState, toParams){ + // if(toState.name === 'dashboard'){ + // this.emit('{"groups":{"jobs": ["status_changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'jobDetail'){ + // this.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); + // console.log(toState.name); + // } + // if(toState.name === 'jobStdout'){ + // this.emit('{"groups":{"jobs": ["status_changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'jobs'){ + // this.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'portalMode'){ + // this.emit('{"groups":{"jobs": ["status_changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'projects'){ + // this.emit('{"groups":{"jobs": ["status_changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'inventoryManage'){ + // this.emit('{"groups":{"jobs": ["status_changed"]}}'); + // console.log(toState.name); + // } + // if(toState.name === 'adHocJobStdout'){ + // this.emit(`{"groups":{"jobs": ["status_changed"] , "ad_hoc_command_events": [${toParams.id}]}}`); + // console.log(toState.name); + // } + // }, checkStatus: function() { function getSocketTip(status) { @@ -138,23 +176,22 @@ export default } }, - on: function (eventName, callback) { - var self = this; - if(self){ - if(self.socket){ - // self.socket.onmessage(function (e) { - // var args = arguments; - // self.scope.$apply(function () { - // callback.apply(self.socket, args); - // }); - // }); - } - } - - }, + // on: function (eventName, callback) { + // var self = this; + // if(self){ + // if(self.socket){ + // // self.socket.onmessage(function (e) { + // // var args = arguments; + // // self.scope.$apply(function () { + // // callback.apply(self.socket, args); + // // }); + // // }); + // } + // } + // + // }, emit: function (eventName, data, callback) { var self = this; - // console.log(eventName) $rootScope.socketPromise.promise.then(function(){ self.socket.send(eventName, data, function () { var args = arguments; @@ -166,9 +203,6 @@ export default }); }); }, - getUrl: function() { - return url; - }, removeAllListeners: function (eventName) { var self = this; if(self){ diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index 89f6ff97ba..e38a8f7d3f 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -1,10 +1,28 @@ export default function($stateProvider) { this.$get = function() { return { + addSocket: function(state){ + // var resolve = state.resolve || {}; + if(!state.resolve){ + state.resolve = {}; + } + state.resolve.socket = ['SocketService', '$rootScope', + function(SocketService, $rootScope) { + // var self = this; + $rootScope.socketPromise.promise.then(function(){ + SocketService.subscribe2(state); + return true; + }); + }] + }, addState: function(state) { var route = state.route || state.url; + if(state.socket){ + this.addSocket(state); + } + $stateProvider.state(state.name, { url: route, controller: state.controller, From 0fb4a1a62abef14f955ce0da8ce629b4406db35a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 7 Sep 2016 18:25:31 -0700 Subject: [PATCH 23/48] Adding socket object to route definitions for stdout routes, as well as removing some old code --- .../client/src/job-detail/job-detail.route.js | 4 +- .../src/portal-mode/portal-mode.route.js | 5 ++ .../src/shared/socket/socket.service.js | 76 +++---------------- .../src/shared/stateExtender.provider.js | 13 +++- .../adhoc/standard-out-adhoc.route.js | 6 ++ .../standard-out-inventory-sync.route.js | 11 ++- .../standard-out-management-jobs.route.js | 11 ++- .../standard-out-scm-update.route.js | 11 ++- 8 files changed, 49 insertions(+), 88 deletions(-) diff --git a/awx/ui/client/src/job-detail/job-detail.route.js b/awx/ui/client/src/job-detail/job-detail.route.js index 274f8f46a4..c989d06ba4 100644 --- a/awx/ui/client/src/job-detail/job-detail.route.js +++ b/awx/ui/client/src/job-detail/job-detail.route.js @@ -15,8 +15,8 @@ export default { }, socket: { "groups":{ - "jobs": ["status_changed", "summary"] - // "job_events": `[${stateParams.id}]` + "jobs": ["status_changed", "summary"], + "job_events": [] } }, templateUrl: templateUrl('job-detail/job-detail'), diff --git a/awx/ui/client/src/portal-mode/portal-mode.route.js b/awx/ui/client/src/portal-mode/portal-mode.route.js index c0f7e5f3fb..788148dd52 100644 --- a/awx/ui/client/src/portal-mode/portal-mode.route.js +++ b/awx/ui/client/src/portal-mode/portal-mode.route.js @@ -9,6 +9,11 @@ export default { url: '/portal', ncyBreadcrumb: { label: "MY VIEW" + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } }, views: { // the empty parent ui-view diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 7f26060f72..efa4d0bbee 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -10,36 +10,31 @@ export default return { init: function() { var self = this, - // token = Authorization.getToken(), host = window.location.host, url = "ws://" + host + "/websocket/"; if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { // We have a valid session token, so attempt socket connection $log.debug('Socket connecting to: ' + url); + self.socket = new ReconnectingWebSocket(url, null, { - debug: true, + // debug: true, timeoutInterval: 3000, maxReconnectAttempts: 10 }); + self.socket.onopen = function () { - console.log('websocket connected'); //log errors + $log.debug("Websocket connection opened"); $rootScope.socketPromise.resolve(); }; - // - // self.socket.onerror = function (error) { - // console.log('Error Logged: ' + error); //log errors - // }; + + self.socket.onerror = function (error) { + $log.debug('Error Logged: ' + error); //log errors + }; + self.socket.onmessage = function (e) { - console.log('Received From Server: ' + e.data); + $log.debug('Received From Server: ' + e.data); var data = JSON.parse(e.data); - // {'groups': - // {'jobs': ['status_changed', 'summary'], - // 'schedules': ['changed'], - // 'ad_hoc_command_events': [ids,], - // 'job_events': [ids,], - // 'control': ['limit_reached'], - // } - // } + if(data.group_name==="jobs"){ if (!('status' in data)){ @@ -107,40 +102,6 @@ export default console.log(state.name); this.emit(JSON.stringify(state.socket)); }, - // subscribe: function(toState, toParams){ - // if(toState.name === 'dashboard'){ - // this.emit('{"groups":{"jobs": ["status_changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'jobDetail'){ - // this.emit(`{"groups":{"jobs": ["status_changed", "summary"] , "job_events":[${toParams.id}]}}`); - // console.log(toState.name); - // } - // if(toState.name === 'jobStdout'){ - // this.emit('{"groups":{"jobs": ["status_changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'jobs'){ - // this.emit('{"groups":{"jobs": ["status_changed"] , "schedules": ["changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'portalMode'){ - // this.emit('{"groups":{"jobs": ["status_changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'projects'){ - // this.emit('{"groups":{"jobs": ["status_changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'inventoryManage'){ - // this.emit('{"groups":{"jobs": ["status_changed"]}}'); - // console.log(toState.name); - // } - // if(toState.name === 'adHocJobStdout'){ - // this.emit(`{"groups":{"jobs": ["status_changed"] , "ad_hoc_command_events": [${toParams.id}]}}`); - // console.log(toState.name); - // } - // }, checkStatus: function() { function getSocketTip(status) { @@ -176,22 +137,9 @@ export default } }, - // on: function (eventName, callback) { - // var self = this; - // if(self){ - // if(self.socket){ - // // self.socket.onmessage(function (e) { - // // var args = arguments; - // // self.scope.$apply(function () { - // // callback.apply(self.socket, args); - // // }); - // // }); - // } - // } - // - // }, emit: function (eventName, data, callback) { var self = this; + $log.debug('Sent to Server: ' + data); $rootScope.socketPromise.promise.then(function(){ self.socket.send(eventName, data, function () { var args = arguments; diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index e38a8f7d3f..320853876c 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -6,14 +6,19 @@ export default function($stateProvider) { if(!state.resolve){ state.resolve = {}; } - state.resolve.socket = ['SocketService', '$rootScope', - function(SocketService, $rootScope) { - // var self = this; + state.resolve.socket = ['SocketService', '$rootScope', '$stateParams', + function(SocketService, $rootScope, $stateParams) { $rootScope.socketPromise.promise.then(function(){ + if(state.socket.groups.hasOwnProperty( "job_events")){ + state.socket.groups.job_events = [$stateParams.id]; + } + if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ + state.socket.groups.job_events = [$stateParams.id]; + } SocketService.subscribe2(state); return true; }); - }] + }]; }, addState: function(state) { diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js index 0df5cd54af..1b7d546231 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js @@ -11,6 +11,12 @@ export default { route: '/ad_hoc_commands/:id', templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'), controller: 'JobStdoutController', + socket: { + "groups":{ + "jobs": ["status_changed"], + "ad_hoc_command_events": [] + } + }, ncyBreadcrumb: { parent: "jobs", label: "{{ job.module_name }}" diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js index e9bfe68f63..3cff062144 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js @@ -13,17 +13,16 @@ export default { route: '/inventory_sync/:id', templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'), controller: 'JobStdoutController', + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, ncyBreadcrumb: { parent: "jobs", label: "{{ inventory_source_name }}" }, data: { jobType: 'inventory_updates' - }, - resolve: { - inventorySyncSocket: [function() { - // TODO: determine whether or not we have socket support for inventory sync standard out - return true; - }] } }; diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js index d98a2bd1d4..c93cbd7a92 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js @@ -11,17 +11,16 @@ export default { route: '/management_jobs/:id', templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'), controller: 'JobStdoutController', + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, ncyBreadcrumb: { parent: "jobs", label: "{{ job.name }}" }, data: { jobType: 'system_jobs' - }, - resolve: { - managementJobSocket: [function() { - // TODO: determine whether or not we have socket support for management job standard out - return true; - }] } }; diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js index 9d399f05a9..d027050444 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js @@ -17,13 +17,12 @@ export default { parent: "jobs", label: "{{ project_name }}" }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, data: { jobType: 'project_updates' - }, - resolve: { - scmUpdateSocket: [function() { - // TODO: determine whether or not we have socket support for scm update standard out - return true; - }] } }; From 947571fe263d90ccfb938d41038d257964e1313d Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 12 Sep 2016 16:00:19 -0700 Subject: [PATCH 24/48] Fixing live events for inventory-manage page --- .../manage/groups/groups-list.controller.js | 48 +++++++++++-------- .../login/loginModal/loginModal.controller.js | 1 - .../src/shared/socket/socket.service.js | 11 +++-- .../src/shared/stateExtender.provider.js | 2 +- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index a2f843793b..cf81927116 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -5,12 +5,14 @@ *************************************************/ export default ['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroups', 'generateList', 'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', - 'InventoryManageService', 'groupsUrl', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Rest', 'GetBasePath', 'rbacUiControlService', + 'InventoryManageService', 'groupsUrl', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Find', 'Rest', 'GetBasePath', 'rbacUiControlService', function($scope, $rootScope, $state, $stateParams, InventoryGroups, generateList, InventoryUpdate, GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, - InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, Rest, GetBasePath, rbacUiControlService){ + InventoryManageService, groupsUrl, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, Find, Rest, GetBasePath, rbacUiControlService){ var list = InventoryGroups, view = generateList, pageSize = 20; + + $scope.inventory_id = $stateParams.inventory_id; $scope.canAdd = false; @@ -97,26 +99,30 @@ group_name: group.name, group_source: res.data.results[0].source })); - $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates - $rootScope.$on('JobStatusChange-inventory', (event, data) => { - switch(data.status){ - case 'failed' || 'successful': - $state.reload(); - break; - default: - var status = GetSyncStatusMsg({ - status: data.status, - has_inventory_sources: group.has_inventory_sources, - source: group.source - }); - group.status = data.status; - group.status_class = status.class; - group.status_tooltip = status.tooltip; - group.launch_tooltip = status.launch_tip; - group.launch_class = status.launch_class; - } - }); }; + + if ($rootScope.inventoryManageStatus) { + $rootScope.inventoryManageStatus(); + } + $rootScope.inventoryManageStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data){ + var group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); + if(data.status === 'failed' || data.status === 'successful'){ + $state.reload(); + } + else{ + var status = GetSyncStatusMsg({ + status: data.status, + has_inventory_sources: group.has_inventory_sources, + source: group.source + }); + group.status = data.status; + group.status_class = status.class; + group.status_tooltip = status.tooltip; + group.launch_tooltip = status.launch_tip; + group.launch_class = status.launch_class; + } + }); + $scope.cancelUpdate = function (id) { GroupsCancelUpdate({ scope: $scope, id: id }); }; diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index 020c64b1ca..69d509925f 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -136,7 +136,6 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', Authorization.setUserInfo(data); Timer.init().then(function(timer){ $rootScope.sessionTimer = timer; - // $rootScope.$emit('OpenSocket'); SocketService.init(); $rootScope.user_is_superuser = data.results[0].is_superuser; $rootScope.user_is_system_auditor = data.results[0].is_system_auditor; diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index efa4d0bbee..75b3281c2e 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -62,8 +62,9 @@ export default $rootScope.$emit('JobStatusChange-portal', data); } else if ($state.is('projects')) { $rootScope.$emit('JobStatusChange-projects', data); - } else if ($state.is('inventoryManage')) { - $rootScope.$emit('JobStatusChange-inventory', data); + } else if ($state.is('inventoryManage') || + $state.includes('inventoryManage')) { + $rootScope.$emit('JobStatusChange-inventory', data); } } if(data.group_name==="job_events"){ @@ -98,7 +99,7 @@ export default $log.debug('socket status: ' + $rootScope.socketStatus); }, 2000); }, - subscribe2: function(state){ + subscribe: function(state){ console.log(state.name); this.emit(JSON.stringify(state.socket)); }, @@ -137,11 +138,11 @@ export default } }, - emit: function (eventName, data, callback) { + emit: function(data, callback) { var self = this; $log.debug('Sent to Server: ' + data); $rootScope.socketPromise.promise.then(function(){ - self.socket.send(eventName, data, function () { + self.socket.send(data, function () { var args = arguments; self.scope.$apply(function () { if (callback) { diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index 320853876c..d58c530a4c 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -15,7 +15,7 @@ export default function($stateProvider) { if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ state.socket.groups.job_events = [$stateParams.id]; } - SocketService.subscribe2(state); + SocketService.subscribe(state); return true; }); }]; From 1510d826a667fb16a14c3edbb2a9518978cedbf3 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 13 Sep 2016 10:07:06 -0700 Subject: [PATCH 25/48] Sending blank object to socket server on routes that are not websocket enabled. --- .../src/shared/stateExtender.provider.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index d58c530a4c..54f292a659 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -9,12 +9,18 @@ export default function($stateProvider) { state.resolve.socket = ['SocketService', '$rootScope', '$stateParams', function(SocketService, $rootScope, $stateParams) { $rootScope.socketPromise.promise.then(function(){ - if(state.socket.groups.hasOwnProperty( "job_events")){ - state.socket.groups.job_events = [$stateParams.id]; + if(!state.socket){ + state.socket = {groups: {}}; } - if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ - state.socket.groups.job_events = [$stateParams.id]; + else{ + if(state.socket.groups.hasOwnProperty( "job_events")){ + state.socket.groups.job_events = [$stateParams.id]; + } + if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ + state.socket.groups.job_events = [$stateParams.id]; + } } + SocketService.subscribe(state); return true; }); @@ -24,9 +30,7 @@ export default function($stateProvider) { addState: function(state) { var route = state.route || state.url; - if(state.socket){ - this.addSocket(state); - } + this.addSocket(state); $stateProvider.state(state.name, { url: route, From f875d65ef74a197eedd7e23626538268c7e73961 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 13 Sep 2016 12:43:41 -0700 Subject: [PATCH 26/48] Adding unsubscribe method for non-socket-enabled routes with the idea that we only want to unsubscribe directly following a subscribe (no need to unsubscribe if we're already unsubscribed). --- .../src/shared/socket/socket.service.js | 30 +++++++++++++++++-- .../src/shared/stateExtender.provider.js | 4 +-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 75b3281c2e..738f615e3a 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -100,8 +100,34 @@ export default }, 2000); }, subscribe: function(state){ - console.log(state.name); + console.log("Next state: " + state.name); this.emit(JSON.stringify(state.socket)); + this.setLast(state); + }, + unsubscribe: function(state){ + if(this.requiresNewSubscribe(state)){ + this.emit(JSON.stringify(state.socket)); + } + this.setLast(state); + }, + setLast: function(state){ + this.last = state; + }, + getLast: function(){ + return this.last; + }, + requiresNewSubscribe(state){ + if (this.getLast() !== undefined){ + if( _.isEmpty(state.socket.groups) && _.isEmpty(this.getLast().socket.groups)){ + return false; + } + else { + return true; + } + } + else { + return true; + } }, checkStatus: function() { @@ -140,7 +166,7 @@ export default }, emit: function(data, callback) { var self = this; - $log.debug('Sent to Server: ' + data); + $log.debug('Sent to Websocket Server: ' + data); $rootScope.socketPromise.promise.then(function(){ self.socket.send(data, function () { var args = arguments; diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index 54f292a659..a04d5c214b 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -11,6 +11,7 @@ export default function($stateProvider) { $rootScope.socketPromise.promise.then(function(){ if(!state.socket){ state.socket = {groups: {}}; + SocketService.unsubscribe(state); } else{ if(state.socket.groups.hasOwnProperty( "job_events")){ @@ -19,9 +20,8 @@ export default function($stateProvider) { if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ state.socket.groups.job_events = [$stateParams.id]; } + SocketService.subscribe(state); } - - SocketService.subscribe(state); return true; }); }]; From 0f32faec60fd1f2af5d2f29e68e1325f00741ef8 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 13 Sep 2016 18:21:56 -0700 Subject: [PATCH 27/48] Changing naming convention for socket-message event listeners to use - --- awx/ui/client/src/controllers/Home.js | 2 +- awx/ui/client/src/controllers/Jobs.js | 4 +- awx/ui/client/src/controllers/Projects.js | 2 +- .../job-status/job-status-graph.service.js | 2 +- .../manage/groups/groups-list.controller.js | 2 +- .../host-summary/host-summary.controller.js | 3 +- .../host-summary/host-summary.route.js | 5 + .../src/job-detail/job-detail.controller.js | 4 +- .../organizations-projects.controller.js | 2 +- .../portal-mode-jobs.controller.js | 2 +- .../src/shared/socket/socket.service.js | 110 +++++++++++------- .../log/standard-out-log.controller.js | 6 +- .../standard-out/standard-out.controller.js | 2 +- 13 files changed, 88 insertions(+), 58 deletions(-) diff --git a/awx/ui/client/src/controllers/Home.js b/awx/ui/client/src/controllers/Home.js index bbaa7d02ad..eb5938aad2 100644 --- a/awx/ui/client/src/controllers/Home.js +++ b/awx/ui/client/src/controllers/Home.js @@ -28,7 +28,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log var dataCount = 0; - $rootScope.$on('JobStatusChange-home', function () { + $rootScope.$on('dashboard-jobs', function () { Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index 0e2f46f736..d0f4c9c640 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -113,14 +113,14 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobs', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('jobs-jobs', function() { $scope.refreshJobs(); }); if ($rootScope.removeScheduleStatusChange) { $rootScope.removeScheduleStatusChange(); } - $rootScope.removeScheduleStatusChange = $rootScope.$on('ScheduleStatusChange', function() { + $rootScope.removeScheduleStatusChange = $rootScope.$on('jobs-schedules', function() { if (api_complete) { scheduled_scope.search('schedule'); } diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index f5db4a2b8c..2f6022fd70 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -90,7 +90,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-projects', function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js index 50a5a2968b..06c2bbf343 100644 --- a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js +++ b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js @@ -58,7 +58,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { destroyWatcher: angular.noop, setupWatcher: function(period, jobType) { this.destroyWatcher = - $rootScope.$on('JobStatusChange-home', function() { + $rootScope.$on('dashboard-jobs', function() { getData(period, jobType).then(function(result) { $rootScope. $broadcast('DataReceived:JobStatusGraph', diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index cf81927116..9e13b74867 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -104,7 +104,7 @@ if ($rootScope.inventoryManageStatus) { $rootScope.inventoryManageStatus(); } - $rootScope.inventoryManageStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data){ + $rootScope.inventoryManageStatus = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data){ var group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); if(data.status === 'failed' || data.status === 'successful'){ $state.reload(); diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js index d5c826ac35..cf648b8521 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js @@ -60,11 +60,10 @@ } }); - // UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py if ($rootScope.removeJobSummaryComplete) { $rootScope.removeJobSummaryComplete(); } - $rootScope.removeJobSummaryComplete = $rootScope.$on('JobStatusChange-jobDetails', function(e, data) { + $rootScope.removeJobSummaryComplete = $rootScope.$on('jobDetail-jobs', function(e, data) { if (parseInt($stateParams.id) === data.unified_job_id){ $scope.status = data.status; } diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js index a2de70e5d4..5d17fb172b 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js @@ -9,6 +9,11 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'jobDetail.host-summary', url: '/event-summary', + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views:{ 'host-summary': { controller: 'HostSummaryController', 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 71940a22f9..9dfef14c84 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -201,7 +201,7 @@ export default if ($rootScope.removeJobEventChange) { $rootScope.removeJobEventChange(); } - $rootScope.removeJobEventChange = $rootScope.$on("job_events-" + job_id, function(e, data) { + $rootScope.removeJobEventChange = $rootScope.$on(`${$state.current.name}-job_events-${job_id}`, function(e, data) { // update elapsed time on each event received scope.job_status.elapsed = GetElapsed({ start: scope.job.created, @@ -225,7 +225,7 @@ export default if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobDetails', function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { // if we receive a status change event for the current job indicating the job // is finished, stop event queue processing and reload if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index a718b423c2..0de1b568ed 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -90,7 +90,7 @@ export default ['$scope', '$rootScope', '$location', '$log', if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-projects', function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js index f6bd8ff9df..278bc2f4fc 100644 --- a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js +++ b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js @@ -16,7 +16,7 @@ export function PortalModeJobsController($scope, $rootScope, GetBasePath, Genera if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-portal', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('portalMode-jobs', function() { $scope.search('job'); }); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 738f615e3a..981c6ee12e 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -33,55 +33,81 @@ export default self.socket.onmessage = function (e) { $log.debug('Received From Server: ' + e.data); - var data = JSON.parse(e.data); - - if(data.group_name==="jobs"){ - - if (!('status' in data)){ - // we know that this must have been a - // summary complete message - $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('JobSummaryComplete', data); - } - if ($state.is('jobs')) { - $rootScope.$emit('JobStatusChange-jobs', data); - } else if ($state.includes('jobDetail') || - $state.is('adHocJobStdout') || - $state.is('inventorySyncStdout') || - $state.is('managementJobStdout') || - $state.is('scmUpdateStdout')) { - - $log.debug("sending status to standard out"); - $rootScope.$emit('JobStatusChange-jobStdout', data); - } - if ($state.includes('jobDetail')) { - $rootScope.$emit('JobStatusChange-jobDetails', data); - } else if ($state.is('dashboard')) { - $rootScope.$emit('JobStatusChange-home', data); - } else if ($state.is('portalMode')) { - $rootScope.$emit('JobStatusChange-portal', data); - } else if ($state.is('projects')) { - $rootScope.$emit('JobStatusChange-projects', data); - } else if ($state.is('inventoryManage') || - $state.includes('inventoryManage')) { - $rootScope.$emit('JobStatusChange-inventory', data); - } + var data = JSON.parse(e.data), str = ""; + if(data.group_name==="jobs" && !('status' in data)){ + // we know that this must have been a + // summary complete message + $log.debug('Job summary_complete ' + data.unified_job_id); + $rootScope.$emit('JobSummaryComplete', data); } - if(data.group_name==="job_events"){ - $rootScope.$emit('job_events-'+data.job, data); + else if(data.group_name==="job_events"){ + str = `${$state.current.name}-${data.group_name}-${data.job}`; } - if(data.group_name==="schedules"){ - $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); - $rootScope.$emit('ScheduleStatusChange', data); + else if(data.group_name==="ad_hoc_command_events"){ + str = `${$state.current.name}-${data.group_name}-${data.ad_hoc_command}`; } - if(data.group_name==="ad_hoc_command_events"){ - $rootScope.$emit('ad_hoc_command_events-'+data.ad_hoc_command, data); - } - if(data.group_name==="control"){ + else if(data.group_name==="control"){ $log.debug(data.reason); $rootScope.sessionTimer.expireSession('session_limit'); $state.go('signOut'); } + else { + // The naming scheme for emitting socket messages to the + // correct route is the route name followed by a + // dash (-) and the group_name. + // ex: 'jobDetail-job_events' + str = `${$state.current.name}-${data.group_name}`; + } + $rootScope.$emit(str, data); + // if(data.group_name==="jobs"){ + // + // if (!('status' in data)){ + // // we know that this must have been a + // // summary complete message + // $log.debug('Job summary_complete ' + data.unified_job_id); + // $rootScope.$emit('JobSummaryComplete', data); + // } + // + // if ($state.is('jobs')) { + // $rootScope.$emit('JobStatusChange-jobs', data); + // } + // else if ($state.includes('jobDetail') || + // $state.is('adHocJobStdout') || + // $state.is('inventorySyncStdout') || + // $state.is('managementJobStdout') || + // $state.is('scmUpdateStdout')) { + // + // $log.debug("sending status to standard out"); + // $rootScope.$emit('JobStatusChange-jobStdout', data); + // } + // if ($state.includes('jobDetail')) { + // $rootScope.$emit('JobStatusChange-jobDetails', data); + // } else if ($state.is('dashboard')) { + // $rootScope.$emit('JobStatusChange-home', data); + // } else if ($state.is('portalMode')) { + // $rootScope.$emit('JobStatusChange-portal', data); + // } else if ($state.is('projects')) { + // $rootScope.$emit('JobStatusChange-projects', data); + // } else if ($state.is('inventoryManage') || + // $state.includes('inventoryManage')) { + // $rootScope.$emit('JobStatusChange-inventory', data); + // } + // } + // if(data.group_name==="job_events"){ + // $rootScope.$emit('job_events-'+data.job, data); + // } + // if(data.group_name==="schedules"){ + // $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); + // $rootScope.$emit('ScheduleStatusChange', data); + // } + // if(data.group_name==="ad_hoc_command_events"){ + // $rootScope.$emit('ad_hoc_command_events-'+data.ad_hoc_command, data); + // } + // if(data.group_name==="control"){ + // $log.debug(data.reason); + // $rootScope.sessionTimer.expireSession('session_limit'); + // $state.go('signOut'); + // } return self.socket; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 74dfede3a0..2c46d218ce 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.$on("job_events-" + job_id, function() { + $rootScope.$on(`${$state.current.name}-job_events-${job_id}`, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; @@ -36,7 +36,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $rootScope.$on("ad_hoc_command_events-" + job_id, function() { + $rootScope.$on(`${$state.current.name}-ad_hoc_command_events-${job_id}`, function() { $log.debug("socket fired on ad_hoc_command_events-" + job_id); if (api_complete) { event_queue++; @@ -192,7 +192,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobStdout', function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index acdd5815a2..242d339ba9 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -28,7 +28,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobStdout', function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { $scope.job.status = data.status; } From 82d33d86867c047e443dba1290b2646d81b375f2 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 08:16:23 -0700 Subject: [PATCH 28/48] fixing issue with adhoc stdout the adhoc stdout was subscribing to job_events instead of ad_hoc_command_events --- awx/ui/client/src/shared/stateExtender.provider.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index a04d5c214b..a80c285245 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -2,7 +2,6 @@ export default function($stateProvider) { this.$get = function() { return { addSocket: function(state){ - // var resolve = state.resolve || {}; if(!state.resolve){ state.resolve = {}; } @@ -18,7 +17,7 @@ export default function($stateProvider) { state.socket.groups.job_events = [$stateParams.id]; } if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ - state.socket.groups.job_events = [$stateParams.id]; + state.socket.groups.ad_hoc_command_events = [$stateParams.id]; } SocketService.subscribe(state); } From 4e533cc63bdfb783be828cbce0e11cb63c9c665c Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 11:39:27 -0700 Subject: [PATCH 29/48] adding disconnect function for logging out and adding ability to specify `socket: null` for a route definition, in order to specify a state that should not subscribe/unsubscribe at all (for login state). --- .../authentication.service.js | 2 + awx/ui/client/src/login/login.route.js | 1 + .../src/shared/socket/socket.service.js | 59 +++---------------- .../src/shared/stateExtender.provider.js | 46 +++++++-------- 4 files changed, 33 insertions(+), 75 deletions(-) diff --git a/awx/ui/client/src/login/authenticationServices/authentication.service.js b/awx/ui/client/src/login/authenticationServices/authentication.service.js index c65ca0061d..73d42a3e50 100644 --- a/awx/ui/client/src/login/authenticationServices/authentication.service.js +++ b/awx/ui/client/src/login/authenticationServices/authentication.service.js @@ -65,6 +65,7 @@ export default var x, ConfigService = $injector.get('ConfigService'), + SocketService = $injector.get('SocketService'), scope = angular.element(document.getElementById('main-view')).scope(); if(scope){ @@ -94,6 +95,7 @@ export default $rootScope.lastUser = $cookieStore.get('current_user').id; } ConfigService.delete(); + SocketService.disconnect(); $cookieStore.remove('token_expires'); $cookieStore.remove('current_user'); $cookieStore.remove('token'); diff --git a/awx/ui/client/src/login/login.route.js b/awx/ui/client/src/login/login.route.js index 83b403ce6c..561cc3958a 100644 --- a/awx/ui/client/src/login/login.route.js +++ b/awx/ui/client/src/login/login.route.js @@ -9,6 +9,7 @@ import {templateUrl} from '../shared/template-url/template-url.factory'; export default { name: 'signIn', route: '/login', + socket: null, templateUrl: templateUrl('login/loginBackDrop'), resolve: { obj: ['$rootScope', 'Authorization', diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 981c6ee12e..1d14d72b69 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -17,7 +17,6 @@ export default $log.debug('Socket connecting to: ' + url); self.socket = new ReconnectingWebSocket(url, null, { - // debug: true, timeoutInterval: 3000, maxReconnectAttempts: 10 }); @@ -31,6 +30,10 @@ export default $log.debug('Error Logged: ' + error); //log errors }; + self.socket.onclose = function (error) { + $log.debug('Websocket Disconnected.'); + }; + self.socket.onmessage = function (e) { $log.debug('Received From Server: ' + e.data); var data = JSON.parse(e.data), str = ""; @@ -59,56 +62,6 @@ export default str = `${$state.current.name}-${data.group_name}`; } $rootScope.$emit(str, data); - // if(data.group_name==="jobs"){ - // - // if (!('status' in data)){ - // // we know that this must have been a - // // summary complete message - // $log.debug('Job summary_complete ' + data.unified_job_id); - // $rootScope.$emit('JobSummaryComplete', data); - // } - // - // if ($state.is('jobs')) { - // $rootScope.$emit('JobStatusChange-jobs', data); - // } - // else if ($state.includes('jobDetail') || - // $state.is('adHocJobStdout') || - // $state.is('inventorySyncStdout') || - // $state.is('managementJobStdout') || - // $state.is('scmUpdateStdout')) { - // - // $log.debug("sending status to standard out"); - // $rootScope.$emit('JobStatusChange-jobStdout', data); - // } - // if ($state.includes('jobDetail')) { - // $rootScope.$emit('JobStatusChange-jobDetails', data); - // } else if ($state.is('dashboard')) { - // $rootScope.$emit('JobStatusChange-home', data); - // } else if ($state.is('portalMode')) { - // $rootScope.$emit('JobStatusChange-portal', data); - // } else if ($state.is('projects')) { - // $rootScope.$emit('JobStatusChange-projects', data); - // } else if ($state.is('inventoryManage') || - // $state.includes('inventoryManage')) { - // $rootScope.$emit('JobStatusChange-inventory', data); - // } - // } - // if(data.group_name==="job_events"){ - // $rootScope.$emit('job_events-'+data.job, data); - // } - // if(data.group_name==="schedules"){ - // $log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status); - // $rootScope.$emit('ScheduleStatusChange', data); - // } - // if(data.group_name==="ad_hoc_command_events"){ - // $rootScope.$emit('ad_hoc_command_events-'+data.ad_hoc_command, data); - // } - // if(data.group_name==="control"){ - // $log.debug(data.reason); - // $rootScope.sessionTimer.expireSession('session_limit'); - // $state.go('signOut'); - // } - return self.socket; }; @@ -125,6 +78,9 @@ export default $log.debug('socket status: ' + $rootScope.socketStatus); }, 2000); }, + disconnect: function(){ + this.socket.close(); + }, subscribe: function(state){ console.log("Next state: " + state.name); this.emit(JSON.stringify(state.socket)); @@ -156,7 +112,6 @@ export default } }, checkStatus: function() { - function getSocketTip(status) { var result = ''; switch(status) { diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index a80c285245..66f01e0051 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -2,35 +2,35 @@ export default function($stateProvider) { this.$get = function() { return { addSocket: function(state){ - if(!state.resolve){ - state.resolve = {}; + if(state.socket!==null){ + if(!state.resolve){ + state.resolve = {}; + } + state.resolve.socket = ['SocketService', '$rootScope', '$stateParams', + function(SocketService, $rootScope, $stateParams) { + $rootScope.socketPromise.promise.then(function(){ + if(!state.socket){ + state.socket = {groups: {}}; + SocketService.unsubscribe(state); + } + else{ + if(state.socket.groups.hasOwnProperty( "job_events")){ + state.socket.groups.job_events = [$stateParams.id]; + } + if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ + state.socket.groups.ad_hoc_command_events = [$stateParams.id]; + } + SocketService.subscribe(state); + } + return true; + }); + }]; } - state.resolve.socket = ['SocketService', '$rootScope', '$stateParams', - function(SocketService, $rootScope, $stateParams) { - $rootScope.socketPromise.promise.then(function(){ - if(!state.socket){ - state.socket = {groups: {}}; - SocketService.unsubscribe(state); - } - else{ - if(state.socket.groups.hasOwnProperty( "job_events")){ - state.socket.groups.job_events = [$stateParams.id]; - } - if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ - state.socket.groups.ad_hoc_command_events = [$stateParams.id]; - } - SocketService.subscribe(state); - } - return true; - }); - }]; }, addState: function(state) { var route = state.route || state.url; - this.addSocket(state); - $stateProvider.state(state.name, { url: route, controller: state.controller, From 37b2b01e02c987f8c839d04eda7038d4018af68c Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 12:15:20 -0700 Subject: [PATCH 30/48] small fix to disconnect function--null pointer exception --- awx/ui/client/src/shared/socket/socket.service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 1d14d72b69..da53e2998a 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -79,7 +79,9 @@ export default }, 2000); }, disconnect: function(){ - this.socket.close(); + if(this.socket){ + this.socket.close(); + } }, subscribe: function(state){ console.log("Next state: " + state.name); From e0a566cdea9018fc80d791491d66b5993f6663b6 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 14:49:18 -0700 Subject: [PATCH 31/48] adding socket coverage to child states so that child states won't unsubscribe the parent from any groups --- awx/ui/client/src/app.js | 11 ++++++ awx/ui/client/src/controllers/Home.js | 2 +- awx/ui/client/src/controllers/Jobs.js | 4 +- awx/ui/client/src/controllers/Projects.js | 2 +- .../job-status/job-status-graph.service.js | 2 +- .../inventories/manage/adhoc/adhoc.route.js | 5 +++ .../manage/copy-move/copy-move.route.js | 10 +++++ .../manage/groups/groups-list.controller.js | 2 +- .../inventories/manage/groups/groups.route.js | 10 +++++ .../inventories/manage/hosts/hosts.route.js | 10 +++++ .../job-detail/host-event/host-event.route.js | 38 +++++++++++++++++-- .../host-events/host-events.route.js | 6 +++ .../host-summary/host-summary.controller.js | 4 +- .../host-summary/host-summary.route.js | 6 +++ .../src/job-detail/job-detail.controller.js | 6 +-- .../add/job-templates-add.route.js | 5 +++ .../edit/job-templates-edit.route.js | 5 +++ .../list/job-templates-list.controller.js | 8 ++++ .../list/job-templates-list.route.js | 5 +++ .../organizations-job-templates.controller.js | 7 ++++ .../organizations-projects.controller.js | 2 +- .../linkout/organizations-linkout.route.js | 10 +++++ .../portal-mode-jobs.controller.js | 2 +- .../src/shared/socket/socket.service.js | 8 ++-- .../log/standard-out-log.controller.js | 6 +-- .../standard-out/standard-out.controller.js | 2 +- 26 files changed, 153 insertions(+), 25 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index db43657283..cff60eb1ec 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -624,6 +624,11 @@ var tower = angular.module('Tower', [ ncyBreadcrumb: { parent: "projects", label: "CREATE PROJECT" + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } } }); @@ -638,6 +643,11 @@ var tower = angular.module('Tower', [ ncyBreadcrumb: { parent: 'projects', label: '{{name}}' + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } } }); @@ -654,6 +664,7 @@ var tower = angular.module('Tower', [ templateUrl: urlPrefix + 'partials/projects.html', controller: OrganizationsAdd }); + $rootScope.addPermission = function(scope) { $compile("")(scope); }; diff --git a/awx/ui/client/src/controllers/Home.js b/awx/ui/client/src/controllers/Home.js index eb5938aad2..281a5cee84 100644 --- a/awx/ui/client/src/controllers/Home.js +++ b/awx/ui/client/src/controllers/Home.js @@ -28,7 +28,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log var dataCount = 0; - $rootScope.$on('dashboard-jobs', function () { + $rootScope.$on('ws-dashboard-jobs', function () { Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index d0f4c9c640..e3cfd2ab99 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -113,14 +113,14 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('jobs-jobs', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs-jobs', function() { $scope.refreshJobs(); }); if ($rootScope.removeScheduleStatusChange) { $rootScope.removeScheduleStatusChange(); } - $rootScope.removeScheduleStatusChange = $rootScope.$on('jobs-schedules', function() { + $rootScope.removeScheduleStatusChange = $rootScope.$on('ws-jobs-schedules', function() { if (api_complete) { scheduled_scope.search('schedule'); } diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 2f6022fd70..56aca39209 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -90,7 +90,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js index 06c2bbf343..cf68287c44 100644 --- a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js +++ b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js @@ -58,7 +58,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { destroyWatcher: angular.noop, setupWatcher: function(period, jobType) { this.destroyWatcher = - $rootScope.$on('dashboard-jobs', function() { + $rootScope.$on('ws-dashboard-jobs', function() { getData(period, jobType).then(function(result) { $rootScope. $broadcast('DataReceived:JobStatusGraph', diff --git a/awx/ui/client/src/inventories/manage/adhoc/adhoc.route.js b/awx/ui/client/src/inventories/manage/adhoc/adhoc.route.js index d8eb57e735..935dac79f6 100644 --- a/awx/ui/client/src/inventories/manage/adhoc/adhoc.route.js +++ b/awx/ui/client/src/inventories/manage/adhoc/adhoc.route.js @@ -23,5 +23,10 @@ export default { }, ncyBreadcrumb: { label: "RUN COMMAND" + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } } }; diff --git a/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js b/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js index 85d856c5ed..e133414ce1 100644 --- a/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js +++ b/awx/ui/client/src/inventories/manage/copy-move/copy-move.route.js @@ -22,6 +22,11 @@ var copyMoveGroup = { return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]); }] }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views: { 'form@inventoryManage' : { controller: CopyMoveGroupsController, @@ -40,6 +45,11 @@ var copyMoveHost = { return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]); }] }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views: { 'form@inventoryManage': { templateUrl: templateUrl('inventories/manage/copy-move/copy-move'), diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index 9e13b74867..5cd2297129 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -104,7 +104,7 @@ if ($rootScope.inventoryManageStatus) { $rootScope.inventoryManageStatus(); } - $rootScope.inventoryManageStatus = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data){ + $rootScope.inventoryManageStatus = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data){ var group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); if(data.status === 'failed' || data.status === 'successful'){ $state.reload(); diff --git a/awx/ui/client/src/inventories/manage/groups/groups.route.js b/awx/ui/client/src/inventories/manage/groups/groups.route.js index 905a7d2997..e7a9ac8732 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups.route.js +++ b/awx/ui/client/src/inventories/manage/groups/groups.route.js @@ -16,6 +16,11 @@ var ManageGroupsEdit = { data: { mode: 'edit' }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, resolve: { groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService){ return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]); @@ -41,6 +46,11 @@ var ManageGroupsAdd = { data: { mode: 'add' }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views: { 'form@inventoryManage': { controller: addController, diff --git a/awx/ui/client/src/inventories/manage/hosts/hosts.route.js b/awx/ui/client/src/inventories/manage/hosts/hosts.route.js index 86661e70f5..2edbf3518b 100644 --- a/awx/ui/client/src/inventories/manage/hosts/hosts.route.js +++ b/awx/ui/client/src/inventories/manage/hosts/hosts.route.js @@ -23,6 +23,11 @@ var ManageHostsEdit = { }); }] }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views: { 'form@inventoryManage': { controller: editController, @@ -40,6 +45,11 @@ var ManageHostsAdd = { data: { mode: 'add' }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } + }, views: { 'form@inventoryManage': { controller: addController, 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 index 885034b747..b9fb2d4707 100644 --- 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 @@ -22,6 +22,12 @@ var hostEventModal = { return JobDetailService.getJobEventChildren($stateParams.taskId).then(res => res.data.results); }] }, + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + }, onExit: function() { // close the modal // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" @@ -36,28 +42,52 @@ var hostEventModal = { name: 'jobDetail.host-event.details', url: '/details', controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-details') + templateUrl: templateUrl('job-detail/host-event/host-event-details'), + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + } }; var hostEventJson = { name: 'jobDetail.host-event.json', url: '/json', controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') + templateUrl: templateUrl('job-detail/host-event/host-event-codemirror'), + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + } }; var hostEventStdout = { name: 'jobDetail.host-event.stdout', url: '/stdout', controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') + templateUrl: templateUrl('job-detail/host-event/host-event-codemirror'), + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + } }; var hostEventStderr = { name: 'jobDetail.host-event.stderr', url: '/stderr', controller: 'HostEventController', - templateUrl: templateUrl('job-detail/host-event/host-event-codemirror') + templateUrl: templateUrl('job-detail/host-event/host-event-codemirror'), + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + } }; 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 835d17b2b9..710ef26e84 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 @@ -22,6 +22,12 @@ export default { $('.modal-backdrop').remove(); $('body').removeClass('modal-open'); }, + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + }, resolve: { hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) { return JobDetailService.getRelatedJobEvents($stateParams.id, { diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js index cf648b8521..ab2b91e993 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js @@ -53,7 +53,7 @@ } // emitted by the API in the same function used to persist host summary data // JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py - $rootScope.removeJobStatusChange = $rootScope.$on('JobSummaryComplete', function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-JobSummaryComplete', function(e, data) { // discard socket msgs we don't care about in this context if (parseInt($stateParams.id) === data.unified_job_id){ init(); @@ -63,7 +63,7 @@ if ($rootScope.removeJobSummaryComplete) { $rootScope.removeJobSummaryComplete(); } - $rootScope.removeJobSummaryComplete = $rootScope.$on('jobDetail-jobs', function(e, data) { + $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobDetail-jobs', function(e, data) { if (parseInt($stateParams.id) === data.unified_job_id){ $scope.status = data.status; } diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js index 5d17fb172b..616e393dd3 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js @@ -14,6 +14,12 @@ export default { "jobs": ["status_changed"] } }, + socket: { + "groups":{ + "jobs": ["status_changed", "summary"], + "job_events": [] + } + }, views:{ 'host-summary': { controller: 'HostSummaryController', 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 9dfef14c84..d37ee0624d 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -201,7 +201,7 @@ export default if ($rootScope.removeJobEventChange) { $rootScope.removeJobEventChange(); } - $rootScope.removeJobEventChange = $rootScope.$on(`${$state.current.name}-job_events-${job_id}`, function(e, data) { + $rootScope.removeJobEventChange = $rootScope.$on(`ws-${$state.current.name}-job_events-${job_id}`, function(e, data) { // update elapsed time on each event received scope.job_status.elapsed = GetElapsed({ start: scope.job.created, @@ -225,7 +225,7 @@ export default if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { // if we receive a status change event for the current job indicating the job // is finished, stop event queue processing and reload if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { @@ -242,7 +242,7 @@ export default if ($rootScope.removeJobSummaryComplete) { $rootScope.removeJobSummaryComplete(); } - $rootScope.removeJobSummaryComplete = $rootScope.$on('JobSummaryComplete', function() { + $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-JobSummaryComplete', function() { // the job host summary should now be available from the API $log.debug('Trigging reload of job_host_summaries'); scope.$emit('InitialLoadComplete'); diff --git a/awx/ui/client/src/job-templates/add/job-templates-add.route.js b/awx/ui/client/src/job-templates/add/job-templates-add.route.js index 68275c0f22..b4ede48910 100644 --- a/awx/ui/client/src/job-templates/add/job-templates-add.route.js +++ b/awx/ui/client/src/job-templates/add/job-templates-add.route.js @@ -15,6 +15,11 @@ export default { parent: "jobTemplates", label: "CREATE JOB TEMPLATE" }, + socket:{ + "groups":{ + "jobs": ["status_changed"] + } + }, onExit: function(){ // close the survey maker modal // using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X" diff --git a/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js index b2dffa229b..4e2d34f3c0 100644 --- a/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js +++ b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js @@ -14,6 +14,11 @@ export default { data: { activityStreamId: 'id' }, + socket:{ + "groups":{ + "jobs": ["status_changed"] + } + }, ncyBreadcrumb: { parent: 'jobTemplates', label: "{{name}}" diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js index 4e50f43fec..6ad5fa053f 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js @@ -37,6 +37,14 @@ export default view.inject(list, { mode: mode, scope: $scope }); $rootScope.flashMessage = null; + + if ($rootScope.JobStatusChange) { + $rootScope.JobStatusChange(); + } + $rootScope.JobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function (e, data) { + $scope.search(list.iterator); + }); + if ($scope.removePostRefresh) { $scope.removePostRefresh(); } diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.route.js b/awx/ui/client/src/job-templates/list/job-templates-list.route.js index deeb1982bd..1a68cc8720 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.route.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.route.js @@ -17,5 +17,10 @@ export default { }, ncyBreadcrumb: { label: "JOB TEMPLATES" + }, + socket:{ + "groups":{ + "jobs": ["status_changed"] + } } }; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js index 58a8c60a17..4ec28f7643 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js @@ -21,6 +21,13 @@ export default ['$scope', '$rootScope', '$location', '$log', generator = GenerateList, orgBase = GetBasePath('organizations'); + if ($rootScope.JobStatusChange) { + $rootScope.JobStatusChange(); + } + $rootScope.JobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function (e, data) { + $scope.search(list.iterator); + }); + Rest.setUrl(orgBase + $stateParams.organization_id); Rest.get() .success(function (data) { diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index 0de1b568ed..f03b1ebb35 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -90,7 +90,7 @@ export default ['$scope', '$rootScope', '$location', '$log', if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js index 13d51cc68f..fa909af4e3 100644 --- a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js +++ b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js @@ -99,6 +99,11 @@ export default [ features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); }] + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } } }, { @@ -121,6 +126,11 @@ export default [ features: ['FeaturesService', function(FeaturesService) { return FeaturesService.get(); }] + }, + socket: { + "groups":{ + "jobs": ["status_changed"] + } } }, { diff --git a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js index 278bc2f4fc..10de01444b 100644 --- a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js +++ b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js @@ -16,7 +16,7 @@ export function PortalModeJobsController($scope, $rootScope, GetBasePath, Genera if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('portalMode-jobs', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-portalMode-jobs', function() { $scope.search('job'); }); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index da53e2998a..922d56759a 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -41,13 +41,13 @@ export default // we know that this must have been a // summary complete message $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('JobSummaryComplete', data); + $rootScope.$emit('ws-JobSummaryComplete', data); } else if(data.group_name==="job_events"){ - str = `${$state.current.name}-${data.group_name}-${data.job}`; + str = `ws-${$state.current.name}-${data.group_name}-${data.job}`; } else if(data.group_name==="ad_hoc_command_events"){ - str = `${$state.current.name}-${data.group_name}-${data.ad_hoc_command}`; + str = `ws-${$state.current.name}-${data.group_name}-${data.ad_hoc_command}`; } else if(data.group_name==="control"){ $log.debug(data.reason); @@ -59,7 +59,7 @@ export default // correct route is the route name followed by a // dash (-) and the group_name. // ex: 'jobDetail-job_events' - str = `${$state.current.name}-${data.group_name}`; + str = `ws-${$state.current.name}-${data.group_name}`; } $rootScope.$emit(str, data); return self.socket; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 2c46d218ce..52af6330ff 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.$on(`${$state.current.name}-job_events-${job_id}`, function() { + $rootScope.$on(`ws-${$state.current.name}-job_events-${job_id}`, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; @@ -36,7 +36,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $rootScope.$on(`${$state.current.name}-ad_hoc_command_events-${job_id}`, function() { + $rootScope.$on(`ws-${$state.current.name}-ad_hoc_command_events-${job_id}`, function() { $log.debug("socket fired on ad_hoc_command_events-" + job_id); if (api_complete) { event_queue++; @@ -192,7 +192,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 242d339ba9..b53acdb798 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -28,7 +28,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on(`${$state.current.name}-jobs`, function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { $scope.job.status = data.status; } From 5801d3cec8e575fb824556f16a8671c60f162c37 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 15:18:39 -0700 Subject: [PATCH 32/48] changing naming scheme for event listeners to "ws-" instead of ws-- --- awx/ui/client/src/controllers/Home.js | 2 +- awx/ui/client/src/controllers/Jobs.js | 4 ++-- awx/ui/client/src/controllers/Projects.js | 2 +- .../graphs/job-status/job-status-graph.service.js | 2 +- .../manage/groups/groups-list.controller.js | 2 +- .../host-summary/host-summary.controller.js | 12 ++++++------ .../src/job-detail/job-detail.controller.js | 6 +++--- .../list/job-templates-list.controller.js | 2 +- .../organizations-job-templates.controller.js | 2 +- .../organizations-projects.controller.js | 2 +- .../portal-mode/portal-mode-jobs.controller.js | 2 +- awx/ui/client/src/shared/socket/socket.service.js | 15 +++++++-------- .../log/standard-out-log.controller.js | 6 +++--- .../src/standard-out/standard-out.controller.js | 2 +- 14 files changed, 30 insertions(+), 31 deletions(-) diff --git a/awx/ui/client/src/controllers/Home.js b/awx/ui/client/src/controllers/Home.js index 281a5cee84..ff443923fc 100644 --- a/awx/ui/client/src/controllers/Home.js +++ b/awx/ui/client/src/controllers/Home.js @@ -28,7 +28,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log var dataCount = 0; - $rootScope.$on('ws-dashboard-jobs', function () { + $rootScope.$on('ws-jobs', function () { Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index e3cfd2ab99..8ed4ca9da0 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -113,14 +113,14 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs-jobs', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function() { $scope.refreshJobs(); }); if ($rootScope.removeScheduleStatusChange) { $rootScope.removeScheduleStatusChange(); } - $rootScope.removeScheduleStatusChange = $rootScope.$on('ws-jobs-schedules', function() { + $rootScope.removeScheduleStatusChange = $rootScope.$on('ws-schedules', function() { if (api_complete) { scheduled_scope.search('schedule'); } diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 56aca39209..3ec77ecb69 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -90,7 +90,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js index cf68287c44..0e6a3b4dda 100644 --- a/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js +++ b/awx/ui/client/src/dashboard/graphs/job-status/job-status-graph.service.js @@ -58,7 +58,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { destroyWatcher: angular.noop, setupWatcher: function(period, jobType) { this.destroyWatcher = - $rootScope.$on('ws-dashboard-jobs', function() { + $rootScope.$on('ws-jobs', function() { getData(period, jobType).then(function(result) { $rootScope. $broadcast('DataReceived:JobStatusGraph', diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index 5cd2297129..fc7326f6d0 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -104,7 +104,7 @@ if ($rootScope.inventoryManageStatus) { $rootScope.inventoryManageStatus(); } - $rootScope.inventoryManageStatus = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data){ + $rootScope.inventoryManageStatus = $rootScope.$on(`ws-jobs`, function(e, data){ var group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); if(data.status === 'failed' || data.status === 'successful'){ $state.reload(); diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js index ab2b91e993..c49eeb3d86 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js @@ -48,22 +48,22 @@ $scope.status = res.results[0].status; }); }; - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); + if ($rootScope.removeJobSummaryComplete) { + $rootScope.removeJobSummaryComplete(); } // emitted by the API in the same function used to persist host summary data // JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py - $rootScope.removeJobStatusChange = $rootScope.$on('ws-JobSummaryComplete', function(e, data) { + $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobs-summary', function(e, data) { // discard socket msgs we don't care about in this context if (parseInt($stateParams.id) === data.unified_job_id){ init(); } }); - if ($rootScope.removeJobSummaryComplete) { - $rootScope.removeJobSummaryComplete(); + if ($rootScope.removeJobStatusChange) { + $rootScope.removeJobStatusChange(); } - $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobDetail-jobs', function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function(e, data) { if (parseInt($stateParams.id) === data.unified_job_id){ $scope.status = data.status; } 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 d37ee0624d..b9f7edfca7 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -201,7 +201,7 @@ export default if ($rootScope.removeJobEventChange) { $rootScope.removeJobEventChange(); } - $rootScope.removeJobEventChange = $rootScope.$on(`ws-${$state.current.name}-job_events-${job_id}`, function(e, data) { + $rootScope.removeJobEventChange = $rootScope.$on(`ws-job_events-${job_id}`, function(e, data) { // update elapsed time on each event received scope.job_status.elapsed = GetElapsed({ start: scope.job.created, @@ -225,7 +225,7 @@ export default if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { // if we receive a status change event for the current job indicating the job // is finished, stop event queue processing and reload if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { @@ -242,7 +242,7 @@ export default if ($rootScope.removeJobSummaryComplete) { $rootScope.removeJobSummaryComplete(); } - $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-JobSummaryComplete', function() { + $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobs-summary', function() { // the job host summary should now be available from the API $log.debug('Trigging reload of job_host_summaries'); scope.$emit('InitialLoadComplete'); diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js index 6ad5fa053f..86890c7a5f 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js @@ -41,7 +41,7 @@ export default if ($rootScope.JobStatusChange) { $rootScope.JobStatusChange(); } - $rootScope.JobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function (e, data) { + $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function (e, data) { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js index 4ec28f7643..53e4317336 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js @@ -24,7 +24,7 @@ export default ['$scope', '$rootScope', '$location', '$log', if ($rootScope.JobStatusChange) { $rootScope.JobStatusChange(); } - $rootScope.JobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function (e, data) { + $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function (e, data) { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index f03b1ebb35..73d6156862 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -90,7 +90,7 @@ export default ['$scope', '$rootScope', '$location', '$log', if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { + $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js index 10de01444b..06c2339633 100644 --- a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js +++ b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js @@ -16,7 +16,7 @@ export function PortalModeJobsController($scope, $rootScope, GetBasePath, Genera if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('ws-portalMode-jobs', function() { + $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function() { $scope.search('job'); }); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 922d56759a..32cacbe7b0 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -30,7 +30,7 @@ export default $log.debug('Error Logged: ' + error); //log errors }; - self.socket.onclose = function (error) { + self.socket.onclose = function () { $log.debug('Websocket Disconnected.'); }; @@ -41,13 +41,13 @@ export default // we know that this must have been a // summary complete message $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('ws-JobSummaryComplete', data); + $rootScope.$emit('ws-jobs-summary', data); } else if(data.group_name==="job_events"){ - str = `ws-${$state.current.name}-${data.group_name}-${data.job}`; + str = `ws-${data.group_name}-${data.job}`; } else if(data.group_name==="ad_hoc_command_events"){ - str = `ws-${$state.current.name}-${data.group_name}-${data.ad_hoc_command}`; + str = `ws-${data.group_name}-${data.ad_hoc_command}`; } else if(data.group_name==="control"){ $log.debug(data.reason); @@ -55,11 +55,10 @@ export default $state.go('signOut'); } else { - // The naming scheme for emitting socket messages to the - // correct route is the route name followed by a + // The naming scheme is "ws" then a // dash (-) and the group_name. - // ex: 'jobDetail-job_events' - str = `ws-${$state.current.name}-${data.group_name}`; + // ex: 'ws-jobs' + str = `ws-${data.group_name}`; } $rootScope.$emit(str, data); return self.socket; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index 52af6330ff..d997b5eb1f 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.$on(`ws-${$state.current.name}-job_events-${job_id}`, function() { + $rootScope.$on(`ws-job_events-${job_id}`, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; @@ -36,7 +36,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $rootScope.$on(`ws-${$state.current.name}-ad_hoc_command_events-${job_id}`, function() { + $rootScope.$on(`ws-ad_hoc_command_events-${job_id}`, function() { $log.debug("socket fired on ad_hoc_command_events-" + job_id); if (api_complete) { event_queue++; @@ -192,7 +192,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index b53acdb798..689052e277 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -28,7 +28,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, if ($scope.removeJobStatusChange) { $scope.removeJobStatusChange(); } - $scope.removeJobStatusChange = $rootScope.$on(`ws-${$state.current.name}-jobs`, function(e, data) { + $scope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { $scope.job.status = data.status; } From cf54fe27e6d80b5dba0bfe0c1bc693f0416d6c63 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 9 Sep 2016 10:49:28 -0400 Subject: [PATCH 33/48] Fix schedules sitewide (#3469) * Remove custom rrule module loaders, update angular-scheduler dept * snip comment * remove nlp module loaders --- awx/ui/npm-shrinkwrap.json | 20 +++++++++++++++----- awx/ui/package.json | 1 - 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json index 376a395a52..c8e7f16371 100644 --- a/awx/ui/npm-shrinkwrap.json +++ b/awx/ui/npm-shrinkwrap.json @@ -149,6 +149,11 @@ "version": "3.8.0", "from": "lodash@>=3.8.0 <3.9.0", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz" + }, + "rrule": { + "version": "2.2.0-dev", + "from": "jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", + "resolved": "git://github.com/jkbrzt/rrule.git#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" } } }, @@ -2832,6 +2837,7 @@ }, "istanbul-lib-coverage": { "version": "1.0.0", +<<<<<<< 09aef3eb4366affa87873a3722971ca537d105a9 "from": "istanbul-lib-coverage@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.0.tgz" }, @@ -2839,6 +2845,15 @@ "version": "1.1.3", "from": "istanbul-lib-instrument@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.1.3.tgz" +======= + "from": "istanbul-lib-coverage@>=1.0.0-alpha.4 <2.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.0.tgz" + }, + "istanbul-lib-instrument": { + "version": "1.1.1", + "from": "istanbul-lib-instrument@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.1.1.tgz" +>>>>>>> Fix schedules sitewide (#3469) }, "javascript-detect-element-resize": { "version": "0.5.3", @@ -3952,11 +3967,6 @@ "from": "ripemd160@0.2.0", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz" }, - "rrule": { - "version": "2.2.0-dev", - "from": "jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", - "resolved": "git://github.com/jkbrzt/rrule.git#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" - }, "rx": { "version": "4.1.0", "from": "rx@4.1.0", diff --git a/awx/ui/package.json b/awx/ui/package.json index 0f212e163e..2aadac00ff 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -99,7 +99,6 @@ "ng-toast": "leigh-johnson/ngToast#2.0.1", "nvd3": "leigh-johnson/nvd3#1.7.1", "reconnectingwebsocket": "^1.0.0", - "rrule": "jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", "select2": "^4.0.2" } } From 4bb223401969c81f80653145d28afb06fa60a284 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 16:10:22 -0700 Subject: [PATCH 34/48] removing old code --- .../src/job-detail/job-detail.controller.js | 5 ----- .../src/shared/socket/socket.service.js | 22 ++++++------------- .../log/standard-out-log.controller.js | 10 --------- 3 files changed, 7 insertions(+), 30 deletions(-) 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 b9f7edfca7..d7bd9c3457 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -214,11 +214,6 @@ export default } UpdateDOM({ scope: scope }); }); - // Unbind $rootScope socket event binding(s) so that they don't get triggered - // in another instance of this controller - // scope.$on('$destroy', function() { - // $rootScope.socket.removeAllListeners("job_events-" + job_id); - // }); } openSocket(); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 32cacbe7b0..49aa2dc1f9 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -22,16 +22,17 @@ export default }); self.socket.onopen = function () { - $log.debug("Websocket connection opened"); + $log.debug("Websocket connection opened."); $rootScope.socketPromise.resolve(); }; self.socket.onerror = function (error) { - $log.debug('Error Logged: ' + error); //log errors + $log.debug('Websocket Error Logged: ' + error); //log errors }; - self.socket.onclose = function () { - $log.debug('Websocket Disconnected.'); + self.socket.onclose = function (error, obj) { + $log.debug('Websocket Disconnected: '+error); + self.checkStatus(); }; self.socket.onmessage = function (e) { @@ -57,12 +58,11 @@ export default else { // The naming scheme is "ws" then a // dash (-) and the group_name. - // ex: 'ws-jobs' + // ex: 'ws-jobs' str = `ws-${data.group_name}`; } $rootScope.$emit(str, data); return self.socket; - }; } @@ -159,14 +159,6 @@ export default }); }); }); - }, - removeAllListeners: function (eventName) { - var self = this; - if(self){ - if(self.socket){ - self.socket.removeEventListener(eventName); - } - } - }, + } }; }]; diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index d997b5eb1f..fe494d5603 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -28,11 +28,6 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce event_queue++; } }); - // Unbind $rootScope socket event binding(s) so that they don't get triggered - // in another instance of this controller - // $scope.$on('$destroy', function() { - // $rootScope.socket.removeAllListeners("job_events-" + job_id); - // }); } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); @@ -42,11 +37,6 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce event_queue++; } }); - // Unbind $rootScope socket event binding(s) so that they don't get triggered - // in another instance of this controller - // $scope.$on('$destroy', function() { - // $rootScope.adhoc_event_socket.removeAllListeners("ad_hoc_command_events-" + job_id); - // }); } } From 77d5772a2e3b67e5c8dcb3833cab38b5937f1f0f Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 14 Sep 2016 18:51:50 -0700 Subject: [PATCH 35/48] cleaning up jshint errors --- awx/ui/client/src/app.js | 95 +------------------ .../host-summary/host-summary.route.js | 5 - .../list/job-templates-list.controller.js | 2 +- .../organizations-job-templates.controller.js | 2 +- awx/ui/client/src/shared/icon/main.js | 2 +- awx/ui/client/src/shared/main.js | 2 - .../src/shared/socket/socket.service.js | 3 +- 7 files changed, 5 insertions(+), 106 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index cff60eb1ec..fbade9490f 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -195,7 +195,7 @@ var tower = angular.module('Tower', [ 'StandardOutHelper', 'LogViewerOptionsDefinition', 'JobDetailHelper', - 'socket', + socket.name, 'lrInfiniteScroll', 'LoadConfigHelper', 'PortalJobsListDefinition', @@ -240,99 +240,6 @@ var tower = angular.module('Tower', [ }); $stateProvider. - // state('dashboard', { - // url: '/home', - // templateUrl: urlPrefix + 'partials/home.html', - // controller: Home, - // params: { licenseMissing: null }, - // data: { - // activityStream: true, - // refreshButton: true - // }, - // ncyBreadcrumb: { - // label: "DASHBOARD" - // }, - // resolve: { - // graphData: ['$q', 'jobStatusGraphData', '$rootScope', - // function($q, jobStatusGraphData, $rootScope) { - // return $rootScope.featuresConfigured.promise.then(function() { - // return $q.all({ - // jobStatus: jobStatusGraphData.get("month", "all"), - // }); - // }); - // } - // ] - // } - // }). - // - // state('jobs', { - // url: '/jobs', - // templateUrl: urlPrefix + 'partials/jobs.html', - // controller: JobsListController, - // ncyBreadcrumb: { - // label: "JOBS" - // } - // }). - // - // state('projects', { - // url: '/projects?{status}', - // templateUrl: urlPrefix + 'partials/projects.html', - // controller: ProjectsList, - // data: { - // activityStream: true, - // activityStreamTarget: 'project' - // }, - // ncyBreadcrumb: { - // label: "PROJECTS" - // }, - // // socket: '{"groups":{"jobs": ["status_changed"]}}' - // // resolve: { - // // socket: ['SocketService', '$rootScope', - // // function(SocketService, $rootScope) { - // // var self = this; - // // $rootScope.socketPromise.promise.then(function(){ - // // SocketService.subscribe(self); - // // return true; - // // }); - // // }] - // // }, - // }). - // - // state('projects.add', { - // url: '/add', - // templateUrl: urlPrefix + 'partials/projects.html', - // controller: ProjectsAdd, - // ncyBreadcrumb: { - // parent: "projects", - // label: "CREATE PROJECT" - // } - // }). - // - // state('projects.edit', { - // url: '/:id', - // templateUrl: urlPrefix + 'partials/projects.html', - // controller: ProjectsEdit, - // data: { - // activityStreamId: 'id' - // }, - // ncyBreadcrumb: { - // parent: 'projects', - // label: '{{name}}' - // } - // }). - // - // state('projectOrganizations', { - // url: '/projects/:project_id/organizations', - // templateUrl: urlPrefix + 'partials/projects.html', - // controller: OrganizationsList - // }). - // - // state('projectOrganizationAdd', { - // url: '/projects/:project_id/organizations/add', - // templateUrl: urlPrefix + 'partials/projects.html', - // controller: OrganizationsAdd - // }). - state('teams', { url: '/teams', templateUrl: urlPrefix + 'partials/teams.html', diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js index 616e393dd3..8f5c1d2c0c 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.route.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.route.js @@ -9,11 +9,6 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'jobDetail.host-summary', url: '/event-summary', - socket: { - "groups":{ - "jobs": ["status_changed"] - } - }, socket: { "groups":{ "jobs": ["status_changed", "summary"], diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js index 86890c7a5f..ed5372e908 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js @@ -41,7 +41,7 @@ export default if ($rootScope.JobStatusChange) { $rootScope.JobStatusChange(); } - $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function (e, data) { + $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function () { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js index 53e4317336..ae1000a5c8 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js @@ -24,7 +24,7 @@ export default ['$scope', '$rootScope', '$location', '$log', if ($rootScope.JobStatusChange) { $rootScope.JobStatusChange(); } - $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function (e, data) { + $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function () { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/shared/icon/main.js b/awx/ui/client/src/shared/icon/main.js index a2dcf5a30c..87c8a0d474 100644 --- a/awx/ui/client/src/shared/icon/main.js +++ b/awx/ui/client/src/shared/icon/main.js @@ -3,5 +3,5 @@ import icon from './icon.directive'; export default angular.module('awIcon', []) - .directive('awIcon', icon) + .directive('awIcon', icon); //.directive('includeSvg', includeSvg); diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index 60c812e12c..fca72b91e1 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -6,7 +6,6 @@ import listGenerator from './list-generator/main'; import pagination from './pagination/main'; -import title from './title.directive'; import lodashAsPromised from './lodash-as-promised'; import stringFilters from './string-filters/main'; import truncatedText from './truncated-text.directive'; @@ -22,5 +21,4 @@ angular.module('shared', [listGenerator.name, ]) .factory('lodashAsPromised', lodashAsPromised) .directive('truncatedText', truncatedText) - //.directive('title', title) .provider('$stateExtender', stateExtender); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 49aa2dc1f9..f9bfb1ea0d 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -30,7 +30,7 @@ export default $log.debug('Websocket Error Logged: ' + error); //log errors }; - self.socket.onclose = function (error, obj) { + self.socket.onclose = function (error) { $log.debug('Websocket Disconnected: '+error); self.checkStatus(); }; @@ -83,7 +83,6 @@ export default } }, subscribe: function(state){ - console.log("Next state: " + state.name); this.emit(JSON.stringify(state.socket)); this.setLast(state); }, From a4f134b772e4c52ea36d0153670136574be7da76 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Wed, 7 Sep 2016 11:47:19 -0400 Subject: [PATCH 36/48] use a better-supported babel plugin instead of jank webpack loader for istanbul instrumentation (#3341) --- awx/ui/npm-shrinkwrap.json | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json index c8e7f16371..c9bfe94f4b 100644 --- a/awx/ui/npm-shrinkwrap.json +++ b/awx/ui/npm-shrinkwrap.json @@ -4872,4 +4872,5 @@ } } } + "version": "3.1.0" } From 541f55fece9d24ce2d34a6542078e4e10b4a8677 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 19 Sep 2016 14:40:38 -0700 Subject: [PATCH 37/48] removing shrinkwrap to avoid merge conflict for now --- awx/ui/npm-shrinkwrap.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json index c9bfe94f4b..69eab6ddfd 100644 --- a/awx/ui/npm-shrinkwrap.json +++ b/awx/ui/npm-shrinkwrap.json @@ -2837,15 +2837,6 @@ }, "istanbul-lib-coverage": { "version": "1.0.0", -<<<<<<< 09aef3eb4366affa87873a3722971ca537d105a9 - "from": "istanbul-lib-coverage@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.0.tgz" - }, - "istanbul-lib-instrument": { - "version": "1.1.3", - "from": "istanbul-lib-instrument@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.1.3.tgz" -======= "from": "istanbul-lib-coverage@>=1.0.0-alpha.4 <2.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.0.tgz" }, @@ -2853,7 +2844,6 @@ "version": "1.1.1", "from": "istanbul-lib-instrument@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.1.1.tgz" ->>>>>>> Fix schedules sitewide (#3469) }, "javascript-detect-element-resize": { "version": "0.5.3", From 849c48a101a13fdd24b93adb9b30e8e3b5efaff5 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 19 Sep 2016 14:54:38 -0700 Subject: [PATCH 38/48] committing first pass at unit test for SocketService --- awx/ui/client/src/app.js | 2 - awx/ui/client/src/shared/main.js | 2 + .../src/shared/socket/socket.service.js | 64 ++++++++++--------- .../tests/spec/socket/socket.service-test.js | 31 +++++++++ 4 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 awx/ui/tests/spec/socket/socket.service-test.js diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index fbade9490f..f3d665fafe 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -74,7 +74,6 @@ import './shared/Modal'; import './shared/prompt-dialog'; import './shared/directives'; import './shared/filters'; -import socket from './shared/socket/main'; import './shared/features/main'; import config from './shared/config/main'; import './login/authenticationServices/pendo/ng-pendo'; @@ -195,7 +194,6 @@ var tower = angular.module('Tower', [ 'StandardOutHelper', 'LogViewerOptionsDefinition', 'JobDetailHelper', - socket.name, 'lrInfiniteScroll', 'LoadConfigHelper', 'PortalJobsListDefinition', diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index fca72b91e1..dbe34cfa1e 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -11,6 +11,7 @@ import stringFilters from './string-filters/main'; import truncatedText from './truncated-text.directive'; import stateExtender from './stateExtender.provider'; import rbacUiControl from './rbacUiControl'; +import socket from './socket/main'; export default angular.module('shared', [listGenerator.name, @@ -18,6 +19,7 @@ angular.module('shared', [listGenerator.name, stringFilters.name, 'ui.router', rbacUiControl.name + socket.name ]) .factory('lodashAsPromised', lodashAsPromised) .directive('truncatedText', truncatedText) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index f9bfb1ea0d..e94045cb21 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -5,8 +5,8 @@ *************************************************/ import ReconnectingWebSocket from 'reconnectingwebsocket'; export default -['$rootScope', '$location', '$log', 'Authorization','$state', - function ($rootScope, $location, $log, Authorization, $state) { +['$rootScope', '$location', '$log','$state', + function ($rootScope, $location, $log, $state) { return { init: function() { var self = this, @@ -35,36 +35,9 @@ export default self.checkStatus(); }; - self.socket.onmessage = function (e) { - $log.debug('Received From Server: ' + e.data); - var data = JSON.parse(e.data), str = ""; - if(data.group_name==="jobs" && !('status' in data)){ - // we know that this must have been a - // summary complete message - $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('ws-jobs-summary', data); - } - else if(data.group_name==="job_events"){ - str = `ws-${data.group_name}-${data.job}`; - } - else if(data.group_name==="ad_hoc_command_events"){ - str = `ws-${data.group_name}-${data.ad_hoc_command}`; - } - else if(data.group_name==="control"){ - $log.debug(data.reason); - $rootScope.sessionTimer.expireSession('session_limit'); - $state.go('signOut'); - } - else { - // The naming scheme is "ws" then a - // dash (-) and the group_name. - // ex: 'ws-jobs' - str = `ws-${data.group_name}`; - } - $rootScope.$emit(str, data); - return self.socket; - }; + self.socket.onmessage = this.onMessage; + return self.socket; } else { // encountered expired token, redirect to login page @@ -77,6 +50,35 @@ export default $log.debug('socket status: ' + $rootScope.socketStatus); }, 2000); }, + onMessage: function(e){ + $log.debug('Received From Server: ' + e.data); + var data = JSON.parse(e.data), str = ""; + if(data.group_name==="jobs" && !('status' in data)){ + // we know that this must have been a + // summary complete message b/c status is missing + $log.debug('Job summary_complete ' + data.unified_job_id); + $rootScope.$emit('ws-jobs-summary', data); + return; + } + else if(data.group_name==="job_events"){ + str = `ws-${data.group_name}-${data.job}`; + } + else if(data.group_name==="ad_hoc_command_events"){ + str = `ws-${data.group_name}-${data.ad_hoc_command}`; + } + else if(data.group_name==="control"){ + $log.debug(data.reason); + $rootScope.sessionTimer.expireSession('session_limit'); + $state.go('signOut'); + } + else { + // The naming scheme is "ws" then a + // dash (-) and the group_name. + // ex: 'ws-jobs' + str = `ws-${data.group_name}`; + } + $rootScope.$emit(str, data); + }, disconnect: function(){ if(this.socket){ this.socket.close(); diff --git a/awx/ui/tests/spec/socket/socket.service-test.js b/awx/ui/tests/spec/socket/socket.service-test.js new file mode 100644 index 0000000000..c2f4d1c0b4 --- /dev/null +++ b/awx/ui/tests/spec/socket/socket.service-test.js @@ -0,0 +1,31 @@ +'use strict'; + +describe('Service: SocketService', () => { + + let SocketService, + rootScope, + event; + + beforeEach(angular.mock.module('shared')); + beforeEach(angular.mock.module('socket', function($provide){ + $provide.value('$rootScope', rootScope); + $provide.value('$location', {url: function(){}}); + })); + beforeEach(angular.mock.inject(($rootScope, _SocketService_) => { + rootScope = $rootScope.$new(); + rootScope.$emit = jasmine.createSpy('$emit'); + SocketService = _SocketService_; + })); + + describe('socket onmessage() should broadcast to correct event listener', function(){ + + it('should send to ws-jobs-summary', function(){ + event = {data : {group_name: "jobs"}}; + event.data = JSON.stringify(event.data); + console.log(event); + SocketService.onMessage(event); + expect(rootScope.$emit).toHaveBeenCalledWith('ws-jobs-summary', event.data); + }); + }); + +}); From 37e282735a6ea74bd9f6582fc3b9244a42637a86 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 4 Oct 2016 13:58:54 -0700 Subject: [PATCH 39/48] fixing statusTip and socketStatus --- .../src/shared/socket/socket.service.js | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index e94045cb21..b2c98e7f01 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -30,13 +30,15 @@ export default $log.debug('Websocket Error Logged: ' + error); //log errors }; - self.socket.onclose = function (error) { - $log.debug('Websocket Disconnected: '+error); - self.checkStatus(); + self.socket.onclose = function () { + $log.debug('Websocket Disconnected'); }; self.socket.onmessage = this.onMessage; - + setTimeout(function() { + self.checkStatus(); + $log.debug('Socket Status: ' + $rootScope.socketStatus); + }, 2000); return self.socket; } else { @@ -44,11 +46,6 @@ export default $rootScope.sessionTimer.expireSession('idle'); $location.url('/login'); } - - setTimeout(function() { - self.checkStatus(); - $log.debug('socket status: ' + $rootScope.socketStatus); - }, 2000); }, onMessage: function(e){ $log.debug('Received From Server: ' + e.data); @@ -114,35 +111,22 @@ export default } }, checkStatus: function() { - function getSocketTip(status) { - var result = ''; - switch(status) { - case 'error': - result = "Live events: error connecting to the Tower server."; - break; - case 'connecting': - result = "Live events: attempting to connect to the Tower server."; - break; - case "ok": - result = "Live events: connected. Pages containing job status information will automatically update in real-time."; - } - return result; - } - // Check connection status var self = this; if(self){ if(self.socket){ if (self.socket.readyState === 0 ) { $rootScope.socketStatus = 'connecting'; + $rootScope.socketTip = "Live events: attempting to connect to the Tower server."; } else if (self.socket.readyState === 1){ $rootScope.socketStatus = 'ok'; + $rootScope.socketTip = "Live events: connected. Pages containing job status information will automatically update in real-time."; } else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ $rootScope.socketStatus = 'error'; + $rootScope.socketTip = "Live events: error connecting to the Tower server."; } - self.socketTip = getSocketTip(self.socketStatus); - return $rootScope.socketStatus; + return; } } From 5edd9e73c889b6b84131d2c5fbe1bdf2e1e3b8cd Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 4 Oct 2016 16:24:58 -0700 Subject: [PATCH 40/48] removing npm-shrinkwrap to avoid conflict --- awx/ui/client/src/shared/main.js | 2 +- awx/ui/npm-shrinkwrap.json | 4866 ------------------------------ 2 files changed, 1 insertion(+), 4867 deletions(-) delete mode 100644 awx/ui/npm-shrinkwrap.json diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index dbe34cfa1e..8f92fab287 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -18,7 +18,7 @@ angular.module('shared', [listGenerator.name, pagination.name, stringFilters.name, 'ui.router', - rbacUiControl.name + rbacUiControl.name, socket.name ]) .factory('lodashAsPromised', lodashAsPromised) diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json deleted file mode 100644 index 69eab6ddfd..0000000000 --- a/awx/ui/npm-shrinkwrap.json +++ /dev/null @@ -1,4866 +0,0 @@ -{ - "name": "ansible-tower", - "version": "3.1.0", - "dependencies": { - "abbrev": { - "version": "1.0.9", - "from": "abbrev@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" - }, - "accepts": { - "version": "1.3.3", - "from": "accepts@>=1.3.3 <1.4.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz" - }, - "acorn": { - "version": "3.3.0", - "from": "acorn@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" - }, - "active-x-obfuscator": { - "version": "0.0.1", - "from": "active-x-obfuscator@0.0.1", - "resolved": "https://registry.npmjs.org/active-x-obfuscator/-/active-x-obfuscator-0.0.1.tgz" - }, - "adm-zip": { - "version": "0.4.7", - "from": "adm-zip@>=0.4.3 <0.5.0", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz" - }, - "after": { - "version": "0.8.1", - "from": "after@0.8.1", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.1.tgz" - }, - "agent-base": { - "version": "2.0.1", - "from": "agent-base@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.1.tgz", - "dependencies": { - "semver": { - "version": "5.0.3", - "from": "semver@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz" - } - } - }, - "align-text": { - "version": "0.1.4", - "from": "align-text@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" - }, - "almond": { - "version": "0.3.3", - "from": "almond@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz" - }, - "amdefine": { - "version": "1.0.0", - "from": "amdefine@>=0.0.4", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" - }, - "angular": { - "version": "1.4.12", - "from": "angular@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.4.12.tgz" - }, - "angular-breadcrumb": { - "version": "0.4.1", - "from": "leigh-johnson/angular-breadcrumb#0.4.1", - "resolved": "git://github.com/leigh-johnson/angular-breadcrumb.git#6c2b1ad45ad5fbe7adf39af1ef3b294ca8e207a9" - }, - "angular-codemirror": { - "version": "1.0.4", - "from": "chouseknecht/angular-codemirror#1.0.4", - "resolved": "git://github.com/chouseknecht/angular-codemirror.git#75c3a2d0ccdf2e4c836fab7d7617d5db6c585c1b", - "dependencies": { - "angular": { - "version": "1.4.7", - "from": "angular@1.4.7", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.4.7.tgz" - } - } - }, - "angular-cookies": { - "version": "1.5.8", - "from": "angular-cookies@>=1.4.3 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.5.8.tgz" - }, - "angular-drag-and-drop-lists": { - "version": "1.4.0", - "from": "leigh-johnson/angular-drag-and-drop-lists#1.4.0", - "resolved": "git://github.com/leigh-johnson/angular-drag-and-drop-lists.git#4d32654ab7159689a7767b9be8fc85f9812ca5a8" - }, - "angular-filters": { - "version": "1.1.2", - "from": "angular-filters@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-filters/-/angular-filters-1.1.2.tgz" - }, - "angular-md5": { - "version": "0.1.10", - "from": "angular-md5@>=0.1.8 <0.2.0", - "resolved": "https://registry.npmjs.org/angular-md5/-/angular-md5-0.1.10.tgz" - }, - "angular-moment": { - "version": "0.10.3", - "from": "angular-moment@>=0.10.1 <0.11.0", - "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-0.10.3.tgz", - "dependencies": { - "moment": { - "version": "2.10.6", - "from": "moment@>=2.8.0 <2.11.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.10.6.tgz" - } - } - }, - "angular-resource": { - "version": "1.5.8", - "from": "angular-resource@>=1.4.3 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-resource/-/angular-resource-1.5.8.tgz" - }, - "angular-sanitize": { - "version": "1.5.8", - "from": "angular-sanitize@>=1.4.3 <2.0.0", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.5.8.tgz" - }, - "angular-scheduler": { - "version": "0.1.0", - "from": "chouseknecht/angular-scheduler#0.1.0", - "resolved": "git://github.com/chouseknecht/angular-scheduler.git#784693054597b9a1c1e49efb4cf94e9054b92e66", - "dependencies": { - "angular-tz-extensions": { - "version": "0.3.11", - "from": "chouseknecht/angular-tz-extensions", - "resolved": "git://github.com/chouseknecht/angular-tz-extensions.git#a9b70c69ba27a19e1b1f9facbd85e870060aace9", - "dependencies": { - "angular": { - "version": "1.4.7", - "from": "angular@1.4.7", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.4.7.tgz" - }, - "jquery": { - "version": "3.1.0", - "from": "jquery@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.1.0.tgz" - } - } - }, - "lodash": { - "version": "3.8.0", - "from": "lodash@>=3.8.0 <3.9.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz" - }, - "rrule": { - "version": "2.2.0-dev", - "from": "jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", - "resolved": "git://github.com/jkbrzt/rrule.git#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" - } - } - }, - "angular-tz-extensions": { - "version": "0.3.11", - "from": "chouseknecht/angular-tz-extensions#0.3.11", - "resolved": "git://github.com/chouseknecht/angular-tz-extensions.git#a9b70c69ba27a19e1b1f9facbd85e870060aace9", - "dependencies": { - "angular": { - "version": "1.4.7", - "from": "angular@1.4.7", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.4.7.tgz" - }, - "jquery": { - "version": "3.1.0", - "from": "jquery@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.1.0.tgz" - } - } - }, - "angular-ui-router": { - "version": "0.2.18", - "from": "angular-ui-router@>=0.2.15 <0.3.0", - "resolved": "https://registry.npmjs.org/angular-ui-router/-/angular-ui-router-0.2.18.tgz" - }, - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "anymatch": { - "version": "1.3.0", - "from": "anymatch@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" - }, - "archiver": { - "version": "0.14.4", - "from": "archiver@>=0.14.0 <0.15.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-0.14.4.tgz", - "dependencies": { - "async": { - "version": "0.9.2", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" - }, - "glob": { - "version": "4.3.5", - "from": "glob@>=4.3.0 <4.4.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "lodash": { - "version": "3.2.0", - "from": "lodash@>=3.2.0 <3.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.2.0.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" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.26 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "argparse": { - "version": "1.0.7", - "from": "argparse@>=1.0.7 <2.0.0", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.7.tgz" - }, - "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" - }, - "arr-flatten": { - "version": "1.0.1", - "from": "arr-flatten@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" - }, - "array-differ": { - "version": "1.0.0", - "from": "array-differ@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz" - }, - "array-find-index": { - "version": "1.0.1", - "from": "array-find-index@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz" - }, - "array-flatten": { - "version": "1.1.1", - "from": "array-flatten@1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - }, - "array-slice": { - "version": "0.2.3", - "from": "array-slice@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz" - }, - "array-union": { - "version": "1.0.2", - "from": "array-union@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz" - }, - "array-uniq": { - "version": "1.0.3", - "from": "array-uniq@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" - }, - "array-unique": { - "version": "0.2.1", - "from": "array-unique@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" - }, - "arraybuffer.slice": { - "version": "0.0.6", - "from": "arraybuffer.slice@0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz" - }, - "arrify": { - "version": "1.0.1", - "from": "arrify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" - }, - "asap": { - "version": "2.0.4", - "from": "asap@>=2.0.3 <2.1.0", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.4.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert": { - "version": "1.4.1", - "from": "assert@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async": { - "version": "1.5.2", - "from": "async@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "async-each": { - "version": "1.0.1", - "from": "async-each@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz" - }, - "async-each-series": { - "version": "0.1.1", - "from": "async-each-series@0.1.1", - "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz" - }, - "asynckit": { - "version": "0.4.0", - "from": "asynckit@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - }, - "autoprefixer": { - "version": "6.4.1", - "from": "autoprefixer@>=6.0.0 <7.0.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.4.1.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "aws4": { - "version": "1.4.1", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" - }, - "babel-code-frame": { - "version": "6.11.0", - "from": "babel-code-frame@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.11.0.tgz" - }, - "babel-core": { - "version": "6.14.0", - "from": "babel-core@>=6.11.4 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.14.0.tgz", - "dependencies": { - "json5": { - "version": "0.4.0", - "from": "json5@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz" - }, - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - }, - "babel-generator": { - "version": "6.14.0", - "from": "babel-generator@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.14.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - }, - "babel-helper-call-delegate": { - "version": "6.8.0", - "from": "babel-helper-call-delegate@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.8.0.tgz" - }, - "babel-helper-define-map": { - "version": "6.9.0", - "from": "babel-helper-define-map@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.9.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-helper-function-name": { - "version": "6.8.0", - "from": "babel-helper-function-name@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.8.0.tgz" - }, - "babel-helper-get-function-arity": { - "version": "6.8.0", - "from": "babel-helper-get-function-arity@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.8.0.tgz" - }, - "babel-helper-hoist-variables": { - "version": "6.8.0", - "from": "babel-helper-hoist-variables@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.8.0.tgz" - }, - "babel-helper-optimise-call-expression": { - "version": "6.8.0", - "from": "babel-helper-optimise-call-expression@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.8.0.tgz" - }, - "babel-helper-regex": { - "version": "6.9.0", - "from": "babel-helper-regex@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.9.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-helper-replace-supers": { - "version": "6.14.0", - "from": "babel-helper-replace-supers@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.14.0.tgz" - }, - "babel-helpers": { - "version": "6.8.0", - "from": "babel-helpers@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.8.0.tgz" - }, - "babel-messages": { - "version": "6.8.0", - "from": "babel-messages@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.8.0.tgz" - }, - "babel-plugin-check-es2015-constants": { - "version": "6.8.0", - "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz" - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "from": "babel-plugin-syntax-async-functions@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz" - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-arrow-functions@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-block-scoped-functions@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.15.0", - "from": "babel-plugin-transform-es2015-block-scoping@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.15.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.14.0", - "from": "babel-plugin-transform-es2015-classes@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.14.0.tgz" - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-computed-properties@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.9.0", - "from": "babel-plugin-transform-es2015-destructuring@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.9.0.tgz" - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-duplicate-keys@>=6.6.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-for-of@>=6.6.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.9.0", - "from": "babel-plugin-transform-es2015-function-name@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.9.0.tgz" - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-literals@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-modules-amd@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.14.0", - "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.14.0.tgz" - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.14.0", - "from": "babel-plugin-transform-es2015-modules-systemjs@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.14.0.tgz" - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.12.0", - "from": "babel-plugin-transform-es2015-modules-umd@>=6.12.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.12.0.tgz" - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-object-super@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.11.4", - "from": "babel-plugin-transform-es2015-parameters@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.11.4.tgz" - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-shorthand-properties@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-spread@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-sticky-regex@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-template-literals@>=6.6.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.8.0", - "from": "babel-plugin-transform-es2015-typeof-symbol@>=6.6.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.8.0.tgz" - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.11.0", - "from": "babel-plugin-transform-es2015-unicode-regex@>=6.3.13 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.11.0.tgz" - }, - "babel-plugin-transform-regenerator": { - "version": "6.14.0", - "from": "babel-plugin-transform-regenerator@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.14.0.tgz" - }, - "babel-plugin-transform-strict-mode": { - "version": "6.11.3", - "from": "babel-plugin-transform-strict-mode@>=6.8.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.11.3.tgz" - }, - "babel-register": { - "version": "6.14.0", - "from": "babel-register@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.14.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-runtime": { - "version": "6.11.6", - "from": "babel-runtime@>=6.9.1 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.11.6.tgz" - }, - "babel-template": { - "version": "6.15.0", - "from": "babel-template@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.15.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-traverse": { - "version": "6.15.0", - "from": "babel-traverse@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.15.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babel-types": { - "version": "6.15.0", - "from": "babel-types@>=6.14.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.15.0.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.2.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "babylon": { - "version": "6.10.0", - "from": "babylon@>=6.9.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.10.0.tgz" - }, - "backo2": { - "version": "1.0.2", - "from": "backo2@1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz" - }, - "balanced-match": { - "version": "0.4.2", - "from": "balanced-match@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" - }, - "Base64": { - "version": "0.2.1", - "from": "Base64@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz" - }, - "base64-arraybuffer": { - "version": "0.1.2", - "from": "base64-arraybuffer@0.1.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.2.tgz" - }, - "base64-js": { - "version": "1.1.2", - "from": "base64-js@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz" - }, - "base64id": { - "version": "0.1.0", - "from": "base64id@0.1.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz" - }, - "batch": { - "version": "0.5.3", - "from": "batch@0.5.3", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz" - }, - "bcrypt-pbkdf": { - "version": "1.0.0", - "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz", - "dependencies": { - "tweetnacl": { - "version": "0.14.3", - "from": "tweetnacl@>=0.14.3 <0.15.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz" - } - } - }, - "beeper": { - "version": "1.1.0", - "from": "beeper@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz" - }, - "benchmark": { - "version": "1.0.0", - "from": "benchmark@1.0.0", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-1.0.0.tgz" - }, - "better-assert": { - "version": "1.0.2", - "from": "better-assert@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" - }, - "big.js": { - "version": "3.1.3", - "from": "big.js@>=3.1.3 <4.0.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz" - }, - "binary-extensions": { - "version": "1.6.0", - "from": "binary-extensions@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.6.0.tgz" - }, - "bindings": { - "version": "1.2.1", - "from": "bindings@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" - }, - "bl": { - "version": "1.1.2", - "from": "bl@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz" - }, - "blob": { - "version": "0.0.4", - "from": "blob@0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz" - }, - "bluebird": { - "version": "3.4.6", - "from": "bluebird@>=3.3.0 <4.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz" - }, - "body-parser": { - "version": "1.14.2", - "from": "body-parser@>=1.14.0 <1.15.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", - "dependencies": { - "http-errors": { - "version": "1.3.1", - "from": "http-errors@>=1.3.1 <1.4.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz" - }, - "qs": { - "version": "5.2.0", - "from": "qs@5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz" - } - } - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "bootstrap": { - "version": "3.3.7", - "from": "bootstrap@>=3.1.1 <4.0.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz" - }, - "bootstrap-datepicker": { - "version": "1.6.4", - "from": "bootstrap-datepicker@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bootstrap-datepicker/-/bootstrap-datepicker-1.6.4.tgz" - }, - "brace-expansion": { - "version": "1.1.6", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" - }, - "braces": { - "version": "1.8.5", - "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz" - }, - "browser-sync": { - "version": "2.16.0", - "from": "browser-sync@>=2.14.0 <3.0.0", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.16.0.tgz", - "dependencies": { - "cliui": { - "version": "3.2.0", - "from": "cliui@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz" - }, - "window-size": { - "version": "0.2.0", - "from": "window-size@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz" - }, - "yargs": { - "version": "5.0.0", - "from": "yargs@5.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz" - } - } - }, - "browser-sync-client": { - "version": "2.4.2", - "from": "browser-sync-client@>=2.3.3 <3.0.0", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.4.2.tgz" - }, - "browser-sync-ui": { - "version": "0.6.1", - "from": "browser-sync-ui@0.6.1", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-0.6.1.tgz" - }, - "browserify-zlib": { - "version": "0.1.4", - "from": "browserify-zlib@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz" - }, - "browserslist": { - "version": "1.3.6", - "from": "browserslist@>=1.3.6 <1.4.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.3.6.tgz" - }, - "bs-recipes": { - "version": "1.2.3", - "from": "bs-recipes@1.2.3", - "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.2.3.tgz" - }, - "buffer": { - "version": "4.9.1", - "from": "buffer@>=4.9.0 <5.0.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz" - }, - "buffer-crc32": { - "version": "0.2.5", - "from": "buffer-crc32@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.5.tgz" - }, - "builtin-modules": { - "version": "1.1.1", - "from": "builtin-modules@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" - }, - "bytes": { - "version": "2.2.0", - "from": "bytes@2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz" - }, - "callsite": { - "version": "1.0.0", - "from": "callsite@1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz" - }, - "camelcase": { - "version": "1.2.1", - "from": "camelcase@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" - }, - "camelcase-keys": { - "version": "2.1.0", - "from": "camelcase-keys@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "dependencies": { - "camelcase": { - "version": "2.1.1", - "from": "camelcase@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz" - } - } - }, - "caniuse-db": { - "version": "1.0.30000539", - "from": "caniuse-db@>=1.0.30000527 <2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000539.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "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" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "chokidar": { - "version": "1.6.0", - "from": "chokidar@1.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.0.tgz" - }, - "cli": { - "version": "1.0.0", - "from": "cli@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.0.tgz", - "dependencies": { - "glob": { - "version": "7.1.0", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.0.tgz" - } - } - }, - "cli-width": { - "version": "1.1.1", - "from": "cli-width@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.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": { - "wordwrap": { - "version": "0.0.2", - "from": "wordwrap@0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" - } - } - }, - "clone": { - "version": "1.0.2", - "from": "clone@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" - }, - "code-point-at": { - "version": "1.0.0", - "from": "code-point-at@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz" - }, - "codemirror": { - "version": "5.19.0", - "from": "codemirror@>=5.17.0 <6.0.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.19.0.tgz" - }, - "coffee-script": { - "version": "1.10.0", - "from": "coffee-script@>=1.10.0 <1.11.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz" - }, - "colors": { - "version": "1.1.2", - "from": "colors@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" - }, - "combine-lists": { - "version": "1.0.1", - "from": "combine-lists@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.5.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - }, - "component-bind": { - "version": "1.0.0", - "from": "component-bind@1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz" - }, - "component-emitter": { - "version": "1.1.2", - "from": "component-emitter@1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz" - }, - "component-inherit": { - "version": "0.0.3", - "from": "component-inherit@0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz" - }, - "components-font-awesome": { - "version": "4.6.1", - "from": "components-font-awesome@>=4.6.1 <5.0.0", - "resolved": "https://registry.npmjs.org/components-font-awesome/-/components-font-awesome-4.6.1.tgz" - }, - "compress-commons": { - "version": "0.2.9", - "from": "compress-commons@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-0.2.9.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.26 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "compressible": { - "version": "2.0.8", - "from": "compressible@>=2.0.8 <2.1.0", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.8.tgz" - }, - "compression": { - "version": "1.6.2", - "from": "compression@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.6.2.tgz", - "dependencies": { - "bytes": { - "version": "2.3.0", - "from": "bytes@2.3.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.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" - }, - "concat-stream": { - "version": "1.5.0", - "from": "concat-stream@1.5.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz" - }, - "connect": { - "version": "3.4.1", - "from": "connect@3.4.1", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz" - }, - "connect-history-api-fallback": { - "version": "1.3.0", - "from": "connect-history-api-fallback@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz" - }, - "console-browserify": { - "version": "1.1.0", - "from": "console-browserify@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz" - }, - "constants-browserify": { - "version": "0.0.1", - "from": "constants-browserify@0.0.1", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz" - }, - "content-disposition": { - "version": "0.5.1", - "from": "content-disposition@0.5.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz" - }, - "content-type": { - "version": "1.0.2", - "from": "content-type@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz" - }, - "contextify": { - "version": "0.1.15", - "from": "contextify@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/contextify/-/contextify-0.1.15.tgz" - }, - "convert-source-map": { - "version": "1.3.0", - "from": "convert-source-map@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.3.0.tgz" - }, - "cookie": { - "version": "0.3.1", - "from": "cookie@0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz" - }, - "cookie-signature": { - "version": "1.0.6", - "from": "cookie-signature@1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - }, - "core-js": { - "version": "2.4.1", - "from": "core-js@>=2.4.0 <3.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "crc32-stream": { - "version": "0.3.4", - "from": "crc32-stream@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.3.4.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.24 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "crypto-browserify": { - "version": "3.2.8", - "from": "crypto-browserify@>=3.2.6 <3.3.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.2.8.tgz" - }, - "cson-parser": { - "version": "1.3.3", - "from": "cson-parser@>=1.0.9 <2.0.0", - "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.3.3.tgz" - }, - "cssom": { - "version": "0.2.5", - "from": "cssom@>=0.2.5 <0.3.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.2.5.tgz" - }, - "cssstyle": { - "version": "0.2.37", - "from": "cssstyle@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "dependencies": { - "cssom": { - "version": "0.3.1", - "from": "cssom@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.1.tgz" - } - } - }, - "ctype": { - "version": "0.5.3", - "from": "ctype@0.5.3", - "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" - }, - "currently-unhandled": { - "version": "0.4.1", - "from": "currently-unhandled@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz" - }, - "custom-event": { - "version": "1.0.0", - "from": "custom-event@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz" - }, - "d3": { - "version": "3.5.17", - "from": "d3@>=3.3.13 <4.0.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz" - }, - "dashdash": { - "version": "1.14.0", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "date-now": { - "version": "0.1.4", - "from": "date-now@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz" - }, - "date-time": { - "version": "1.1.0", - "from": "date-time@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-1.1.0.tgz" - }, - "dateformat": { - "version": "1.0.12", - "from": "dateformat@>=1.0.12 <1.1.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz" - }, - "debug": { - "version": "2.2.0", - "from": "debug@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.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" - }, - "deep-is": { - "version": "0.1.3", - "from": "deep-is@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "depd": { - "version": "1.1.0", - "from": "depd@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz" - }, - "destroy": { - "version": "1.0.4", - "from": "destroy@>=1.0.4 <1.1.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" - }, - "detect-indent": { - "version": "3.0.1", - "from": "detect-indent@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz" - }, - "dev-ip": { - "version": "1.0.1", - "from": "dev-ip@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz" - }, - "di": { - "version": "0.0.1", - "from": "di@>=0.0.1 <0.0.2", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz" - }, - "dom-serialize": { - "version": "2.2.1", - "from": "dom-serialize@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz" - }, - "dom-serializer": { - "version": "0.1.0", - "from": "dom-serializer@>=0.0.0 <1.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "from": "domelementtype@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz" - }, - "entities": { - "version": "1.1.1", - "from": "entities@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" - } - } - }, - "domain-browser": { - "version": "1.1.7", - "from": "domain-browser@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz" - }, - "domelementtype": { - "version": "1.3.0", - "from": "domelementtype@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" - }, - "domhandler": { - "version": "2.3.0", - "from": "domhandler@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz" - }, - "domutils": { - "version": "1.5.1", - "from": "domutils@>=1.5.0 <1.6.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz" - }, - "duplexify": { - "version": "3.4.5", - "from": "duplexify@>=3.1.2 <4.0.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.5.tgz" - }, - "easy-extender": { - "version": "2.3.2", - "from": "easy-extender@2.3.2", - "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.2.tgz" - }, - "eazy-logger": { - "version": "3.0.2", - "from": "eazy-logger@3.0.2", - "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.0.2.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "ee-first": { - "version": "1.1.1", - "from": "ee-first@1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - }, - "emitter-steward": { - "version": "1.0.0", - "from": "emitter-steward@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/emitter-steward/-/emitter-steward-1.0.0.tgz" - }, - "emojis-list": { - "version": "2.0.1", - "from": "emojis-list@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.0.1.tgz" - }, - "encodeurl": { - "version": "1.0.1", - "from": "encodeurl@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz" - }, - "end-of-stream": { - "version": "1.0.0", - "from": "end-of-stream@1.0.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", - "dependencies": { - "once": { - "version": "1.3.3", - "from": "once@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - } - } - }, - "engine.io": { - "version": "1.6.11", - "from": "engine.io@1.6.11", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.6.11.tgz", - "dependencies": { - "accepts": { - "version": "1.1.4", - "from": "accepts@1.1.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz" - }, - "mime-db": { - "version": "1.12.0", - "from": "mime-db@>=1.12.0 <1.13.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz" - }, - "mime-types": { - "version": "2.0.14", - "from": "mime-types@>=2.0.4 <2.1.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz" - }, - "negotiator": { - "version": "0.4.9", - "from": "negotiator@0.4.9", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" - }, - "ws": { - "version": "1.1.0", - "from": "ws@1.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.0.tgz" - } - } - }, - "engine.io-client": { - "version": "1.6.11", - "from": "engine.io-client@1.6.11", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.6.11.tgz", - "dependencies": { - "ws": { - "version": "1.0.1", - "from": "ws@1.0.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz" - } - } - }, - "engine.io-parser": { - "version": "1.2.4", - "from": "engine.io-parser@1.2.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.2.4.tgz", - "dependencies": { - "has-binary": { - "version": "0.1.6", - "from": "has-binary@0.1.6", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.6.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - } - } - }, - "enhanced-resolve": { - "version": "0.9.1", - "from": "enhanced-resolve@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", - "dependencies": { - "memory-fs": { - "version": "0.2.0", - "from": "memory-fs@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz" - } - } - }, - "ent": { - "version": "2.2.0", - "from": "ent@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz" - }, - "entities": { - "version": "1.0.0", - "from": "entities@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz" - }, - "errno": { - "version": "0.1.4", - "from": "errno@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz" - }, - "error-ex": { - "version": "1.3.0", - "from": "error-ex@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz" - }, - "es6-promise": { - "version": "3.2.1", - "from": "es6-promise@>=3.2.1 <3.3.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz" - }, - "escape-html": { - "version": "1.0.3", - "from": "escape-html@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "escodegen": { - "version": "1.8.1", - "from": "escodegen@>=1.8.0 <1.9.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "dependencies": { - "source-map": { - "version": "0.2.0", - "from": "source-map@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz" - } - } - }, - "esprima": { - "version": "2.7.3", - "from": "esprima@>=2.6.0 <3.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz" - }, - "estraverse": { - "version": "1.9.3", - "from": "estraverse@>=1.9.1 <2.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" - }, - "esutils": { - "version": "2.0.2", - "from": "esutils@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" - }, - "etag": { - "version": "1.7.0", - "from": "etag@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz" - }, - "eventemitter2": { - "version": "0.4.14", - "from": "eventemitter2@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz" - }, - "eventemitter3": { - "version": "1.2.0", - "from": "eventemitter3@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz" - }, - "events": { - "version": "1.1.1", - "from": "events@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz" - }, - "eventsource": { - "version": "0.1.6", - "from": "eventsource@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz" - }, - "exit": { - "version": "0.1.2", - "from": "exit@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" - }, - "expand-braces": { - "version": "0.1.2", - "from": "expand-braces@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "dependencies": { - "braces": { - "version": "0.1.5", - "from": "braces@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz" - }, - "expand-range": { - "version": "0.1.1", - "from": "expand-range@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz" - }, - "is-number": { - "version": "0.1.1", - "from": "is-number@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz" - }, - "repeat-string": { - "version": "0.2.2", - "from": "repeat-string@>=0.2.2 <0.3.0", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz" - } - } - }, - "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" - }, - "expand-range": { - "version": "1.8.2", - "from": "expand-range@>=1.8.1 <2.0.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz" - }, - "express": { - "version": "2.5.11", - "from": "express@>=2.5.0 <2.6.0", - "resolved": "https://registry.npmjs.org/express/-/express-2.5.11.tgz", - "dependencies": { - "connect": { - "version": "1.9.2", - "from": "connect@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz" - }, - "mkdirp": { - "version": "0.3.0", - "from": "mkdirp@0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" - }, - "qs": { - "version": "0.4.2", - "from": "qs@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.4.2.tgz" - } - } - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extglob": { - "version": "0.3.2", - "from": "extglob@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" - }, - "extract-zip": { - "version": "1.5.0", - "from": "extract-zip@>=1.5.0 <1.6.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", - "dependencies": { - "debug": { - "version": "0.7.4", - "from": "debug@0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.0", - "from": "mkdirp@0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz" - } - } - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "fast-levenshtein": { - "version": "2.0.4", - "from": "fast-levenshtein@>=2.0.4 <2.1.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.4.tgz" - }, - "faye-websocket": { - "version": "0.10.0", - "from": "faye-websocket@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz" - }, - "fd-slicer": { - "version": "1.0.1", - "from": "fd-slicer@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" - }, - "figures": { - "version": "1.7.0", - "from": "figures@>=1.3.5 <2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz" - }, - "file-sync-cmp": { - "version": "0.1.1", - "from": "file-sync-cmp@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz" - }, - "filename-regex": { - "version": "2.0.0", - "from": "filename-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" - }, - "fileset": { - "version": "0.2.1", - "from": "fileset@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz", - "dependencies": { - "minimatch": { - "version": "2.0.10", - "from": "minimatch@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" - } - } - }, - "fill-range": { - "version": "2.2.3", - "from": "fill-range@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" - }, - "finalhandler": { - "version": "0.4.1", - "from": "finalhandler@0.4.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz" - }, - "find-up": { - "version": "1.1.2", - "from": "find-up@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "dependencies": { - "path-exists": { - "version": "2.1.0", - "from": "path-exists@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" - } - } - }, - "findup-sync": { - "version": "0.3.0", - "from": "findup-sync@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz" - }, - "for-in": { - "version": "0.1.6", - "from": "for-in@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.6.tgz" - }, - "for-own": { - "version": "0.1.4", - "from": "for-own@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.4.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "2.0.0", - "from": "form-data@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz" - }, - "formidable": { - "version": "1.0.17", - "from": "formidable@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz" - }, - "forwarded": { - "version": "0.1.0", - "from": "forwarded@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" - }, - "fresh": { - "version": "0.3.0", - "from": "fresh@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz" - }, - "fs": { - "version": "0.0.2", - "from": "fs@0.0.2", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz" - }, - "fs-access": { - "version": "1.0.0", - "from": "fs-access@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.0.tgz" - }, - "fs-extra": { - "version": "0.30.0", - "from": "fs-extra@0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz" - }, - "fs.realpath": { - "version": "1.0.0", - "from": "fs.realpath@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - }, - "fsevents": { - "version": "1.0.14", - "from": "fsevents@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.14.tgz", - "dependencies": { - "abbrev": { - "version": "1.0.9", - "from": "abbrev@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" - }, - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "aproba": { - "version": "1.0.4", - "from": "aproba@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.0.4.tgz" - }, - "are-we-there-yet": { - "version": "1.1.2", - "from": "are-we-there-yet@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async": { - "version": "1.5.2", - "from": "async@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "aws4": { - "version": "1.4.1", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" - }, - "balanced-match": { - "version": "0.4.2", - "from": "balanced-match@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" - }, - "bl": { - "version": "1.1.2", - "from": "bl@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, - "block-stream": { - "version": "0.0.9", - "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "brace-expansion": { - "version": "1.1.5", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.5.tgz" - }, - "buffer-shims": { - "version": "1.0.0", - "from": "buffer-shims@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "code-point-at": { - "version": "1.0.0", - "from": "code-point-at@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.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" - }, - "console-control-strings": { - "version": "1.1.0", - "from": "console-control-strings@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "dashdash": { - "version": "1.14.0", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "debug": { - "version": "2.2.0", - "from": "debug@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" - }, - "deep-extend": { - "version": "0.4.1", - "from": "deep-extend@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "delegates": { - "version": "1.0.0", - "from": "delegates@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "1.0.0-rc4", - "from": "form-data@>=1.0.0-rc4 <1.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" - }, - "fs.realpath": { - "version": "1.0.0", - "from": "fs.realpath@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - }, - "fstream": { - "version": "1.0.10", - "from": "fstream@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz" - }, - "fstream-ignore": { - "version": "1.0.5", - "from": "fstream-ignore@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz" - }, - "gauge": { - "version": "2.6.0", - "from": "gauge@>=2.6.0 <2.7.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.6.0.tgz" - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "glob": { - "version": "7.0.5", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz" - }, - "graceful-fs": { - "version": "4.1.4", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "has-color": { - "version": "0.1.7", - "from": "has-color@>=0.1.7 <0.2.0", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" - }, - "has-unicode": { - "version": "2.0.1", - "from": "has-unicode@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.3 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "inflight": { - "version": "1.0.5", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" - }, - "inherits": { - "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "ini": { - "version": "1.3.4", - "from": "ini@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" - }, - "is-my-json-valid": { - "version": "2.13.1", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "json-schema": { - "version": "0.2.2", - "from": "json-schema@0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "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" - }, - "jsprim": { - "version": "1.3.0", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz" - }, - "mime-db": { - "version": "1.23.0", - "from": "mime-db@>=1.23.0 <1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" - }, - "mime-types": { - "version": "2.1.11", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" - }, - "minimatch": { - "version": "3.0.2", - "from": "minimatch@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.2.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "node-pre-gyp": { - "version": "0.6.29", - "from": "node-pre-gyp@>=0.6.29 <0.7.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.29.tgz" - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "nopt": { - "version": "3.0.6", - "from": "nopt@>=3.0.1 <3.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" - }, - "npmlog": { - "version": "3.1.2", - "from": "npmlog@>=3.1.2 <3.2.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-3.1.2.tgz" - }, - "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" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, - "object-assign": { - "version": "4.1.0", - "from": "object-assign@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.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" - }, - "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" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, - "process-nextick-args": { - "version": "1.0.7", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - }, - "qs": { - "version": "6.2.0", - "from": "qs@>=6.2.0 <6.3.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz" - }, - "rc": { - "version": "1.1.6", - "from": "rc@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", - "dependencies": { - "minimist": { - "version": "1.2.0", - "from": "minimist@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" - } - } - }, - "readable-stream": { - "version": "2.1.4", - "from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.4.tgz" - }, - "request": { - "version": "2.73.0", - "from": "request@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.73.0.tgz" - }, - "rimraf": { - "version": "2.5.3", - "from": "rimraf@>=2.5.0 <2.6.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.3.tgz" - }, - "semver": { - "version": "5.2.0", - "from": "semver@>=5.2.0 <5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.2.0.tgz" - }, - "set-blocking": { - "version": "2.0.0", - "from": "set-blocking@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - }, - "signal-exit": { - "version": "3.0.0", - "from": "signal-exit@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.0.tgz" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "sshpk": { - "version": "1.8.3", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "string-width": { - "version": "1.0.1", - "from": "string-width@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@>=1.0.4 <1.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "tar": { - "version": "2.2.1", - "from": "tar@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" - }, - "tar-pack": { - "version": "3.1.4", - "from": "tar-pack@>=3.1.0 <3.2.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.1.4.tgz" - }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" - }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" - }, - "tweetnacl": { - "version": "0.13.3", - "from": "tweetnacl@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" - }, - "uid-number": { - "version": "0.0.6", - "from": "uid-number@>=0.0.6 <0.1.0", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "wide-align": { - "version": "1.1.0", - "from": "wide-align@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz" - }, - "wrappy": { - "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" - } - } - }, - "gaze": { - "version": "1.1.1", - "from": "gaze@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.1.tgz" - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "get-caller-file": { - "version": "1.0.2", - "from": "get-caller-file@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz" - }, - "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" - }, - "getobject": { - "version": "0.1.0", - "from": "getobject@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz" - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "glob": { - "version": "5.0.15", - "from": "glob@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" - }, - "glob-base": { - "version": "0.3.0", - "from": "glob-base@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" - }, - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" - }, - "globals": { - "version": "8.18.0", - "from": "globals@>=8.3.0 <9.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz" - }, - "globule": { - "version": "1.0.0", - "from": "globule@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.0.0.tgz", - "dependencies": { - "glob": { - "version": "7.0.6", - "from": "glob@>=7.0.3 <7.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz" - }, - "lodash": { - "version": "4.9.0", - "from": "lodash@>=4.9.0 <4.10.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.9.0.tgz" - } - } - }, - "graceful-fs": { - "version": "4.1.6", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.6.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "grunt-cli": { - "version": "1.2.0", - "from": "grunt-cli@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz" - }, - "grunt-known-options": { - "version": "1.1.0", - "from": "grunt-known-options@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz" - }, - "grunt-legacy-log": { - "version": "1.0.0", - "from": "grunt-legacy-log@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz" - }, - "grunt-legacy-log-utils": { - "version": "1.0.0", - "from": "grunt-legacy-log-utils@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "dependencies": { - "lodash": { - "version": "4.3.0", - "from": "lodash@>=4.3.0 <4.4.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz" - } - } - }, - "grunt-legacy-util": { - "version": "1.0.0", - "from": "grunt-legacy-util@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "dependencies": { - "lodash": { - "version": "4.3.0", - "from": "lodash@>=4.3.0 <4.4.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz" - } - } - }, - "handlebars": { - "version": "4.0.5", - "from": "handlebars@>=4.0.0 <4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.5.tgz", - "dependencies": { - "source-map": { - "version": "0.4.4", - "from": "source-map@>=0.4.4 <0.5.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" - }, - "uglify-js": { - "version": "2.7.3", - "from": "uglify-js@>=2.6.0 <3.0.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.3.tgz", - "dependencies": { - "async": { - "version": "0.2.10", - "from": "async@>=0.2.6 <0.3.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - } - } - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "has-binary": { - "version": "0.1.7", - "from": "has-binary@0.1.7", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - } - } - }, - "has-cors": { - "version": "1.1.0", - "from": "has-cors@1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz" - }, - "has-flag": { - "version": "1.0.0", - "from": "has-flag@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" - }, - "hasha": { - "version": "2.2.0", - "from": "hasha@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.3 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.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" - }, - "hooker": { - "version": "0.2.3", - "from": "hooker@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz" - }, - "hosted-git-info": { - "version": "2.1.5", - "from": "hosted-git-info@>=2.1.4 <3.0.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.5.tgz" - }, - "htmlparser": { - "version": "1.7.7", - "from": "htmlparser@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/htmlparser/-/htmlparser-1.7.7.tgz" - }, - "htmlparser2": { - "version": "3.8.3", - "from": "htmlparser2@>=3.8.0 <3.9.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "http-browserify": { - "version": "1.7.0", - "from": "http-browserify@>=1.3.2 <2.0.0", - "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz" - }, - "http-errors": { - "version": "1.5.0", - "from": "http-errors@>=1.5.0 <1.6.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.0.tgz", - "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - } - } - }, - "http-proxy": { - "version": "1.14.0", - "from": "http-proxy@1.14.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.14.0.tgz" - }, - "http-proxy-middleware": { - "version": "0.17.1", - "from": "http-proxy-middleware@>=0.17.1 <0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.1.tgz", - "dependencies": { - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.14.2 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - } - } - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "https-browserify": { - "version": "0.0.0", - "from": "https-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.0.tgz" - }, - "https-proxy-agent": { - "version": "1.0.0", - "from": "https-proxy-agent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz" - }, - "iconv-lite": { - "version": "0.4.13", - "from": "iconv-lite@>=0.4.13 <0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" - }, - "ieee754": { - "version": "1.1.6", - "from": "ieee754@>=1.1.4 <2.0.0", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.6.tgz" - }, - "image-size": { - "version": "0.5.0", - "from": "image-size@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.0.tgz" - }, - "immutable": { - "version": "3.8.1", - "from": "immutable@3.8.1", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.1.tgz" - }, - "indent-string": { - "version": "2.1.0", - "from": "indent-string@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "dependencies": { - "repeating": { - "version": "2.0.1", - "from": "repeating@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" - } - } - }, - "indexof": { - "version": "0.0.1", - "from": "indexof@0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" - }, - "inflight": { - "version": "1.0.5", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" - }, - "inherits": { - "version": "2.0.3", - "from": "inherits@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - }, - "inquirer": { - "version": "0.8.5", - "from": "inquirer@>=0.8.2 <0.9.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", - "dependencies": { - "ansi-regex": { - "version": "1.1.1", - "from": "ansi-regex@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" - }, - "rx": { - "version": "2.5.3", - "from": "rx@>=2.4.3 <3.0.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz" - } - } - }, - "interpret": { - "version": "0.6.6", - "from": "interpret@>=0.6.4 <0.7.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz" - }, - "invariant": { - "version": "2.2.1", - "from": "invariant@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.1.tgz" - }, - "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" - }, - "ipaddr.js": { - "version": "1.1.1", - "from": "ipaddr.js@1.1.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.1.1.tgz" - }, - "irregular-plurals": { - "version": "1.2.0", - "from": "irregular-plurals@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz" - }, - "is-arrayish": { - "version": "0.2.1", - "from": "is-arrayish@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - }, - "is-binary-path": { - "version": "1.0.1", - "from": "is-binary-path@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" - }, - "is-buffer": { - "version": "1.1.4", - "from": "is-buffer@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" - }, - "is-builtin-module": { - "version": "1.0.0", - "from": "is-builtin-module@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz" - }, - "is-dotfile": { - "version": "1.0.2", - "from": "is-dotfile@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz" - }, - "is-equal-shallow": { - "version": "0.1.3", - "from": "is-equal-shallow@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" - }, - "is-extendable": { - "version": "0.1.1", - "from": "is-extendable@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" - }, - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" - }, - "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" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - }, - "is-my-json-valid": { - "version": "2.14.0", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.14.0.tgz" - }, - "is-number": { - "version": "2.1.0", - "from": "is-number@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" - }, - "is-posix-bracket": { - "version": "0.1.1", - "from": "is-posix-bracket@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" - }, - "is-primitive": { - "version": "2.0.0", - "from": "is-primitive@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-stream": { - "version": "1.1.0", - "from": "is-stream@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "is-utf8": { - "version": "0.2.1", - "from": "is-utf8@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isbinaryfile": { - "version": "3.0.1", - "from": "isbinaryfile@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.1.tgz" - }, - "isexe": { - "version": "1.1.2", - "from": "isexe@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" - }, - "isobject": { - "version": "2.1.0", - "from": "isobject@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "istanbul": { - "version": "0.4.5", - "from": "istanbul@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "dependencies": { - "supports-color": { - "version": "3.1.2", - "from": "supports-color@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" - } - } - }, - "istanbul-lib-coverage": { - "version": "1.0.0", - "from": "istanbul-lib-coverage@>=1.0.0-alpha.4 <2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.0.tgz" - }, - "istanbul-lib-instrument": { - "version": "1.1.1", - "from": "istanbul-lib-instrument@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.1.1.tgz" - }, - "javascript-detect-element-resize": { - "version": "0.5.3", - "from": "javascript-detect-element-resize@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "jquery": { - "version": "2.2.4", - "from": "jquery@2.2.4", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz" - }, - "jquery-mousewheel": { - "version": "3.1.13", - "from": "jquery-mousewheel@>=3.1.13 <3.2.0", - "resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz" - }, - "jquery-ui": { - "version": "1.10.5", - "from": "jquery-ui@1.10.5", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.10.5.tgz" - }, - "js-base64": { - "version": "2.1.9", - "from": "js-base64@>=2.1.9 <3.0.0", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz" - }, - "js-tokens": { - "version": "2.0.0", - "from": "js-tokens@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz" - }, - "js-yaml": { - "version": "3.6.1", - "from": "js-yaml@>=3.2.7 <4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "jsdom": { - "version": "0.5.7", - "from": "jsdom@0.5.7", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-0.5.7.tgz" - }, - "jsesc": { - "version": "0.5.0", - "from": "jsesc@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" - }, - "json-schema": { - "version": "0.2.3", - "from": "json-schema@0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - }, - "json3": { - "version": "3.2.6", - "from": "json3@3.2.6", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.2.6.tgz" - }, - "json5": { - "version": "0.5.0", - "from": "json5@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.0.tgz" - }, - "jsonfile": { - "version": "2.4.0", - "from": "jsonfile@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz" - }, - "jsonpointer": { - "version": "2.0.0", - "from": "jsonpointer@2.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" - }, - "jsprim": { - "version": "1.3.1", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz" - }, - "jstimezonedetect": { - "version": "1.0.5", - "from": "jstimezonedetect@1.0.5", - "resolved": "https://registry.npmjs.org/jstimezonedetect/-/jstimezonedetect-1.0.5.tgz" - }, - "kew": { - "version": "0.7.0", - "from": "kew@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz" - }, - "kind-of": { - "version": "3.0.4", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz" - }, - "klaw": { - "version": "1.3.0", - "from": "klaw@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.0.tgz" - }, - "lazy-cache": { - "version": "1.0.4", - "from": "lazy-cache@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" - }, - "lazy.js": { - "version": "0.4.2", - "from": "lazy.js@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/lazy.js/-/lazy.js-0.4.2.tgz" - }, - "lazystream": { - "version": "0.1.0", - "from": "lazystream@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-0.1.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "lcid": { - "version": "1.0.0", - "from": "lcid@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" - }, - "legacy-loader": { - "version": "0.0.2", - "from": "legacy-loader@0.0.2", - "resolved": "https://registry.npmjs.org/legacy-loader/-/legacy-loader-0.0.2.tgz" - }, - "less": { - "version": "2.7.1", - "from": "less@>=2.7.1 <2.8.0", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.1.tgz", - "dependencies": { - "mime": { - "version": "1.3.4", - "from": "mime@>=1.2.11 <2.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - } - } - }, - "levn": { - "version": "0.3.0", - "from": "levn@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" - }, - "limiter": { - "version": "1.1.0", - "from": "limiter@>=1.0.5 <2.0.0", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.0.tgz" - }, - "livereload-js": { - "version": "2.2.2", - "from": "livereload-js@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.2.2.tgz" - }, - "load-json-file": { - "version": "1.1.0", - "from": "load-json-file@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz" - }, - "loader-utils": { - "version": "0.2.16", - "from": "loader-utils@>=0.2.6 <0.3.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.16.tgz" - }, - "localtunnel": { - "version": "1.8.1", - "from": "localtunnel@1.8.1", - "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-1.8.1.tgz", - "dependencies": { - "asn1": { - "version": "0.1.11", - "from": "asn1@0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" - }, - "assert-plus": { - "version": "0.1.5", - "from": "assert-plus@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" - }, - "async": { - "version": "2.0.1", - "from": "async@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz" - }, - "bl": { - "version": "1.0.3", - "from": "bl@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz" - }, - "cliui": { - "version": "3.2.0", - "from": "cliui@>=3.0.3 <4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz" - }, - "form-data": { - "version": "1.0.1", - "from": "form-data@>=1.0.0-rc3 <1.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz" - }, - "http-signature": { - "version": "0.11.0", - "from": "http-signature@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz" - }, - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.8.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - }, - "qs": { - "version": "5.2.1", - "from": "qs@>=5.2.0 <5.3.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz" - }, - "request": { - "version": "2.65.0", - "from": "request@2.65.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.65.0.tgz" - }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.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" - }, - "yargs": { - "version": "3.29.0", - "from": "yargs@3.29.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.29.0.tgz" - } - } - }, - "lodash": { - "version": "3.10.1", - "from": "lodash@>=3.8.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" - }, - "lodash.assign": { - "version": "4.2.0", - "from": "lodash.assign@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "from": "lodash.clonedeep@>=4.3.2 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz" - }, - "log-symbols": { - "version": "1.0.2", - "from": "log-symbols@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz" - }, - "log4js": { - "version": "0.6.38", - "from": "log4js@>=0.6.31 <0.7.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - }, - "semver": { - "version": "4.3.6", - "from": "semver@>=4.3.3 <4.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.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" - }, - "loose-envify": { - "version": "1.2.0", - "from": "loose-envify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.2.0.tgz", - "dependencies": { - "js-tokens": { - "version": "1.0.3", - "from": "js-tokens@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.3.tgz" - } - } - }, - "loud-rejection": { - "version": "1.6.0", - "from": "loud-rejection@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz" - }, - "lr-infinite-scroll": { - "version": "1.0.0", - "from": "lorenzofox3/lrInfiniteScroll", - "resolved": "git://github.com/lorenzofox3/lrInfiniteScroll.git#59d348bc5c18f164438d2a30f1fd79b333c3b649" - }, - "lru-cache": { - "version": "2.2.4", - "from": "lru-cache@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz" - }, - "map-obj": { - "version": "1.0.1", - "from": "map-obj@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" - }, - "media-typer": { - "version": "0.3.0", - "from": "media-typer@0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - }, - "memory-fs": { - "version": "0.3.0", - "from": "memory-fs@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz" - }, - "meow": { - "version": "3.7.0", - "from": "meow@>=3.3.0 <4.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz" - }, - "merge-descriptors": { - "version": "1.0.1", - "from": "merge-descriptors@1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - }, - "methods": { - "version": "1.1.2", - "from": "methods@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - }, - "micromatch": { - "version": "2.3.11", - "from": "micromatch@>=2.3.11 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz" - }, - "mime": { - "version": "1.2.4", - "from": "mime@1.2.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.4.tgz" - }, - "mime-db": { - "version": "1.24.0", - "from": "mime-db@>=1.24.0 <1.25.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz" - }, - "mime-types": { - "version": "2.1.12", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz" - }, - "minimatch": { - "version": "3.0.3", - "from": "minimatch@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" - }, - "minimist": { - "version": "1.2.0", - "from": "minimist@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "dependencies": { - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - } - } - }, - "moment": { - "version": "2.15.1", - "from": "moment@>=2.10.2 <3.0.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.15.1.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "multimatch": { - "version": "2.1.0", - "from": "multimatch@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz" - }, - "mute-stream": { - "version": "0.0.4", - "from": "mute-stream@0.0.4", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz" - }, - "nan": { - "version": "2.4.0", - "from": "nan@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz" - }, - "negotiator": { - "version": "0.6.1", - "from": "negotiator@0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz" - }, - "ng-toast": { - "version": "2.0.0", - "from": "leigh-johnson/ngToast#2.0.1", - "resolved": "git://github.com/leigh-johnson/ngToast.git#fea95bb34d27687e414619b4f72c11735d909f93" - }, - "node-int64": { - "version": "0.3.3", - "from": "node-int64@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.3.3.tgz" - }, - "node-libs-browser": { - "version": "0.6.0", - "from": "node-libs-browser@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.6.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.1.13 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "nopt": { - "version": "3.0.6", - "from": "nopt@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz" - }, - "normalize-package-data": { - "version": "2.3.5", - "from": "normalize-package-data@>=2.3.2 <3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz" - }, - "normalize-path": { - "version": "2.0.1", - "from": "normalize-path@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" - }, - "normalize-range": { - "version": "0.1.2", - "from": "normalize-range@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" - }, - "null-check": { - "version": "1.0.0", - "from": "null-check@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz" - }, - "num2fraction": { - "version": "1.2.2", - "from": "num2fraction@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz" - }, - "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" - }, - "nvd3": { - "version": "1.7.1", - "from": "leigh-johnson/nvd3#1.7.1", - "resolved": "git://github.com/leigh-johnson/nvd3.git#a28bcd494a1df0677be7cf2ebc0578f44eb21102", - "dependencies": { - "d3": { - "version": "3.3.13", - "from": "d3@>=3.3.13 <3.4.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.3.13.tgz" - } - } - }, - "nwmatcher": { - "version": "1.3.8", - "from": "nwmatcher@>=1.3.1 <1.4.0", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.8.tgz" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, - "object-assign": { - "version": "4.1.0", - "from": "object-assign@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" - }, - "object-component": { - "version": "0.0.3", - "from": "object-component@0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz" - }, - "object-path": { - "version": "0.9.2", - "from": "object-path@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.9.2.tgz" - }, - "object.omit": { - "version": "2.0.0", - "from": "object.omit@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" - }, - "on-finished": { - "version": "2.3.0", - "from": "on-finished@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" - }, - "on-headers": { - "version": "1.0.1", - "from": "on-headers@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz" - }, - "once": { - "version": "1.4.0", - "from": "once@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - }, - "open": { - "version": "0.0.5", - "from": "open@0.0.5", - "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz" - }, - "openurl": { - "version": "1.1.0", - "from": "openurl@1.1.0", - "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.0.tgz" - }, - "opn": { - "version": "4.0.2", - "from": "opn@4.0.2", - "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz" - }, - "optimist": { - "version": "0.6.1", - "from": "optimist@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "dependencies": { - "minimist": { - "version": "0.0.10", - "from": "minimist@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" - }, - "wordwrap": { - "version": "0.0.3", - "from": "wordwrap@>=0.0.2 <0.1.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" - } - } - }, - "optionator": { - "version": "0.8.2", - "from": "optionator@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz" - }, - "options": { - "version": "0.0.6", - "from": "options@>=0.0.5", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz" - }, - "original": { - "version": "1.0.0", - "from": "original@>=0.0.5", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "dependencies": { - "url-parse": { - "version": "1.0.5", - "from": "url-parse@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz" - } - } - }, - "os-browserify": { - "version": "0.1.2", - "from": "os-browserify@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.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" - }, - "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" - }, - "pad-stream": { - "version": "1.2.0", - "from": "pad-stream@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz", - "dependencies": { - "repeating": { - "version": "2.0.1", - "from": "repeating@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz" - } - } - }, - "pako": { - "version": "0.2.9", - "from": "pako@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz" - }, - "parse-glob": { - "version": "3.0.4", - "from": "parse-glob@>=3.0.4 <4.0.0", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" - }, - "parse-json": { - "version": "2.2.0", - "from": "parse-json@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz" - }, - "parse-ms": { - "version": "1.0.1", - "from": "parse-ms@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz" - }, - "parsejson": { - "version": "0.0.1", - "from": "parsejson@0.0.1", - "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.1.tgz" - }, - "parseqs": { - "version": "0.0.2", - "from": "parseqs@0.0.2", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.2.tgz" - }, - "parseuri": { - "version": "0.0.4", - "from": "parseuri@0.0.4", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.4.tgz" - }, - "parseurl": { - "version": "1.3.1", - "from": "parseurl@>=1.3.1 <1.4.0", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz" - }, - "path-browserify": { - "version": "0.0.0", - "from": "path-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.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" - }, - "path-to-regexp": { - "version": "0.1.7", - "from": "path-to-regexp@0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - }, - "path-type": { - "version": "1.1.0", - "from": "path-type@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" - }, - "pbkdf2-compat": { - "version": "2.0.1", - "from": "pbkdf2-compat@2.0.1", - "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz" - }, - "pend": { - "version": "1.2.0", - "from": "pend@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" - }, - "phantomjs-prebuilt": { - "version": "2.1.12", - "from": "phantomjs-prebuilt@>=2.1.12 <3.0.0", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.12.tgz", - "dependencies": { - "async": { - "version": "2.0.1", - "from": "async@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.0.1.tgz" - }, - "form-data": { - "version": "1.0.1", - "from": "form-data@>=1.0.0-rc4 <1.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz" - }, - "lodash": { - "version": "4.16.1", - "from": "lodash@>=4.8.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.1.tgz" - }, - "request": { - "version": "2.74.0", - "from": "request@>=2.74.0 <2.75.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz" - } - } - }, - "pify": { - "version": "2.3.0", - "from": "pify@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, - "pkg-up": { - "version": "1.0.0", - "from": "pkg-up@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz" - }, - "plur": { - "version": "2.1.2", - "from": "plur@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz" - }, - "portscanner": { - "version": "1.0.0", - "from": "portscanner@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-1.0.0.tgz", - "dependencies": { - "async": { - "version": "0.1.15", - "from": "async@0.1.15", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.15.tgz" - } - } - }, - "postcss": { - "version": "5.2.0", - "from": "postcss@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.0.tgz", - "dependencies": { - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.6 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - }, - "supports-color": { - "version": "3.1.2", - "from": "supports-color@>=3.1.2 <4.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz" - } - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "from": "postcss-value-parser@>=3.2.3 <4.0.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz" - }, - "prelude-ls": { - "version": "1.1.2", - "from": "prelude-ls@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" - }, - "preserve": { - "version": "0.2.0", - "from": "preserve@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" - }, - "pretty-ms": { - "version": "2.1.0", - "from": "pretty-ms@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", - "dependencies": { - "plur": { - "version": "1.0.0", - "from": "plur@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-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" - }, - "process": { - "version": "0.11.9", - "from": "process@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.9.tgz" - }, - "process-nextick-args": { - "version": "1.0.7", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - }, - "progress": { - "version": "1.1.8", - "from": "progress@>=1.1.8 <1.2.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz" - }, - "promise": { - "version": "7.1.1", - "from": "promise@>=7.1.1 <8.0.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz" - }, - "proxy-addr": { - "version": "1.1.2", - "from": "proxy-addr@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.2.tgz" - }, - "prr": { - "version": "0.0.0", - "from": "prr@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz" - }, - "pump": { - "version": "1.0.1", - "from": "pump@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.1.tgz", - "dependencies": { - "end-of-stream": { - "version": "1.1.0", - "from": "end-of-stream@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", - "dependencies": { - "once": { - "version": "1.3.3", - "from": "once@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - } - } - } - } - }, - "pumpify": { - "version": "1.3.5", - "from": "pumpify@>=1.3.3 <2.0.0", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz" - }, - "punycode": { - "version": "1.4.1", - "from": "punycode@>=1.2.4 <2.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - }, - "q": { - "version": "1.4.1", - "from": "q@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz" - }, - "qjobs": { - "version": "1.1.5", - "from": "qjobs@>=1.1.4 <2.0.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz" - }, - "qs": { - "version": "6.2.1", - "from": "qs@>=6.2.0 <6.3.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz" - }, - "querystring": { - "version": "0.2.0", - "from": "querystring@0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" - }, - "querystring-es3": { - "version": "0.2.1", - "from": "querystring-es3@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" - }, - "querystringify": { - "version": "0.0.4", - "from": "querystringify@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz" - }, - "randomatic": { - "version": "1.1.5", - "from": "randomatic@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" - }, - "range-parser": { - "version": "1.2.0", - "from": "range-parser@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" - }, - "raw-body": { - "version": "2.1.7", - "from": "raw-body@>=2.1.5 <2.2.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "dependencies": { - "bytes": { - "version": "2.4.0", - "from": "bytes@2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz" - } - } - }, - "rcfinder": { - "version": "0.1.9", - "from": "rcfinder@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/rcfinder/-/rcfinder-0.1.9.tgz" - }, - "rcloader": { - "version": "0.1.2", - "from": "rcloader@0.1.2", - "resolved": "https://registry.npmjs.org/rcloader/-/rcloader-0.1.2.tgz", - "dependencies": { - "lodash": { - "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" - } - } - }, - "read-pkg": { - "version": "1.1.0", - "from": "read-pkg@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz" - }, - "read-pkg-up": { - "version": "1.0.1", - "from": "read-pkg-up@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz" - }, - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - }, - "readdirp": { - "version": "2.1.0", - "from": "readdirp@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz" - }, - "readline2": { - "version": "0.1.1", - "from": "readline2@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", - "dependencies": { - "ansi-regex": { - "version": "1.1.1", - "from": "ansi-regex@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" - }, - "strip-ansi": { - "version": "2.0.1", - "from": "strip-ansi@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz" - } - } - }, - "redent": { - "version": "1.0.0", - "from": "redent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz" - }, - "regenerate": { - "version": "1.3.1", - "from": "regenerate@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.1.tgz" - }, - "regenerator-runtime": { - "version": "0.9.5", - "from": "regenerator-runtime@>=0.9.5 <0.10.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz" - }, - "regex-cache": { - "version": "0.4.3", - "from": "regex-cache@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" - }, - "regexpu-core": { - "version": "2.0.0", - "from": "regexpu-core@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.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" - }, - "repeat-element": { - "version": "1.1.2", - "from": "repeat-element@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.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" - }, - "repeating": { - "version": "1.1.3", - "from": "repeating@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz" - }, - "request": { - "version": "2.75.0", - "from": "request@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz" - }, - "request-progress": { - "version": "2.0.1", - "from": "request-progress@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz" - }, - "require-directory": { - "version": "2.1.1", - "from": "require-directory@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - }, - "require-main-filename": { - "version": "1.0.1", - "from": "require-main-filename@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz" - }, - "requires-port": { - "version": "1.0.0", - "from": "requires-port@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" - }, - "resolve": { - "version": "1.1.7", - "from": "resolve@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" - }, - "resolve-from": { - "version": "2.0.0", - "from": "resolve-from@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz" - }, - "resolve-pkg": { - "version": "0.1.0", - "from": "resolve-pkg@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz" - }, - "resp-modifier": { - "version": "6.0.2", - "from": "resp-modifier@6.0.2", - "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.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" - }, - "rimraf": { - "version": "2.5.4", - "from": "rimraf@>=2.2.8 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "dependencies": { - "glob": { - "version": "7.1.0", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.0.tgz" - } - } - }, - "ripemd160": { - "version": "0.2.0", - "from": "ripemd160@0.2.0", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz" - }, - "rx": { - "version": "4.1.0", - "from": "rx@4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz" - }, - "sauce-connect-launcher": { - "version": "0.13.0", - "from": "sauce-connect-launcher@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/sauce-connect-launcher/-/sauce-connect-launcher-0.13.0.tgz", - "dependencies": { - "async": { - "version": "1.4.0", - "from": "async@1.4.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.4.0.tgz" - }, - "rimraf": { - "version": "2.4.3", - "from": "rimraf@2.4.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz" - } - } - }, - "saucelabs": { - "version": "1.3.0", - "from": "saucelabs@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz" - }, - "select2": { - "version": "4.0.3", - "from": "select2@>=4.0.2 <5.0.0", - "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.3.tgz" - }, - "semver": { - "version": "5.3.0", - "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz" - }, - "send": { - "version": "0.14.1", - "from": "send@0.14.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.14.1.tgz", - "dependencies": { - "mime": { - "version": "1.3.4", - "from": "mime@1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" - } - } - }, - "serve-index": { - "version": "1.8.0", - "from": "serve-index@1.8.0", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.8.0.tgz" - }, - "serve-static": { - "version": "1.11.1", - "from": "serve-static@1.11.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.1.tgz" - }, - "server-destroy": { - "version": "1.0.1", - "from": "server-destroy@1.0.1", - "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz" - }, - "set-blocking": { - "version": "2.0.0", - "from": "set-blocking@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - }, - "set-immediate-shim": { - "version": "1.0.1", - "from": "set-immediate-shim@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz" - }, - "setprototypeof": { - "version": "1.0.1", - "from": "setprototypeof@1.0.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.1.tgz" - }, - "sha.js": { - "version": "2.2.6", - "from": "sha.js@2.2.6", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.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" - }, - "shelljs": { - "version": "0.3.0", - "from": "shelljs@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz" - }, - "signal-exit": { - "version": "3.0.1", - "from": "signal-exit@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.1.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" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "socket.io": { - "version": "1.4.8", - "from": "socket.io@1.4.8", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.4.8.tgz", - "dependencies": { - "component-emitter": { - "version": "1.2.0", - "from": "component-emitter@1.2.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.0.tgz" - }, - "socket.io-client": { - "version": "1.4.8", - "from": "socket.io-client@1.4.8", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.4.8.tgz" - } - } - }, - "socket.io-adapter": { - "version": "0.4.0", - "from": "socket.io-adapter@0.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.4.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "socket.io-parser": { - "version": "2.2.2", - "from": "socket.io-parser@2.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.2.tgz", - "dependencies": { - "debug": { - "version": "0.7.4", - "from": "debug@0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" - } - } - } - } - }, - "socket.io-client": { - "version": "0.9.17", - "from": "socket.io-client@>=0.9.17 <0.10.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-0.9.17.tgz" - }, - "socket.io-parser": { - "version": "2.2.6", - "from": "socket.io-parser@2.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.6.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "json3": { - "version": "3.3.2", - "from": "json3@3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz" - } - } - }, - "sockjs": { - "version": "0.3.17", - "from": "sockjs@>=0.3.15 <0.4.0", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.17.tgz" - }, - "sockjs-client": { - "version": "1.1.1", - "from": "sockjs-client@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.1.tgz", - "dependencies": { - "faye-websocket": { - "version": "0.11.0", - "from": "faye-websocket@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.0.tgz" - }, - "json3": { - "version": "3.3.2", - "from": "json3@>=3.3.2 <4.0.0", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz" - } - } - }, - "source-list-map": { - "version": "0.1.6", - "from": "source-list-map@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.6.tgz" - }, - "source-map": { - "version": "0.3.0", - "from": "source-map@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.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" - } - } - }, - "spdx-correct": { - "version": "1.0.2", - "from": "spdx-correct@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz" - }, - "spdx-expression-parse": { - "version": "1.0.3", - "from": "spdx-expression-parse@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.3.tgz" - }, - "spdx-license-ids": { - "version": "1.2.2", - "from": "spdx-license-ids@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz" - }, - "split2": { - "version": "1.1.1", - "from": "split2@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz" - }, - "sprintf-js": { - "version": "1.0.3", - "from": "sprintf-js@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - }, - "sshpk": { - "version": "1.10.0", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "statuses": { - "version": "1.3.0", - "from": "statuses@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz" - }, - "stream-browserify": { - "version": "1.0.0", - "from": "stream-browserify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.0.27-1 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "stream-cache": { - "version": "0.0.2", - "from": "stream-cache@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/stream-cache/-/stream-cache-0.0.2.tgz" - }, - "stream-shift": { - "version": "1.0.0", - "from": "stream-shift@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz" - }, - "stream-throttle": { - "version": "0.1.3", - "from": "stream-throttle@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz" - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "string-length": { - "version": "1.0.1", - "from": "string-length@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz" - }, - "string-width": { - "version": "1.0.2", - "from": "string-width@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "strip-bom": { - "version": "2.0.0", - "from": "strip-bom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" - }, - "strip-indent": { - "version": "1.0.1", - "from": "strip-indent@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz" - }, - "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "tapable": { - "version": "0.1.10", - "from": "tapable@>=0.1.8 <0.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz" - }, - "tar-stream": { - "version": "1.1.5", - "from": "tar-stream@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.1.5.tgz", - "dependencies": { - "bl": { - "version": "0.9.5", - "from": "bl@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.33 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "test-exclude": { - "version": "2.1.2", - "from": "test-exclude@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-2.1.2.tgz" - }, - "text-table": { - "version": "0.2.0", - "from": "text-table@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" - }, - "tfunk": { - "version": "3.0.2", - "from": "tfunk@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/tfunk/-/tfunk-3.0.2.tgz" - }, - "throttleit": { - "version": "1.0.0", - "from": "throttleit@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz" - }, - "through": { - "version": "2.3.8", - "from": "through@>=2.3.6 <3.0.0", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - }, - "through2": { - "version": "2.0.1", - "from": "through2@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz" - }, - "time-zone": { - "version": "0.1.0", - "from": "time-zone@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-0.1.0.tgz" - }, - "timers-browserify": { - "version": "1.4.2", - "from": "timers-browserify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz" - }, - "timezone-js": { - "version": "0.4.11", - "from": "leigh-johnson/timezone-js#0.4.13", - "resolved": "git://github.com/leigh-johnson/timezone-js.git#3b1de3f89106706483e79831312d3c325f95dd8a" - }, - "tiny-lr": { - "version": "0.2.1", - "from": "tiny-lr@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", - "dependencies": { - "qs": { - "version": "5.1.0", - "from": "qs@>=5.1.0 <5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz" - } - } - }, - "tinycolor": { - "version": "0.0.1", - "from": "tinycolor@>=0.0.0 <1.0.0", - "resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz" - }, - "tmp": { - "version": "0.0.28", - "from": "tmp@0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz" - }, - "to-array": { - "version": "0.1.4", - "from": "to-array@0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz" - }, - "to-fast-properties": { - "version": "1.0.2", - "from": "to-fast-properties@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz" - }, - "tough-cookie": { - "version": "2.3.1", - "from": "tough-cookie@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz" - }, - "trim-newlines": { - "version": "1.0.0", - "from": "trim-newlines@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz" - }, - "tty-browserify": { - "version": "0.0.0", - "from": "tty-browserify@0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz" - }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" - }, - "tweetnacl": { - "version": "0.13.3", - "from": "tweetnacl@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" - }, - "type-check": { - "version": "0.3.2", - "from": "type-check@>=0.3.2 <0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" - }, - "type-is": { - "version": "1.6.13", - "from": "type-is@>=1.6.10 <1.7.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz" - }, - "typedarray": { - "version": "0.0.6", - "from": "typedarray@>=0.0.5 <0.1.0", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" - }, - "ua-parser-js": { - "version": "0.7.10", - "from": "ua-parser-js@0.7.10", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.10.tgz" - }, - "uglify-js": { - "version": "1.2.5", - "from": "uglify-js@1.2.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.2.5.tgz" - }, - "uglify-to-browserify": { - "version": "1.0.2", - "from": "uglify-to-browserify@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz" - }, - "ultron": { - "version": "1.0.2", - "from": "ultron@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" - }, - "underscore": { - "version": "1.7.0", - "from": "underscore@>=1.7.0 <1.8.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz" - }, - "underscore.string": { - "version": "3.2.3", - "from": "underscore.string@>=3.2.3 <3.3.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz" - }, - "unpipe": { - "version": "1.0.0", - "from": "unpipe@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - }, - "url": { - "version": "0.10.3", - "from": "url@>=0.10.1 <0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "dependencies": { - "punycode": { - "version": "1.3.2", - "from": "punycode@1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" - } - } - }, - "url-parse": { - "version": "1.1.3", - "from": "url-parse@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.3.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" - }, - "useragent": { - "version": "2.1.9", - "from": "useragent@>=2.1.9 <3.0.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.1.9.tgz" - }, - "utf8": { - "version": "2.1.0", - "from": "utf8@2.1.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.0.tgz" - }, - "util": { - "version": "0.10.3", - "from": "util@>=0.10.3 <0.11.0", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "dependencies": { - "inherits": { - "version": "2.0.1", - "from": "inherits@2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "utils-merge": { - "version": "1.0.0", - "from": "utils-merge@1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" - }, - "uuid": { - "version": "2.0.3", - "from": "uuid@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz" - }, - "validate-npm-package-license": { - "version": "3.0.1", - "from": "validate-npm-package-license@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz" - }, - "vargs": { - "version": "0.1.0", - "from": "vargs@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/vargs/-/vargs-0.1.0.tgz" - }, - "vary": { - "version": "1.1.0", - "from": "vary@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.0.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "vm-browserify": { - "version": "0.0.4", - "from": "vm-browserify@0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz" - }, - "void-elements": { - "version": "2.0.1", - "from": "void-elements@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" - }, - "watchpack": { - "version": "0.2.9", - "from": "watchpack@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.9.tgz", - "dependencies": { - "async": { - "version": "0.9.2", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" - } - } - }, - "wd": { - "version": "0.3.12", - "from": "wd@>=0.3.4 <0.4.0", - "resolved": "https://registry.npmjs.org/wd/-/wd-0.3.12.tgz", - "dependencies": { - "asn1": { - "version": "0.1.11", - "from": "asn1@0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" - }, - "assert-plus": { - "version": "0.1.5", - "from": "assert-plus@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" - }, - "async": { - "version": "1.0.0", - "from": "async@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz" - }, - "aws-sign2": { - "version": "0.5.0", - "from": "aws-sign2@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" - }, - "bl": { - "version": "0.9.5", - "from": "bl@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz" - }, - "bluebird": { - "version": "2.11.0", - "from": "bluebird@>=2.9.30 <3.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz" - }, - "caseless": { - "version": "0.9.0", - "from": "caseless@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz" - }, - "combined-stream": { - "version": "0.0.7", - "from": "combined-stream@>=0.0.5 <0.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz" - }, - "delayed-stream": { - "version": "0.0.5", - "from": "delayed-stream@0.0.5", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" - }, - "form-data": { - "version": "0.2.0", - "from": "form-data@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", - "dependencies": { - "async": { - "version": "0.9.2", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" - } - } - }, - "har-validator": { - "version": "1.8.0", - "from": "har-validator@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz" - }, - "hawk": { - "version": "2.3.1", - "from": "hawk@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz" - }, - "http-signature": { - "version": "0.10.1", - "from": "http-signature@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "lodash": { - "version": "3.9.3", - "from": "lodash@>=3.9.3 <3.10.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.9.3.tgz" - }, - "mime-db": { - "version": "1.12.0", - "from": "mime-db@>=1.12.0 <1.13.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz" - }, - "mime-types": { - "version": "2.0.14", - "from": "mime-types@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz" - }, - "oauth-sign": { - "version": "0.6.0", - "from": "oauth-sign@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz" - }, - "qs": { - "version": "2.4.2", - "from": "qs@>=2.4.0 <2.5.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.26 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - }, - "request": { - "version": "2.55.0", - "from": "request@>=2.55.0 <2.56.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz" - }, - "underscore.string": { - "version": "3.0.3", - "from": "underscore.string@>=3.0.3 <3.1.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.0.3.tgz" - } - } - }, - "webpack-core": { - "version": "0.6.8", - "from": "webpack-core@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.8.tgz", - "dependencies": { - "source-map": { - "version": "0.4.4", - "from": "source-map@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" - } - } - }, - "webpack-dev-middleware": { - "version": "1.8.1", - "from": "webpack-dev-middleware@>=1.0.11 <2.0.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.8.1.tgz", - "dependencies": { - "mime": { - "version": "1.3.4", - "from": "mime@>=1.3.4 <2.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" - } - } - }, - "websocket-driver": { - "version": "0.6.5", - "from": "websocket-driver@>=0.5.1", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz" - }, - "websocket-extensions": { - "version": "0.1.1", - "from": "websocket-extensions@>=0.1.1", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz" - }, - "weinre": { - "version": "2.0.0-pre-I0Z7U9OV", - "from": "weinre@>=2.0.0-pre-I0Z7U9OV <3.0.0", - "resolved": "https://registry.npmjs.org/weinre/-/weinre-2.0.0-pre-I0Z7U9OV.tgz" - }, - "which": { - "version": "1.2.11", - "from": "which@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.11.tgz" - }, - "which-module": { - "version": "1.0.0", - "from": "which-module@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz" - }, - "window-size": { - "version": "0.1.0", - "from": "window-size@0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" - }, - "wordwrap": { - "version": "1.0.0", - "from": "wordwrap@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" - }, - "wrap-ansi": { - "version": "2.0.0", - "from": "wrap-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz" - }, - "wrappy": { - "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - }, - "ws": { - "version": "0.4.32", - "from": "ws@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-0.4.32.tgz", - "dependencies": { - "commander": { - "version": "2.1.0", - "from": "commander@>=2.1.0 <2.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz" - }, - "nan": { - "version": "1.0.0", - "from": "nan@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz" - } - } - }, - "xmlbuilder": { - "version": "8.2.2", - "from": "xmlbuilder@8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz" - }, - "xmlhttprequest": { - "version": "1.4.2", - "from": "xmlhttprequest@1.4.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.4.2.tgz" - }, - "xmlhttprequest-ssl": { - "version": "1.5.1", - "from": "xmlhttprequest-ssl@1.5.1", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.1.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.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" - }, - "yargs": { - "version": "3.10.0", - "from": "yargs@>=3.10.0 <3.11.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz" - }, - "yargs-parser": { - "version": "3.2.0", - "from": "yargs-parser@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz", - "dependencies": { - "camelcase": { - "version": "3.0.0", - "from": "camelcase@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz" - } - } - }, - "yauzl": { - "version": "2.4.1", - "from": "yauzl@2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz" - }, - "yeast": { - "version": "0.1.2", - "from": "yeast@0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz" - }, - "zeparser": { - "version": "0.0.5", - "from": "zeparser@0.0.5", - "resolved": "https://registry.npmjs.org/zeparser/-/zeparser-0.0.5.tgz" - }, - "zip-stream": { - "version": "0.5.2", - "from": "zip-stream@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-0.5.2.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "lodash": { - "version": "3.2.0", - "from": "lodash@>=3.2.0 <3.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.2.0.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.26 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - } - } - "version": "3.1.0" -} From e80c99e737053ff4b5cf42a2fc16fe2b505decd2 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 5 Oct 2016 10:35:55 -0700 Subject: [PATCH 41/48] removing status checks in socket.service --- awx/ui/client/src/shared/socket/socket.service.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index b2c98e7f01..dd638e73bc 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -24,9 +24,11 @@ export default self.socket.onopen = function () { $log.debug("Websocket connection opened."); $rootScope.socketPromise.resolve(); + self.checkStatus(); }; self.socket.onerror = function (error) { + self.checkStatus(); $log.debug('Websocket Error Logged: ' + error); //log errors }; @@ -35,10 +37,7 @@ export default }; self.socket.onmessage = this.onMessage; - setTimeout(function() { - self.checkStatus(); - $log.debug('Socket Status: ' + $rootScope.socketStatus); - }, 2000); + return self.socket; } else { From 1b8b4a68b70881dc95c52441a08a1fb622a5430d Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 5 Oct 2016 14:10:40 -0700 Subject: [PATCH 42/48] Resubscribing the UI to the last group it was on, whenever a disconnect happens that was no intentional --- .../client/src/shared/socket/socket.service.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index dd638e73bc..59419771af 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -7,6 +7,7 @@ import ReconnectingWebSocket from 'reconnectingwebsocket'; export default ['$rootScope', '$location', '$log','$state', function ($rootScope, $location, $log, $state) { + var needsResubscribing = false; return { init: function() { var self = this, @@ -25,6 +26,11 @@ export default $log.debug("Websocket connection opened."); $rootScope.socketPromise.resolve(); self.checkStatus(); + if(needsResubscribing){ + self.subscribe(self.getLast()); + needsResubscribing = false; + } + }; self.socket.onerror = function (error) { @@ -32,8 +38,15 @@ export default $log.debug('Websocket Error Logged: ' + error); //log errors }; - self.socket.onclose = function () { - $log.debug('Websocket Disconnected'); + self.socket.onconnecting = function (event) { + self.checkStatus(); + $log.debug('Websocket reconnecting'); + needsResubscribing = true; + }; + + self.socket.onclose = function (event) { + self.checkStatus(); + $log.debug(`Websocket disconnected`); }; self.socket.onmessage = this.onMessage; From 71c84a6383ea239fa97947eb7b0bf457758ca6e0 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 6 Oct 2016 09:58:51 -0400 Subject: [PATCH 43/48] convert scheduler to use websocket --- awx/main/scheduler/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 63bf006bbe..eeab8ab9f7 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -64,7 +64,7 @@ def spawn_workflow_graph_jobs(workflow_jobs): job.status = 'failed' job.job_explanation = "Workflow job could not start because it was not in the right state or required manual credentials" job.save(update_fields=['status', 'job_explanation']) - job.socketio_emit_status("failed") + job.websocket_emit_status("failed") # TODO: should we emit a status on the socket here similar to tasks.py tower_periodic_scheduler() ? #emit_websocket_notification('/socket.io/jobs', '', dict(id=)) @@ -78,7 +78,7 @@ def process_finished_workflow_jobs(workflow_jobs): # TODO: detect if wfj failed workflow_job.status = 'completed' workflow_job.save() - workflow_job.socketio_emit_status('completed') + workflow_job.websocket_emit_status('completed') def rebuild_graph(): """Regenerate the task graph by refreshing known tasks from Tower, purging @@ -130,8 +130,8 @@ def rebuild_graph(): 'Celery, so it has been marked as failed.', )) task.save() - task.socketio_emit_status("failed") - running_tasks.pop(task) + task.websocket_emit_status("failed") + running_tasks.pop(running_tasks.index(task)) logger.error("Task %s appears orphaned... marking as failed" % task) # Create and process dependencies for new tasks @@ -144,7 +144,7 @@ def rebuild_graph(): task.status = 'failed' task.job_explanation += 'Task failed to generate dependencies: {}'.format(e) task.save() - task.socketio_emit_status("failed") + task.websocket_emit_status("failed") continue logger.debug("New dependencies: %s" % str(task_dependencies)) for dep in task_dependencies: From 39f337d9333c33eb91a1278840e3c752e784e373 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 6 Oct 2016 16:16:27 -0400 Subject: [PATCH 44/48] added model for holding channel group information, updating asgi_amsqp req --- awx/main/consumers.py | 7 +----- awx/main/migrations/0039_v310_channelgroup.py | 22 +++++++++++++++++++ awx/main/models/__init__.py | 1 + awx/main/models/channels.py | 5 +++++ requirements/requirements.txt | 2 +- 5 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 awx/main/migrations/0039_v310_channelgroup.py create mode 100644 awx/main/models/channels.py diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 67fe0e428d..fb4bf55fd3 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -7,7 +7,6 @@ from channels.sessions import channel_session def discard_groups(message): if 'groups' in message.channel_session: for group in message.channel_session['groups']: - print("removing from group: {}".format(group)) Group(group).discard(message.reply_channel) @channel_session @@ -16,9 +15,7 @@ def ws_disconnect(message): @channel_session def ws_receive(message): - print(message) raw_data = message.content['text'] - print(raw_data) data = json.loads(raw_data) if 'groups' in data: @@ -29,16 +26,14 @@ def ws_receive(message): if type(v) is list: for oid in v: name = '{}-{}'.format(group_name, oid) - print("listening to group: {}".format(name)) current_groups.append(name) Group(name).add(message.reply_channel) else: - print("listening to group: {}".format(group_name)) current_groups.append(group_name) Group(group_name).add(message.reply_channel) message.channel_session['groups'] = current_groups def emit_channel_notification(group, payload): - print("sending message to group {}".format(group)) + payload = json.dumps(payload) Group(group).send({"text": json.dumps(payload)}) diff --git a/awx/main/migrations/0039_v310_channelgroup.py b/awx/main/migrations/0039_v310_channelgroup.py new file mode 100644 index 0000000000..a150f4cf29 --- /dev/null +++ b/awx/main/migrations/0039_v310_channelgroup.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0038_v310_workflow_rbac_prompts'), + ] + + operations = [ + migrations.CreateModel( + name='ChannelGroup', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('group', models.CharField(unique=True, max_length=200)), + ('channels', models.TextField()), + ], + ), + ] diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 161a59e65c..f70229cf72 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -22,6 +22,7 @@ from awx.main.models.notifications import * # noqa from awx.main.models.fact import * # noqa from awx.main.models.label import * # noqa from awx.main.models.workflow import * # noqa +from awx.main.models.channels import * # noqa # Monkeypatch Django serializer to ignore django-taggit fields (which break # the dumpdata command; see https://github.com/alex/django-taggit/issues/155). diff --git a/awx/main/models/channels.py b/awx/main/models/channels.py new file mode 100644 index 0000000000..cd34678638 --- /dev/null +++ b/awx/main/models/channels.py @@ -0,0 +1,5 @@ +from django.db import models + +class ChannelGroup(models.Model): + group = models.CharField(max_length=200, unique=True) + channels = models.TextField() diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5f0beba4b7..bcdded9433 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -9,7 +9,7 @@ billiard==3.3.0.16 boto==2.40.0 celery==3.1.23 channels==0.17.2 -asgi_amqp==0.2.1 +asgi_amqp==0.3 cliff==1.15.0 cmd2==0.6.8 d2to1==0.2.11 # TODO: Still needed? From 163ec843fd8e5ac23b0a61f000d6ae251b65f579 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 6 Oct 2016 16:32:35 -0400 Subject: [PATCH 45/48] update channels reqs --- requirements/requirements.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bcdded9433..c2effa3802 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -8,8 +8,6 @@ baron==0.6.2 billiard==3.3.0.16 boto==2.40.0 celery==3.1.23 -channels==0.17.2 -asgi_amqp==0.3 cliff==1.15.0 cmd2==0.6.8 d2to1==0.2.11 # TODO: Still needed? @@ -134,5 +132,6 @@ wheel==0.24.0 wrapt==1.10.6 wsgiref==0.1.2 xmltodict==0.9.2 -channels==0.17.1 -asgi_redis==0.14.0 +channels==0.17.2 +asgi_amqp==0.3 + From c6d5f751fb8c64dcd876224e2a747c2fbe729e52 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 6 Oct 2016 18:42:18 -0700 Subject: [PATCH 46/48] Feedback from PR specifically the scope that is used for attaching event listeners for the $emit/$braodcast/$on that is used for routing socket messages. Also removing some commented out code --- awx/ui/client/src/app.js | 5 -- awx/ui/client/src/controllers/Home.js | 2 +- awx/ui/client/src/controllers/Jobs.js | 12 +-- awx/ui/client/src/controllers/Projects.js | 6 +- .../manage/groups/groups-list.controller.js | 5 +- .../host-summary/host-summary.controller.js | 7 +- .../src/job-detail/job-detail.controller.js | 43 ++++------- .../list/job-templates-list.controller.js | 6 +- .../organizations-job-templates.controller.js | 5 +- .../organizations-projects.controller.js | 6 +- .../portal-mode-jobs.controller.js | 7 +- .../src/shared/socket/socket.service.js | 73 ++++++++++++++++--- .../src/shared/stateExtender.provider.js | 26 ++----- .../log/standard-out-log.controller.js | 9 +-- .../standard-out/standard-out.controller.js | 11 +-- .../tests/spec/socket/socket.service-test.js | 1 - 16 files changed, 104 insertions(+), 120 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index f3d665fafe..6df9567b34 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -802,7 +802,6 @@ var tower = angular.module('Tower', [ ConfigService.getConfig().then(function() { Timer.init().then(function(timer) { $rootScope.sessionTimer = timer; - // $rootScope.$emit('OpenSocket'); SocketService.init(); pendoService.issuePendoIdentity(); CheckLicense.test(); @@ -847,10 +846,6 @@ var tower = angular.module('Tower', [ // create a promise that will resolve when features are loaded $rootScope.featuresConfigured = $q.defer(); } - if (!$rootScope.socketPromise) { - // create a promise that resolves when the socket connection is open - $rootScope.socketPromise = $q.defer(); - } $rootScope.licenseMissing = true; //the authorization controller redirects to the home page automatcially if there is no last path defined. in order to override // this, set the last path to /portal for instances where portal is visited for the first time. diff --git a/awx/ui/client/src/controllers/Home.js b/awx/ui/client/src/controllers/Home.js index ff443923fc..a4999487f6 100644 --- a/awx/ui/client/src/controllers/Home.js +++ b/awx/ui/client/src/controllers/Home.js @@ -28,7 +28,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log var dataCount = 0; - $rootScope.$on('ws-jobs', function () { + $scope.$on('ws-jobs', function () { Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { diff --git a/awx/ui/client/src/controllers/Jobs.js b/awx/ui/client/src/controllers/Jobs.js index 8ed4ca9da0..07e84df3ee 100644 --- a/awx/ui/client/src/controllers/Jobs.js +++ b/awx/ui/client/src/controllers/Jobs.js @@ -65,7 +65,7 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa jobs_scope.viewJob = function (id) { $state.transitionTo('jobDetail', {id: id}); }; - + jobs_scope.showJobType = true; LoadJobsScope({ parent_scope: $scope, @@ -110,17 +110,11 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa } }; - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function() { + $scope.$on('ws-jobs', function() { $scope.refreshJobs(); }); - if ($rootScope.removeScheduleStatusChange) { - $rootScope.removeScheduleStatusChange(); - } - $rootScope.removeScheduleStatusChange = $rootScope.$on('ws-schedules', function() { + $scope.$on('ws-schedules', function() { if (api_complete) { scheduled_scope.search('schedule'); } diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 3ec77ecb69..0a7b1dcd64 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -86,11 +86,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams, } }); - // Handle project update status changes - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { + $scope.$on(`ws-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js index fc7326f6d0..f9a20a8830 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-list.controller.js @@ -101,10 +101,7 @@ })); }; - if ($rootScope.inventoryManageStatus) { - $rootScope.inventoryManageStatus(); - } - $rootScope.inventoryManageStatus = $rootScope.$on(`ws-jobs`, function(e, data){ + $scope.$on(`ws-jobs`, function(e, data){ var group = Find({ list: $scope.groups, key: 'id', val: data.group_id }); if(data.status === 'failed' || data.status === 'successful'){ $state.reload(); diff --git a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js index c49eeb3d86..4f3e1be88d 100644 --- a/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js +++ b/awx/ui/client/src/job-detail/host-summary/host-summary.controller.js @@ -53,17 +53,14 @@ } // emitted by the API in the same function used to persist host summary data // JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py - $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobs-summary', function(e, data) { + $scope.$on('ws-jobs-summary', function(e, data) { // discard socket msgs we don't care about in this context if (parseInt($stateParams.id) === data.unified_job_id){ init(); } }); - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function(e, data) { + $scope.$on('ws-jobs', function(e, data) { if (parseInt($stateParams.id) === data.unified_job_id){ $scope.status = data.status; } 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 d7bd9c3457..81587e325d 100644 --- a/awx/ui/client/src/job-detail/job-detail.controller.js +++ b/awx/ui/client/src/job-detail/job-detail.controller.js @@ -197,30 +197,22 @@ export default "

Changed

\n" + "

Unreachable

\n" + "

Failed

\n"; - function openSocket() { - if ($rootScope.removeJobEventChange) { - $rootScope.removeJobEventChange(); - } - $rootScope.removeJobEventChange = $rootScope.$on(`ws-job_events-${job_id}`, function(e, data) { - // update elapsed time on each event received - scope.job_status.elapsed = GetElapsed({ - start: scope.job.created, - end: Date.now() - }); - if (api_complete && data.id > lastEventId) { - scope.waiting = false; - data.event = data.event_name; - DigestEvent({ scope: scope, event: data }); - } - UpdateDOM({ scope: scope }); - }); - } - openSocket(); - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { + scope.$on(`ws-job_events-${job_id}`, function(e, data) { + // update elapsed time on each event received + scope.job_status.elapsed = GetElapsed({ + start: scope.job.created, + end: Date.now() + }); + if (api_complete && data.id > lastEventId) { + scope.waiting = false; + data.event = data.event_name; + DigestEvent({ scope: scope, event: data }); + } + UpdateDOM({ scope: scope }); + }); + + scope.$on(`ws-jobs`, function(e, data) { // if we receive a status change event for the current job indicating the job // is finished, stop event queue processing and reload if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { @@ -234,10 +226,7 @@ export default } }); - if ($rootScope.removeJobSummaryComplete) { - $rootScope.removeJobSummaryComplete(); - } - $rootScope.removeJobSummaryComplete = $rootScope.$on('ws-jobs-summary', function() { + scope.$on('ws-jobs-summary', function() { // the job host summary should now be available from the API $log.debug('Trigging reload of job_host_summaries'); scope.$emit('InitialLoadComplete'); diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js index ed5372e908..5273f1c0b4 100644 --- a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js +++ b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js @@ -37,11 +37,7 @@ export default view.inject(list, { mode: mode, scope: $scope }); $rootScope.flashMessage = null; - - if ($rootScope.JobStatusChange) { - $rootScope.JobStatusChange(); - } - $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function () { + $scope.$on(`ws-jobs`, function () { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js index ae1000a5c8..c9669462b5 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js @@ -21,10 +21,7 @@ export default ['$scope', '$rootScope', '$location', '$log', generator = GenerateList, orgBase = GetBasePath('organizations'); - if ($rootScope.JobStatusChange) { - $rootScope.JobStatusChange(); - } - $rootScope.JobStatusChange = $rootScope.$on(`ws-jobs`, function () { + $scope.$on(`ws-jobs`, function () { $scope.search(list.iterator); }); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js index 73d6156862..5c3fde00fc 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js @@ -86,11 +86,7 @@ export default ['$scope', '$rootScope', '$location', '$log', } }); - // Handle project update status changes - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { + $scope.$on(`ws-jobs`, function(e, data) { var project; $log.debug(data); if ($scope.projects) { diff --git a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js index 06c2339633..81d899e85f 100644 --- a/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js +++ b/awx/ui/client/src/portal-mode/portal-mode-jobs.controller.js @@ -13,12 +13,9 @@ export function PortalModeJobsController($scope, $rootScope, GetBasePath, Genera defaultUrl = GetBasePath('jobs') + '?created_by=' + $rootScope.current_user.id, pageSize = 12; - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - $rootScope.removeJobStatusChange = $rootScope.$on('ws-jobs', function() { + $scope.$on('ws-jobs', function() { $scope.search('job'); - }); + }); $scope.iterator = list.iterator; $scope.activeFilter = 'user'; diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js index 59419771af..6a992d4e5f 100644 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ b/awx/ui/client/src/shared/socket/socket.service.js @@ -5,9 +5,10 @@ *************************************************/ import ReconnectingWebSocket from 'reconnectingwebsocket'; export default -['$rootScope', '$location', '$log','$state', - function ($rootScope, $location, $log, $state) { - var needsResubscribing = false; +['$rootScope', '$location', '$log','$state', '$q', + function ($rootScope, $location, $log, $state, $q) { + var needsResubscribing = false, + socketPromise = $q.defer(); return { init: function() { var self = this, @@ -24,7 +25,7 @@ export default self.socket.onopen = function () { $log.debug("Websocket connection opened."); - $rootScope.socketPromise.resolve(); + socketPromise.resolve(); self.checkStatus(); if(needsResubscribing){ self.subscribe(self.getLast()); @@ -60,22 +61,40 @@ export default } }, onMessage: function(e){ + // Function called when messages are received on by the UI from + // the API over the websocket. This will route each message to + // the appropriate controller for the current $state. + e.data = e.data.replace(/\\/g, ''); + e.data = e.data.substr(0, e.data.length-1); + e.data = e.data.substr(1); $log.debug('Received From Server: ' + e.data); + var data = JSON.parse(e.data), str = ""; if(data.group_name==="jobs" && !('status' in data)){ // we know that this must have been a - // summary complete message b/c status is missing + // summary complete message b/c status is missing. + // A an object w/ group_name === "jobs" AND a 'status' key + // means it was for the event: status_changed. $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$emit('ws-jobs-summary', data); + $rootScope.$broadcast('ws-jobs-summary', data); return; } else if(data.group_name==="job_events"){ - str = `ws-${data.group_name}-${data.job}`; + // The naming scheme is "ws" then a + // dash (-) and the group_name, then the job ID + // ex: 'ws-jobs-' + str = `ws-${data.group_name}-${data.job}` } else if(data.group_name==="ad_hoc_command_events"){ + // The naming scheme is "ws" then a + // dash (-) and the group_name, then the job ID + // ex: 'ws-jobs-' str = `ws-${data.group_name}-${data.ad_hoc_command}`; } else if(data.group_name==="control"){ + // As of Tower v. 3.1.0, there is only 1 "control" + // message, which is for expiring the session if the + // session limit is breached. $log.debug(data.reason); $rootScope.sessionTimer.expireSession('session_limit'); $state.go('signOut'); @@ -86,7 +105,7 @@ export default // ex: 'ws-jobs' str = `ws-${data.group_name}`; } - $rootScope.$emit(str, data); + $rootScope.$broadcast(str, data); }, disconnect: function(){ if(this.socket){ @@ -94,10 +113,18 @@ export default } }, subscribe: function(state){ + // Subscribe is used to tell the API that the UI wants to + // listen for specific messages. A subscription object could + // look like {"groups":{"jobs": ["status_changed", "summary"]}. + // This is used by all socket-enabled $states this.emit(JSON.stringify(state.socket)); this.setLast(state); }, unsubscribe: function(state){ + // Unsubscribing tells the API that the user is no longer on + // on a socket-enabled page, and sends an empty groups object + // to the API: {"groups": {}}. + // This is used for all pages that are socket-disabled if(this.requiresNewSubscribe(state)){ this.emit(JSON.stringify(state.socket)); } @@ -110,6 +137,9 @@ export default return this.last; }, requiresNewSubscribe(state){ + // This function is used for unsubscribing. If the last $state + // required an "unsubscribe", then we don't need to unsubscribe + // again, b/c the UI is already unsubscribed from all groups if (this.getLast() !== undefined){ if( _.isEmpty(state.socket.groups) && _.isEmpty(this.getLast().socket.groups)){ return false; @@ -123,6 +153,7 @@ export default } }, checkStatus: function() { + // Function for changing the socket indicator icon in the nav bar var self = this; if(self){ if(self.socket){ @@ -144,9 +175,11 @@ export default }, emit: function(data, callback) { + // Function used for sending objects to the API over the + // websocket. var self = this; $log.debug('Sent to Websocket Server: ' + data); - $rootScope.socketPromise.promise.then(function(){ + socketPromise.promise.then(function(){ self.socket.send(data, function () { var args = arguments; self.scope.$apply(function () { @@ -156,6 +189,28 @@ export default }); }); }); + }, + addStateResolve: function(state, id){ + // This function is used for add a state resolve to all states, + // socket-enabled AND socket-disabled, and whether the $state + // requires a subscribe or an unsubscribe + self = this; + socketPromise.promise.then(function(){ + if(!state.socket){ + state.socket = {groups: {}}; + self.unsubscribe(state); + } + else{ + if(state.socket.groups.hasOwnProperty( "job_events")){ + state.socket.groups.job_events = [id]; + } + if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ + state.socket.groups.ad_hoc_command_events = [id]; + } + self.subscribe(state); + } + return true; + }); } }; }]; diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js index 66f01e0051..9c5bf0bfae 100644 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ b/awx/ui/client/src/shared/stateExtender.provider.js @@ -2,29 +2,17 @@ export default function($stateProvider) { this.$get = function() { return { addSocket: function(state){ + // The login route has a 'null' socket because it should + // neither subscribe or unsubscribe if(state.socket!==null){ if(!state.resolve){ state.resolve = {}; } - state.resolve.socket = ['SocketService', '$rootScope', '$stateParams', - function(SocketService, $rootScope, $stateParams) { - $rootScope.socketPromise.promise.then(function(){ - if(!state.socket){ - state.socket = {groups: {}}; - SocketService.unsubscribe(state); - } - else{ - if(state.socket.groups.hasOwnProperty( "job_events")){ - state.socket.groups.job_events = [$stateParams.id]; - } - if(state.socket.groups.hasOwnProperty( "ad_hoc_command_events")){ - state.socket.groups.ad_hoc_command_events = [$stateParams.id]; - } - SocketService.subscribe(state); - } - return true; - }); - }]; + state.resolve.socket = ['SocketService', '$stateParams', + function(SocketService, $stateParams) { + SocketService.addStateResolve(state, $stateParams.id); + } + ]; } }, diff --git a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js index fe494d5603..45564fffc6 100644 --- a/awx/ui/client/src/standard-out/log/standard-out-log.controller.js +++ b/awx/ui/client/src/standard-out/log/standard-out-log.controller.js @@ -22,7 +22,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce function openSockets() { if ($state.current.name === 'jobDetail') { $log.debug("socket watching on job_events-" + job_id); - $rootScope.$on(`ws-job_events-${job_id}`, function() { + $scope.$on(`ws-job_events-${job_id}`, function() { $log.debug("socket fired on job_events-" + job_id); if (api_complete) { event_queue++; @@ -31,7 +31,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce } if ($state.current.name === 'adHocJobStdout') { $log.debug("socket watching on ad_hoc_command_events-" + job_id); - $rootScope.$on(`ws-ad_hoc_command_events-${job_id}`, function() { + $scope.$on(`ws-ad_hoc_command_events-${job_id}`, function() { $log.debug("socket fired on ad_hoc_command_events-" + job_id); if (api_complete) { event_queue++; @@ -179,10 +179,7 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce // We watch for job status changes here. If the job completes we want to clear out the // stdout interval and kill the live_event_processing flag. - if ($scope.removeJobStatusChange) { - $scope.removeJobStatusChange(); - } - $scope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { + $scope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index 689052e277..663d165cf3 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -25,10 +25,7 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, // Listen for job status updates that may come across via sockets. We need to check the payload // to see whethere the updated job is the one that we're currently looking at. - if ($scope.removeJobStatusChange) { - $scope.removeJobStatusChange(); - } - $scope.removeJobStatusChange = $rootScope.$on(`ws-jobs`, function(e, data) { + $scope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { $scope.job.status = data.status; } @@ -39,12 +36,6 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, } }); - // Unbind $rootScope socket event binding(s) so that they don't get triggered - // in another instance of this controller - $scope.$on('$destroy', function() { - $scope.removeJobStatusChange(); - }); - // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON $scope.parseType = 'yaml'; diff --git a/awx/ui/tests/spec/socket/socket.service-test.js b/awx/ui/tests/spec/socket/socket.service-test.js index c2f4d1c0b4..9310adfc92 100644 --- a/awx/ui/tests/spec/socket/socket.service-test.js +++ b/awx/ui/tests/spec/socket/socket.service-test.js @@ -22,7 +22,6 @@ describe('Service: SocketService', () => { it('should send to ws-jobs-summary', function(){ event = {data : {group_name: "jobs"}}; event.data = JSON.stringify(event.data); - console.log(event); SocketService.onMessage(event); expect(rootScope.$emit).toHaveBeenCalledWith('ws-jobs-summary', event.data); }); From 3a1b4a10c812f4c9a08797bd692acf9bed907089 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 6 Oct 2016 20:15:15 -0700 Subject: [PATCH 47/48] Turning debug_mode to false --- awx/ui/client/src/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/config.js b/awx/ui/client/src/config.js index 0aa7dbaf17..55956732a6 100644 --- a/awx/ui/client/src/config.js +++ b/awx/ui/client/src/config.js @@ -28,7 +28,7 @@ // custom_login_info: "example notice" // have a notice displayed in the login modal for users. note that, as a security measure, custom html is not supported and will be escaped. tooltip_delay: { show: 500, hide: 100 }, // Default number of milliseconds to delay displaying/hiding tooltips - debug_mode: true, // Enable console logging messages + debug_mode: false, // Enable console logging messages password_length: 8, // Minimum user password length. Set to 0 to not set a limit password_hasLowercase: true, // require a lowercase letter in the password From 48d49b41cb3a49a5bd8cde68b9f1587cbc75e2fe Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 7 Oct 2016 09:46:57 -0400 Subject: [PATCH 48/48] revert to old rabbitmq --- tools/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index a930f1569d..2e5b18460d 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -30,7 +30,7 @@ services: memcached: image: memcached:alpine rabbitmq: - image: gcr.io/ansible-tower-engineering/rabbitmq:latest + image: rabbitmq:3-management ports: - "15672:15672"