mirror of
https://github.com/ansible/awx.git
synced 2026-04-11 04:59:22 -02:30
Merge pull request #3657 from wwitzel3/jtabor-sockets
Switch to Django Channels
This commit is contained in:
1
Makefile
1
Makefile
@@ -364,7 +364,6 @@ server_noattach:
|
|||||||
tmux new-window 'exec make receiver'
|
tmux new-window 'exec make receiver'
|
||||||
tmux select-window -t tower:1
|
tmux select-window -t tower:1
|
||||||
tmux rename-window 'Extra Services'
|
tmux rename-window 'Extra Services'
|
||||||
tmux split-window -v 'exec make socketservice'
|
|
||||||
tmux split-window -h 'exec make factcacher'
|
tmux split-window -h 'exec make factcacher'
|
||||||
|
|
||||||
server: server_noattach
|
server: server_noattach
|
||||||
|
|||||||
1
Procfile
1
Procfile
@@ -1,6 +1,5 @@
|
|||||||
runserver: make runserver
|
runserver: make runserver
|
||||||
celeryd: make celeryd
|
celeryd: make celeryd
|
||||||
receiver: make receiver
|
receiver: make receiver
|
||||||
socketservice: make socketservice
|
|
||||||
factcacher: make factcacher
|
factcacher: make factcacher
|
||||||
flower: make flower
|
flower: make flower
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ from awx.api.permissions import * # noqa
|
|||||||
from awx.api.renderers import * # noqa
|
from awx.api.renderers import * # noqa
|
||||||
from awx.api.serializers import * # noqa
|
from awx.api.serializers import * # noqa
|
||||||
from awx.api.metadata import RoleMetadata
|
from awx.api.metadata import RoleMetadata
|
||||||
from awx.main.utils import emit_websocket_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.views')
|
logger = logging.getLogger('awx.api.views')
|
||||||
|
|
||||||
@@ -532,11 +532,9 @@ class AuthTokenView(APIView):
|
|||||||
# Mark them as invalid and inform the user
|
# Mark them as invalid and inform the user
|
||||||
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user'])
|
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user'])
|
||||||
for t in invalid_tokens:
|
for t in invalid_tokens:
|
||||||
# TODO: send socket notification
|
emit_channel_notification('control-limit_reached', dict(group_name='control',
|
||||||
emit_websocket_notification('/socket.io/control',
|
reason=force_text(AuthToken.reason_long('limit_reached')),
|
||||||
'limit_reached',
|
token_key=t.key))
|
||||||
dict(reason=force_text(AuthToken.reason_long('limit_reached'))),
|
|
||||||
token_key=t.key)
|
|
||||||
t.invalidate(reason='limit_reached')
|
t.invalidate(reason='limit_reached')
|
||||||
|
|
||||||
# Note: This header is normally added in the middleware whenever an
|
# Note: This header is normally added in the middleware whenever an
|
||||||
@@ -2768,6 +2766,7 @@ class WorkflowJobTemplateJobsList(SubListAPIView):
|
|||||||
relationship = 'jobs'
|
relationship = 'jobs'
|
||||||
parent_key = 'workflow_job_template'
|
parent_key = 'workflow_job_template'
|
||||||
|
|
||||||
|
# TODO:
|
||||||
class WorkflowJobList(ListCreateAPIView):
|
class WorkflowJobList(ListCreateAPIView):
|
||||||
|
|
||||||
model = WorkflowJob
|
model = WorkflowJob
|
||||||
@@ -3174,21 +3173,8 @@ class JobJobTasksList(BaseJobEventsList):
|
|||||||
return ({'detail': 'Parent event not found.'}, -1, status.HTTP_404_NOT_FOUND)
|
return ({'detail': 'Parent event not found.'}, -1, status.HTTP_404_NOT_FOUND)
|
||||||
parent_task = parent_task[0]
|
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')
|
STARTING_EVENTS = ('playbook_on_task_start', 'playbook_on_setup')
|
||||||
|
queryset = JobEvent.get_startevent_queryset(parent_task, STARTING_EVENTS)
|
||||||
# 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'))
|
|
||||||
|
|
||||||
# The data above will come back in a list, but we are going to
|
# 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
|
# want to access it based on the parent id, so map it into a
|
||||||
|
|||||||
37
awx/asgi.py
Normal file
37
awx/asgi.py
Normal file
@@ -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()
|
||||||
39
awx/main/consumers.py
Normal file
39
awx/main/consumers.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from channels import Group
|
||||||
|
from channels.sessions import channel_session
|
||||||
|
|
||||||
|
|
||||||
|
def discard_groups(message):
|
||||||
|
if 'groups' in message.channel_session:
|
||||||
|
for group in message.channel_session['groups']:
|
||||||
|
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():
|
||||||
|
if type(v) is list:
|
||||||
|
for oid in v:
|
||||||
|
name = '{}-{}'.format(group_name, oid)
|
||||||
|
current_groups.append(name)
|
||||||
|
Group(name).add(message.reply_channel)
|
||||||
|
else:
|
||||||
|
current_groups.append(group_name)
|
||||||
|
Group(group_name).add(message.reply_channel)
|
||||||
|
message.channel_session['groups'] = current_groups
|
||||||
|
|
||||||
|
|
||||||
|
def emit_channel_notification(group, payload):
|
||||||
|
payload = json.dumps(payload)
|
||||||
|
Group(group).send({"text": json.dumps(payload)})
|
||||||
22
awx/main/migrations/0039_v310_channelgroup.py
Normal file
22
awx/main/migrations/0039_v310_channelgroup.py
Normal file
@@ -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()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -22,6 +22,7 @@ from awx.main.models.notifications import * # noqa
|
|||||||
from awx.main.models.fact import * # noqa
|
from awx.main.models.fact import * # noqa
|
||||||
from awx.main.models.label import * # noqa
|
from awx.main.models.label import * # noqa
|
||||||
from awx.main.models.workflow 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
|
# Monkeypatch Django serializer to ignore django-taggit fields (which break
|
||||||
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
|
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
|
||||||
|
|||||||
5
awx/main/models/channels.py
Normal file
5
awx/main/models/channels.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class ChannelGroup(models.Model):
|
||||||
|
group = models.CharField(max_length=200, unique=True)
|
||||||
|
channels = models.TextField()
|
||||||
@@ -1222,7 +1222,7 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin):
|
|||||||
from awx.main.tasks import RunInventoryUpdate
|
from awx.main.tasks import RunInventoryUpdate
|
||||||
return RunInventoryUpdate
|
return RunInventoryUpdate
|
||||||
|
|
||||||
def socketio_emit_data(self):
|
def websocket_emit_data(self):
|
||||||
if self.inventory_source.group is not None:
|
if self.inventory_source.group is not None:
|
||||||
return dict(group_id=self.inventory_source.group.id)
|
return dict(group_id=self.inventory_source.group.id)
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -29,12 +29,13 @@ from awx.main.models.notifications import (
|
|||||||
JobNotificationMixin,
|
JobNotificationMixin,
|
||||||
)
|
)
|
||||||
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
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.redact import PlainTextCleaner
|
||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.models.mixins import ResourceMixin
|
from awx.main.models.mixins import ResourceMixin
|
||||||
from awx.main.models.base import PERM_INVENTORY_SCAN
|
from awx.main.models.base import PERM_INVENTORY_SCAN
|
||||||
|
|
||||||
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.jobs')
|
logger = logging.getLogger('awx.main.models.jobs')
|
||||||
|
|
||||||
@@ -1270,11 +1271,10 @@ class JobEvent(CreatedModifiedModel):
|
|||||||
if update_fields:
|
if update_fields:
|
||||||
host_summary.save(update_fields=update_fields)
|
host_summary.save(update_fields=update_fields)
|
||||||
job.inventory.update_computed_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(group_name='jobs', unified_job_id=job.id))
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@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.
|
We need to pull information about each start event.
|
||||||
|
|
||||||
@@ -1380,7 +1380,7 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):
|
|||||||
from awx.main.tasks import RunSystemJob
|
from awx.main.tasks import RunSystemJob
|
||||||
return RunSystemJob
|
return RunSystemJob
|
||||||
|
|
||||||
def socketio_emit_data(self):
|
def websocket_emit_data(self):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@@ -1421,4 +1421,3 @@ class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):
|
|||||||
|
|
||||||
def get_notification_friendly_name(self):
|
def get_notification_friendly_name(self):
|
||||||
return "System Job"
|
return "System Job"
|
||||||
|
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def socketio_emit_data(self):
|
def websocket_emit_data(self):
|
||||||
return dict(project_id=self.project.id)
|
return dict(project_id=self.project.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ from jsonfield import JSONField
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import * # noqa
|
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
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.schedule')
|
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())
|
self.dtend = make_aware(datetime.datetime.strptime(until_date, "%Y%m%dT%H%M%SZ"), get_default_timezone())
|
||||||
if 'count' in self.rrule.lower():
|
if 'count' in self.rrule.lower():
|
||||||
self.dtend = future_rs[-1]
|
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, group_name='schedules'))
|
||||||
with ignore_inventory_computed_fields():
|
with ignore_inventory_computed_fields():
|
||||||
self.unified_job_template.update_computed_fields()
|
self.unified_job_template.update_computed_fields()
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ from djcelery.models import TaskMeta
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.schedules import Schedule
|
from awx.main.models.schedules import Schedule
|
||||||
from awx.main.utils import decrypt_field, emit_websocket_notification, _inventory_updates
|
from awx.main.utils import decrypt_field, _inventory_updates
|
||||||
from awx.main.redact import UriCleaner, REPLACE_STR
|
from awx.main.redact import UriCleaner
|
||||||
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
||||||
|
|
||||||
@@ -774,14 +775,15 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
''' Given another task object determine if this task would be blocked by it '''
|
''' Given another task object determine if this task would be blocked by it '''
|
||||||
raise NotImplementedError # Implement in subclass.
|
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 extra data that should be included when submitting data to the browser over the websocket connection '''
|
||||||
return {}
|
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 = dict(unified_job_id=self.id, status=status)
|
||||||
status_data.update(self.socketio_emit_data())
|
status_data.update(self.websocket_emit_data())
|
||||||
emit_websocket_notification('/socket.io/jobs', 'status_changed', status_data)
|
status_data['group_name'] = 'jobs'
|
||||||
|
emit_channel_notification('jobs-status_changed', status_data)
|
||||||
|
|
||||||
def generate_dependencies(self, active_tasks):
|
def generate_dependencies(self, active_tasks):
|
||||||
''' Generate any tasks that the current task might be dependent on given a list of active
|
''' Generate any tasks that the current task might be dependent on given a list of active
|
||||||
@@ -859,7 +861,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
|
|
||||||
# Save the pending status, and inform the SocketIO listener.
|
# Save the pending status, and inform the SocketIO listener.
|
||||||
self.update_fields(start_args=json.dumps(kwargs), status='pending')
|
self.update_fields(start_args=json.dumps(kwargs), status='pending')
|
||||||
self.socketio_emit_status("pending")
|
self.websocket_emit_status("pending")
|
||||||
|
|
||||||
from awx.main.scheduler.tasks import run_job_launch
|
from awx.main.scheduler.tasks import run_job_launch
|
||||||
run_job_launch.delay(self.id)
|
run_job_launch.delay(self.id)
|
||||||
@@ -912,7 +914,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
instance.job_explanation = 'Forced cancel'
|
instance.job_explanation = 'Forced cancel'
|
||||||
update_fields.append('job_explanation')
|
update_fields.append('job_explanation')
|
||||||
instance.save(update_fields=update_fields)
|
instance.save(update_fields=update_fields)
|
||||||
self.socketio_emit_status("canceled")
|
self.websocket_emit_status("canceled")
|
||||||
except: # FIXME: Log this exception!
|
except: # FIXME: Log this exception!
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
raise
|
raise
|
||||||
@@ -926,8 +928,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
self.status = 'canceled'
|
self.status = 'canceled'
|
||||||
cancel_fields.append('status')
|
cancel_fields.append('status')
|
||||||
self.save(update_fields=cancel_fields)
|
self.save(update_fields=cancel_fields)
|
||||||
self.socketio_emit_status("canceled")
|
self.websocket_emit_status("canceled")
|
||||||
if settings.BROKER_URL.startswith('amqp://'):
|
if settings.BROKER_URL.startswith('amqp://'):
|
||||||
self._force_cancel()
|
self._force_cancel()
|
||||||
return self.cancel_flag
|
return self.cancel_flag
|
||||||
|
|
||||||
|
|||||||
7
awx/main/routing.py
Normal file
7
awx/main/routing.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from channels.routing import route
|
||||||
|
|
||||||
|
|
||||||
|
channel_routing = [
|
||||||
|
route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'),
|
||||||
|
route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'),
|
||||||
|
]
|
||||||
@@ -62,7 +62,7 @@ def spawn_workflow_graph_jobs(workflow_jobs):
|
|||||||
job.status = 'failed'
|
job.status = 'failed'
|
||||||
job.job_explanation = "Workflow job could not start because it was not in the right state or required manual credentials"
|
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.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() ?
|
# 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=))
|
#emit_websocket_notification('/socket.io/jobs', '', dict(id=))
|
||||||
@@ -76,7 +76,7 @@ def process_finished_workflow_jobs(workflow_jobs):
|
|||||||
# TODO: detect if wfj failed
|
# TODO: detect if wfj failed
|
||||||
workflow_job.status = 'completed'
|
workflow_job.status = 'completed'
|
||||||
workflow_job.save()
|
workflow_job.save()
|
||||||
workflow_job.socketio_emit_status('completed')
|
workflow_job.websocket_emit_status('completed')
|
||||||
|
|
||||||
def rebuild_graph():
|
def rebuild_graph():
|
||||||
"""Regenerate the task graph by refreshing known tasks from Tower, purging
|
"""Regenerate the task graph by refreshing known tasks from Tower, purging
|
||||||
@@ -128,8 +128,8 @@ def rebuild_graph():
|
|||||||
'Celery, so it has been marked as failed.',
|
'Celery, so it has been marked as failed.',
|
||||||
))
|
))
|
||||||
task.save()
|
task.save()
|
||||||
task.socketio_emit_status("failed")
|
task.websocket_emit_status("failed")
|
||||||
running_tasks.pop(task)
|
running_tasks.pop(running_tasks.index(task))
|
||||||
logger.error("Task %s appears orphaned... marking as failed" % task)
|
logger.error("Task %s appears orphaned... marking as failed" % task)
|
||||||
|
|
||||||
# Create and process dependencies for new tasks
|
# Create and process dependencies for new tasks
|
||||||
@@ -142,7 +142,7 @@ def rebuild_graph():
|
|||||||
task.status = 'failed'
|
task.status = 'failed'
|
||||||
task.job_explanation += 'Task failed to generate dependencies: {}'.format(e)
|
task.job_explanation += 'Task failed to generate dependencies: {}'.format(e)
|
||||||
task.save()
|
task.save()
|
||||||
task.socketio_emit_status("failed")
|
task.websocket_emit_status("failed")
|
||||||
continue
|
continue
|
||||||
logger.debug("New dependencies: %s" % str(task_dependencies))
|
logger.debug("New dependencies: %s" % str(task_dependencies))
|
||||||
for dep in task_dependencies:
|
for dep in task_dependencies:
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ from crum.signals import current_user_getter
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.api.serializers 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.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates
|
||||||
from awx.main.tasks import update_inventory_computed_fields
|
from awx.main.tasks import update_inventory_computed_fields
|
||||||
|
|
||||||
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
__all__ = []
|
__all__ = []
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.signals')
|
logger = logging.getLogger('awx.main.signals')
|
||||||
@@ -33,13 +35,15 @@ logger = logging.getLogger('awx.main.signals')
|
|||||||
def emit_job_event_detail(sender, **kwargs):
|
def emit_job_event_detail(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
created = kwargs['created']
|
created = kwargs['created']
|
||||||
|
print("before created job_event_detail")
|
||||||
if created:
|
if created:
|
||||||
event_serialized = JobEventSerializer(instance).data
|
event_serialized = JobEventSerializer(instance).data
|
||||||
event_serialized['id'] = instance.id
|
event_serialized['id'] = instance.id
|
||||||
event_serialized["created"] = event_serialized["created"].isoformat()
|
event_serialized["created"] = event_serialized["created"].isoformat()
|
||||||
event_serialized["modified"] = event_serialized["modified"].isoformat()
|
event_serialized["modified"] = event_serialized["modified"].isoformat()
|
||||||
event_serialized["event_name"] = instance.event
|
event_serialized["event_name"] = instance.event
|
||||||
emit_websocket_notification('/socket.io/job_events', 'job_events-' + str(instance.job.id), event_serialized)
|
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):
|
def emit_ad_hoc_command_event_detail(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
@@ -50,7 +54,8 @@ def emit_ad_hoc_command_event_detail(sender, **kwargs):
|
|||||||
event_serialized["created"] = event_serialized["created"].isoformat()
|
event_serialized["created"] = event_serialized["created"].isoformat()
|
||||||
event_serialized["modified"] = event_serialized["modified"].isoformat()
|
event_serialized["modified"] = event_serialized["modified"].isoformat()
|
||||||
event_serialized["event_name"] = instance.event
|
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)
|
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):
|
def emit_update_inventory_computed_fields(sender, **kwargs):
|
||||||
logger.debug("In update inventory computed fields")
|
logger.debug("In update inventory computed fields")
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ from awx.main.models import * # noqa
|
|||||||
from awx.main.models import UnifiedJob
|
from awx.main.models import UnifiedJob
|
||||||
from awx.main.task_engine import TaskEnhancer
|
from awx.main.task_engine import TaskEnhancer
|
||||||
from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url,
|
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)
|
check_proot_installed, build_proot_temp_dir, wrap_args_with_proot)
|
||||||
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
__all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
|
__all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
|
||||||
'RunAdHocCommand', 'RunWorkflowJob', 'handle_work_error',
|
'RunAdHocCommand', 'RunWorkflowJob', 'handle_work_error',
|
||||||
@@ -183,8 +183,8 @@ def tower_periodic_scheduler(self):
|
|||||||
new_unified_job.status = 'failed'
|
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.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.save(update_fields=['status', 'job_explanation'])
|
||||||
new_unified_job.socketio_emit_status("failed")
|
new_unified_job.websocket_emit_status("failed")
|
||||||
emit_websocket_notification('/socket.io/schedules', 'schedule_changed', dict(id=schedule.id))
|
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
||||||
|
|
||||||
def _send_notification_templates(instance, status_str):
|
def _send_notification_templates(instance, status_str):
|
||||||
if status_str not in ['succeeded', 'failed']:
|
if status_str not in ['succeeded', 'failed']:
|
||||||
@@ -237,7 +237,7 @@ def handle_work_error(self, task_id, subtasks=None):
|
|||||||
instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
|
instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
|
||||||
(first_instance_type, first_instance.name, first_instance.id)
|
(first_instance_type, first_instance.name, first_instance.id)
|
||||||
instance.save()
|
instance.save()
|
||||||
instance.socketio_emit_status("failed")
|
instance.websocket_emit_status("failed")
|
||||||
|
|
||||||
if first_instance:
|
if first_instance:
|
||||||
_send_notification_templates(first_instance, 'failed')
|
_send_notification_templates(first_instance, 'failed')
|
||||||
@@ -590,7 +590,7 @@ class BaseTask(Task):
|
|||||||
'''
|
'''
|
||||||
instance = self.update_model(pk, status='running', celery_task_id=self.request.id)
|
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, ''
|
status, rc, tb = 'error', None, ''
|
||||||
output_replacements = []
|
output_replacements = []
|
||||||
try:
|
try:
|
||||||
@@ -659,7 +659,7 @@ class BaseTask(Task):
|
|||||||
instance = self.update_model(pk, status=status, result_traceback=tb,
|
instance = self.update_model(pk, status=status, result_traceback=tb,
|
||||||
output_replacements=output_replacements)
|
output_replacements=output_replacements)
|
||||||
self.post_run_hook(instance, **kwargs)
|
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'):
|
if status != 'successful' and not hasattr(settings, 'CELERY_UNIT_TEST'):
|
||||||
# Raising an exception will mark the job as 'failed' in celery
|
# Raising an exception will mark the job as 'failed' in celery
|
||||||
# and will stop a task chain from continuing to execute
|
# and will stop a task chain from continuing to execute
|
||||||
|
|||||||
@@ -491,19 +491,6 @@ def get_system_task_capacity():
|
|||||||
return 50 + ((int(total_mem_value) / 1024) - 2) * 75
|
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()
|
_inventory_updates = threading.local()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ INSTALLED_APPS = (
|
|||||||
'django_extensions',
|
'django_extensions',
|
||||||
'djcelery',
|
'djcelery',
|
||||||
'kombu.transport.django',
|
'kombu.transport.django',
|
||||||
|
'channels',
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
'taggit',
|
'taggit',
|
||||||
'social.apps.django_app.default',
|
'social.apps.django_app.default',
|
||||||
@@ -976,4 +977,3 @@ LOGGING = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ BROKER_URL = "amqp://{}:{}@{}/{}".format(os.environ.get("RABBITMQ_USER"),
|
|||||||
os.environ.get("RABBITMQ_HOST"),
|
os.environ.get("RABBITMQ_HOST"),
|
||||||
os.environ.get("RABBITMQ_VHOST"))
|
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 configuration
|
||||||
MONGO_HOST = NotImplemented
|
MONGO_HOST = NotImplemented
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import './helpers';
|
|||||||
import './forms';
|
import './forms';
|
||||||
import './lists';
|
import './lists';
|
||||||
import './widgets';
|
import './widgets';
|
||||||
import './help';
|
|
||||||
import './filters';
|
import './filters';
|
||||||
import { Home } from './controllers/Home';
|
import { Home } from './controllers/Home';
|
||||||
import { SocketsController } from './controllers/Sockets';
|
import { SocketsController } from './controllers/Sockets';
|
||||||
@@ -75,7 +74,6 @@ import './shared/Modal';
|
|||||||
import './shared/prompt-dialog';
|
import './shared/prompt-dialog';
|
||||||
import './shared/directives';
|
import './shared/directives';
|
||||||
import './shared/filters';
|
import './shared/filters';
|
||||||
import './shared/Socket';
|
|
||||||
import './shared/features/main';
|
import './shared/features/main';
|
||||||
import config from './shared/config/main';
|
import config from './shared/config/main';
|
||||||
import './login/authenticationServices/pendo/ng-pendo';
|
import './login/authenticationServices/pendo/ng-pendo';
|
||||||
@@ -183,7 +181,6 @@ var tower = angular.module('Tower', [
|
|||||||
'HostGroupsFormDefinition',
|
'HostGroupsFormDefinition',
|
||||||
'StreamWidget',
|
'StreamWidget',
|
||||||
'JobsHelper',
|
'JobsHelper',
|
||||||
'InventoryGroupsHelpDefinition',
|
|
||||||
'CredentialsHelper',
|
'CredentialsHelper',
|
||||||
'StreamListDefinition',
|
'StreamListDefinition',
|
||||||
'ActivityDetailDefinition',
|
'ActivityDetailDefinition',
|
||||||
@@ -197,10 +194,8 @@ var tower = angular.module('Tower', [
|
|||||||
'StandardOutHelper',
|
'StandardOutHelper',
|
||||||
'LogViewerOptionsDefinition',
|
'LogViewerOptionsDefinition',
|
||||||
'JobDetailHelper',
|
'JobDetailHelper',
|
||||||
'SocketIO',
|
|
||||||
'lrInfiniteScroll',
|
'lrInfiniteScroll',
|
||||||
'LoadConfigHelper',
|
'LoadConfigHelper',
|
||||||
'SocketHelper',
|
|
||||||
'PortalJobsListDefinition',
|
'PortalJobsListDefinition',
|
||||||
'features',
|
'features',
|
||||||
'longDateFilter',
|
'longDateFilter',
|
||||||
@@ -243,88 +238,6 @@ var tower = angular.module('Tower', [
|
|||||||
});
|
});
|
||||||
|
|
||||||
$stateProvider.
|
$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('teams', {
|
state('teams', {
|
||||||
url: '/teams',
|
url: '/teams',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
@@ -526,18 +439,137 @@ var tower = angular.module('Tower', [
|
|||||||
}]);
|
}]);
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.run(['$q', '$compile', '$cookieStore', '$rootScope', '$log',
|
.run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log',
|
||||||
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
|
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
|
||||||
'ClearScope', 'Socket', 'LoadConfig', 'Store',
|
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
|
||||||
'ShowSocketHelp', 'pendoService', 'Prompt', 'Rest', 'Wait',
|
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
|
||||||
'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
|
'FeaturesService', '$filter', 'SocketService',
|
||||||
'FeaturesService', '$filter',
|
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log,
|
||||||
function($q, $compile, $cookieStore, $rootScope, $log, CheckLicense,
|
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
|
||||||
$location, Authorization, LoadBasePaths, Timer, ClearScope, Socket,
|
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
|
||||||
LoadConfig, Store, ShowSocketHelp, pendoService, Prompt, Rest, Wait,
|
|
||||||
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
|
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
|
||||||
$filter) {
|
$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"
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$stateExtender.addState({
|
||||||
|
name: 'projects.edit',
|
||||||
|
url: '/:id',
|
||||||
|
templateUrl: urlPrefix + 'partials/projects.html',
|
||||||
|
controller: ProjectsEdit,
|
||||||
|
data: {
|
||||||
|
activityStreamId: 'id'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
parent: 'projects',
|
||||||
|
label: '{{name}}'
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$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) {
|
$rootScope.addPermission = function(scope) {
|
||||||
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
|
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
|
||||||
};
|
};
|
||||||
@@ -709,84 +741,9 @@ var tower = angular.module('Tower', [
|
|||||||
|
|
||||||
$rootScope.crumbCache = [];
|
$rootScope.crumbCache = [];
|
||||||
|
|
||||||
if ($rootScope.removeOpenSocket) {
|
// $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
|
||||||
$rootScope.removeOpenSocket();
|
// SocketService.subscribe(toState, toParams);
|
||||||
}
|
// });
|
||||||
$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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
openSocket();
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
$rootScope.$apply(function() {
|
|
||||||
sock.checkStatus();
|
|
||||||
$log.debug('socket status: ' + $rootScope.socketStatus);
|
|
||||||
});
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) {
|
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) {
|
||||||
|
|
||||||
@@ -845,7 +802,7 @@ var tower = angular.module('Tower', [
|
|||||||
ConfigService.getConfig().then(function() {
|
ConfigService.getConfig().then(function() {
|
||||||
Timer.init().then(function(timer) {
|
Timer.init().then(function(timer) {
|
||||||
$rootScope.sessionTimer = timer;
|
$rootScope.sessionTimer = timer;
|
||||||
$rootScope.$emit('OpenSocket');
|
SocketService.init();
|
||||||
pendoService.issuePendoIdentity();
|
pendoService.issuePendoIdentity();
|
||||||
CheckLicense.test();
|
CheckLicense.test();
|
||||||
FeaturesService.get();
|
FeaturesService.get();
|
||||||
@@ -873,10 +830,6 @@ var tower = angular.module('Tower', [
|
|||||||
$('#' + tabs + ' #' + tab).tab('show');
|
$('#' + tabs + ' #' + tab).tab('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.socketHelp = function() {
|
|
||||||
ShowSocketHelp();
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.leavePortal = function() {
|
$rootScope.leavePortal = function() {
|
||||||
$rootScope.portalMode = false;
|
$rootScope.portalMode = false;
|
||||||
$location.path('/home/');
|
$location.path('/home/');
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function Home($scope, $compile, $stateParams, $rootScope, $location, $log
|
|||||||
|
|
||||||
var dataCount = 0;
|
var dataCount = 0;
|
||||||
|
|
||||||
$rootScope.$on('JobStatusChange-home', function () {
|
$scope.$on('ws-jobs', function () {
|
||||||
Rest.setUrl(GetBasePath('dashboard'));
|
Rest.setUrl(GetBasePath('dashboard'));
|
||||||
Rest.get()
|
Rest.get()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
|
|||||||
@@ -110,17 +110,11 @@ export function JobsListController ($rootScope, $log, $scope, $compile, $statePa
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($rootScope.removeJobStatusChange) {
|
$scope.$on('ws-jobs', function() {
|
||||||
$rootScope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobs', function() {
|
|
||||||
$scope.refreshJobs();
|
$scope.refreshJobs();
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($rootScope.removeScheduleStatusChange) {
|
$scope.$on('ws-schedules', function() {
|
||||||
$rootScope.removeScheduleStatusChange();
|
|
||||||
}
|
|
||||||
$rootScope.removeScheduleStatusChange = $rootScope.$on('ScheduleStatusChange', function() {
|
|
||||||
if (api_complete) {
|
if (api_complete) {
|
||||||
scheduled_scope.search('schedule');
|
scheduled_scope.search('schedule');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,11 +86,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle project update status changes
|
$scope.$on(`ws-jobs`, function(e, data) {
|
||||||
if ($rootScope.removeJobStatusChange) {
|
|
||||||
$rootScope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-projects', function(e, data) {
|
|
||||||
var project;
|
var project;
|
||||||
$log.debug(data);
|
$log.debug(data);
|
||||||
if ($scope.projects) {
|
if ($scope.projects) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) {
|
|||||||
destroyWatcher: angular.noop,
|
destroyWatcher: angular.noop,
|
||||||
setupWatcher: function(period, jobType) {
|
setupWatcher: function(period, jobType) {
|
||||||
this.destroyWatcher =
|
this.destroyWatcher =
|
||||||
$rootScope.$on('JobStatusChange-home', function() {
|
$rootScope.$on('ws-jobs', function() {
|
||||||
getData(period, jobType).then(function(result) {
|
getData(period, jobType).then(function(result) {
|
||||||
$rootScope.
|
$rootScope.
|
||||||
$broadcast('DataReceived:JobStatusGraph',
|
$broadcast('DataReceived:JobStatusGraph',
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/*************************************************
|
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
|
||||||
*
|
|
||||||
* All Rights Reserved
|
|
||||||
*************************************************/
|
|
||||||
|
|
||||||
import "./help/ChromeSocketHelp";
|
|
||||||
import "./help/FirefoxSocketHelp";
|
|
||||||
import "./help/InventoryGroups";
|
|
||||||
import "./help/SafariSocketHelp";
|
|
||||||
@@ -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: "<p><i class=\"fa icon-socket-ok\"></i> indicates live events are streaming and the browser is connected to the live events server.</p><p>If the indicator continually shows <i class=\"fa icon-socket-error\"></i> " +
|
|
||||||
"or <i class=\"fa icon-socket-connecting\"></i>, 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.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Live events connection:',
|
|
||||||
icon: {
|
|
||||||
"class": "fa fa-5x fa-rss {{ socketStatus }}-color",
|
|
||||||
style: "margin-top: 75px;",
|
|
||||||
containerHeight: 200
|
|
||||||
},
|
|
||||||
box: "<p><strong>{{ browserName }}</strong> is connecting to the live events server on port <strong>{{ socketPort }}</strong>. The current connection status is " +
|
|
||||||
"<i class=\"fa icon-socket-{{ socketStatus }}\"></i> <strong>{{ socketStatus }}</strong>.</p><p>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."
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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: "<p><i class=\"fa icon-socket-ok\"></i> indicates live events are streaming and the browser is connected to the live events server.</p><p>If the indicator continually shows <i class=\"fa icon-socket-error\"></i> " +
|
|
||||||
"or <i class=\"fa icon-socket-connecting\"></i>, 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.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Live events connection:',
|
|
||||||
icon: {
|
|
||||||
"class": "fa fa-5x fa-rss {{ socketStatus }}-color",
|
|
||||||
style: "margin-top: 75px;",
|
|
||||||
containerHeight: 200
|
|
||||||
},
|
|
||||||
box: "<p><strong>{{ browserName }}</strong> is connecting to the live events server on port <strong>{{ socketPort }}</strong>. The current connection status is " +
|
|
||||||
"<i class=\"fa icon-socket-{{ socketStatus }}\"></i> <strong>{{ socketStatus }}</strong>.</p><p>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.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Self signed certificate:',
|
|
||||||
icon: {
|
|
||||||
"class": "fa fa-5x fa-check ok-color",
|
|
||||||
style: "margin-top: 75px;",
|
|
||||||
containerHeight: 200
|
|
||||||
},
|
|
||||||
box: "<p>If the Tower web server is using a self signed security certificate, Firefox needs to accept the certificate and allow the " +
|
|
||||||
"connection.</p><p>Click Next for help accepting a self signed certificate.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Accepting a self-signed certificate:',
|
|
||||||
img: {
|
|
||||||
src: 'understand_the_risk.png',
|
|
||||||
maxWidth: 440
|
|
||||||
},
|
|
||||||
box: "<p>Navigate to <a href=\"{{ socketURL }}\" target=\"_blank\">{{ socketURL }}</a> The above warning will appear.</p><p>Click <i>I Understand the Risks</i></p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Accepting a self-signed certificate:',
|
|
||||||
img: {
|
|
||||||
src: 'add_exception.png',
|
|
||||||
maxWidth: 440
|
|
||||||
},
|
|
||||||
box: "<p>Click the <i>Add Exception</i> button."
|
|
||||||
}, {
|
|
||||||
intro: 'Accepting a self-signed certificate:',
|
|
||||||
img: {
|
|
||||||
src: 'confirm_exception.png',
|
|
||||||
maxWidth: 340
|
|
||||||
},
|
|
||||||
box: "<p>Click the <i>Confirm the Security Exception</i> button. This will add the self signed certificate from the Tower server to Firefox's list of trusted certificates.<p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Accepting a self-signed certificate:',
|
|
||||||
img: {
|
|
||||||
src: 'refresh_firefox.png',
|
|
||||||
maxWidth: 480
|
|
||||||
},
|
|
||||||
box: "<p>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."
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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 <i class=\"fa fa-plus\"></i> 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 <a href=\"http://docs.ansible.com/intro_inventory.html\" target="_blank"> ' +
|
|
||||||
'docs.ansible.com/intro_inventory.html</a>'
|
|
||||||
}, {
|
|
||||||
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 <a href=\"/#/credentials\" " +
|
|
||||||
"target=\"_blank\">Credentials</a> 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 <i class=\"fa fa-refresh\"></i>."
|
|
||||||
}, {
|
|
||||||
intro: "Add subgroups:",
|
|
||||||
img: {
|
|
||||||
src: 'groups008.png',
|
|
||||||
maxWidth: 469,
|
|
||||||
maxHeight: 243
|
|
||||||
},
|
|
||||||
box: "<div class=\"text-left\">First, select an existing group.</div>"
|
|
||||||
}, {
|
|
||||||
intro: "Add subgroups:",
|
|
||||||
img: {
|
|
||||||
src: 'groups009.png',
|
|
||||||
maxWidth: 475,
|
|
||||||
maxHeight: 198
|
|
||||||
},
|
|
||||||
box: "<div class=\"text-left\">Then click <i class=\"fa fa-plus\"></i> to create a new group. The new group " +
|
|
||||||
"will be added to the selected group.</div>"
|
|
||||||
}, {
|
|
||||||
intro: 'Add hosts:',
|
|
||||||
img: {
|
|
||||||
src: 'groups010.png',
|
|
||||||
maxWidth: 475,
|
|
||||||
maxHeight: 122
|
|
||||||
},
|
|
||||||
box: "<div class=\"text-left\"><p>First, select a Group. " +
|
|
||||||
"Then click <i class=\"fa fa-plus\"></i> on the hosts list (the right side of the page) to create a host. " +
|
|
||||||
"The new host will be part of the selected group.</p></div>"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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: "<p><i class=\"fa icon-socket-ok\"></i> indicates live events are streaming and the browser is connected to the live events server.</p><p>If the indicator continually shows <i class=\"fa icon-socket-error\"></i> " +
|
|
||||||
"or <i class=\"fa icon-socket-connecting\"></i>, 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.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Live events connection:',
|
|
||||||
icon: {
|
|
||||||
"class": "fa fa-5x fa-rss {{ socketStatus }}-color",
|
|
||||||
style: "margin-top: 75px;",
|
|
||||||
containerHeight: 200
|
|
||||||
},
|
|
||||||
box: "<p><strong>{{ browserName }}</strong> is connecting to the live events server on port <strong>{{ socketPort }}</strong>. The current connection status is " +
|
|
||||||
"<i class=\"fa icon-socket-{{ socketStatus }}\"></i> <strong>{{ socketStatus }}</strong>.</p><p>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.</p>"
|
|
||||||
}, {
|
|
||||||
intro: 'Self signed certificate:',
|
|
||||||
icon: {
|
|
||||||
"class": "fa fa-5x fa-check ok-color",
|
|
||||||
style: "margin-top: 75px;",
|
|
||||||
containerHeight: 200
|
|
||||||
},
|
|
||||||
box: "<p>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.</p>" +
|
|
||||||
"<p>Switching browsers to either Chrome or Firefox will work as well.</p>"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -23,7 +23,6 @@ import ProjectPath from "./helpers/ProjectPath";
|
|||||||
import Projects from "./helpers/Projects";
|
import Projects from "./helpers/Projects";
|
||||||
import Schedules from "./helpers/Schedules";
|
import Schedules from "./helpers/Schedules";
|
||||||
import Selection from "./helpers/Selection";
|
import Selection from "./helpers/Selection";
|
||||||
import SocketHelper from "./helpers/SocketHelper";
|
|
||||||
import Users from "./helpers/Users";
|
import Users from "./helpers/Users";
|
||||||
import Variables from "./helpers/Variables";
|
import Variables from "./helpers/Variables";
|
||||||
import ApiDefaults from "./helpers/api-defaults";
|
import ApiDefaults from "./helpers/api-defaults";
|
||||||
@@ -55,7 +54,6 @@ export
|
|||||||
Projects,
|
Projects,
|
||||||
Schedules,
|
Schedules,
|
||||||
Selection,
|
Selection,
|
||||||
SocketHelper,
|
|
||||||
Users,
|
Users,
|
||||||
Variables,
|
Variables,
|
||||||
ApiDefaults,
|
ApiDefaults,
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -23,5 +23,10 @@ export default {
|
|||||||
},
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
label: "RUN COMMAND"
|
label: "RUN COMMAND"
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ var copyMoveGroup = {
|
|||||||
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
|
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
'form@inventoryManage' : {
|
'form@inventoryManage' : {
|
||||||
controller: CopyMoveGroupsController,
|
controller: CopyMoveGroupsController,
|
||||||
@@ -40,6 +45,11 @@ var copyMoveHost = {
|
|||||||
return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]);
|
return HostManageService.get({id: $stateParams.host_id}).then(res => res.data.results[0]);
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
'form@inventoryManage': {
|
'form@inventoryManage': {
|
||||||
templateUrl: templateUrl('inventories/manage/copy-move/copy-move'),
|
templateUrl: templateUrl('inventories/manage/copy-move/copy-move'),
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
*************************************************/
|
*************************************************/
|
||||||
export default
|
export default
|
||||||
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroups', 'generateList', 'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus',
|
['$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,
|
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,
|
var list = InventoryGroups,
|
||||||
view = generateList,
|
view = generateList,
|
||||||
pageSize = 20;
|
pageSize = 20;
|
||||||
|
|
||||||
|
|
||||||
$scope.inventory_id = $stateParams.inventory_id;
|
$scope.inventory_id = $stateParams.inventory_id;
|
||||||
|
|
||||||
$scope.canAdd = false;
|
$scope.canAdd = false;
|
||||||
@@ -97,26 +99,27 @@
|
|||||||
group_name: group.name,
|
group_name: group.name,
|
||||||
group_source: res.data.results[0].source
|
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$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();
|
||||||
|
}
|
||||||
|
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) {
|
$scope.cancelUpdate = function (id) {
|
||||||
GroupsCancelUpdate({ scope: $scope, id: id });
|
GroupsCancelUpdate({ scope: $scope, id: id });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ var ManageGroupsEdit = {
|
|||||||
data: {
|
data: {
|
||||||
mode: 'edit'
|
mode: 'edit'
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService){
|
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService){
|
||||||
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
|
return GroupManageService.get({id: $stateParams.group_id}).then(res => res.data.results[0]);
|
||||||
@@ -41,6 +46,11 @@ var ManageGroupsAdd = {
|
|||||||
data: {
|
data: {
|
||||||
mode: 'add'
|
mode: 'add'
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
'form@inventoryManage': {
|
'form@inventoryManage': {
|
||||||
controller: addController,
|
controller: addController,
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ var ManageHostsEdit = {
|
|||||||
});
|
});
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
'form@inventoryManage': {
|
'form@inventoryManage': {
|
||||||
controller: editController,
|
controller: editController,
|
||||||
@@ -40,6 +45,11 @@ var ManageHostsAdd = {
|
|||||||
data: {
|
data: {
|
||||||
mode: 'add'
|
mode: 'add'
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
views: {
|
views: {
|
||||||
'form@inventoryManage': {
|
'form@inventoryManage': {
|
||||||
controller: addController,
|
controller: addController,
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ import GroupsListController from './groups/groups-list.controller';
|
|||||||
export default {
|
export default {
|
||||||
name: 'inventoryManage',
|
name: 'inventoryManage',
|
||||||
url: '/inventories/:inventory_id/manage?{group:int}{failed}',
|
url: '/inventories/:inventory_id/manage?{group:int}{failed}',
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
params:{
|
params:{
|
||||||
group:{
|
group:{
|
||||||
array: true
|
array: true
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ var hostEventModal = {
|
|||||||
return JobDetailService.getJobEventChildren($stateParams.taskId).then(res => res.data.results);
|
return JobDetailService.getJobEventChildren($stateParams.taskId).then(res => res.data.results);
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed", "summary"],
|
||||||
|
"job_events": []
|
||||||
|
}
|
||||||
|
},
|
||||||
onExit: function() {
|
onExit: function() {
|
||||||
// close the modal
|
// close the modal
|
||||||
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
|
// 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',
|
name: 'jobDetail.host-event.details',
|
||||||
url: '/details',
|
url: '/details',
|
||||||
controller: 'HostEventController',
|
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 = {
|
var hostEventJson = {
|
||||||
name: 'jobDetail.host-event.json',
|
name: 'jobDetail.host-event.json',
|
||||||
url: '/json',
|
url: '/json',
|
||||||
controller: 'HostEventController',
|
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 = {
|
var hostEventStdout = {
|
||||||
name: 'jobDetail.host-event.stdout',
|
name: 'jobDetail.host-event.stdout',
|
||||||
url: '/stdout',
|
url: '/stdout',
|
||||||
controller: 'HostEventController',
|
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 = {
|
var hostEventStderr = {
|
||||||
name: 'jobDetail.host-event.stderr',
|
name: 'jobDetail.host-event.stderr',
|
||||||
url: '/stderr',
|
url: '/stderr',
|
||||||
controller: 'HostEventController',
|
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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export default {
|
|||||||
$('.modal-backdrop').remove();
|
$('.modal-backdrop').remove();
|
||||||
$('body').removeClass('modal-open');
|
$('body').removeClass('modal-open');
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed", "summary"],
|
||||||
|
"job_events": []
|
||||||
|
}
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
hosts: ['JobDetailService','$stateParams', function(JobDetailService, $stateParams) {
|
||||||
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
return JobDetailService.getRelatedJobEvents($stateParams.id, {
|
||||||
|
|||||||
@@ -48,23 +48,19 @@
|
|||||||
$scope.status = res.results[0].status;
|
$scope.status = res.results[0].status;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if ($rootScope.removeJobStatusChange) {
|
if ($rootScope.removeJobSummaryComplete) {
|
||||||
$rootScope.removeJobStatusChange();
|
$rootScope.removeJobSummaryComplete();
|
||||||
}
|
}
|
||||||
// emitted by the API in the same function used to persist host summary data
|
// 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
|
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobSummaryComplete', function(e, data) {
|
$scope.$on('ws-jobs-summary', function(e, data) {
|
||||||
// discard socket msgs we don't care about in this context
|
// discard socket msgs we don't care about in this context
|
||||||
if (parseInt($stateParams.id) === data.unified_job_id){
|
if (parseInt($stateParams.id) === data.unified_job_id){
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
|
$scope.$on('ws-jobs', function(e, data) {
|
||||||
if ($rootScope.removeJobSummaryComplete) {
|
|
||||||
$rootScope.removeJobSummaryComplete();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobSummaryComplete = $rootScope.$on('JobStatusChange-jobDetails', function(e, data) {
|
|
||||||
if (parseInt($stateParams.id) === data.unified_job_id){
|
if (parseInt($stateParams.id) === data.unified_job_id){
|
||||||
$scope.status = data.status;
|
$scope.status = data.status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import {templateUrl} from '../../shared/template-url/template-url.factory';
|
|||||||
export default {
|
export default {
|
||||||
name: 'jobDetail.host-summary',
|
name: 'jobDetail.host-summary',
|
||||||
url: '/event-summary',
|
url: '/event-summary',
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed", "summary"],
|
||||||
|
"job_events": []
|
||||||
|
}
|
||||||
|
},
|
||||||
views:{
|
views:{
|
||||||
'host-summary': {
|
'host-summary': {
|
||||||
controller: 'HostSummaryController',
|
controller: 'HostSummaryController',
|
||||||
|
|||||||
@@ -197,32 +197,22 @@ export default
|
|||||||
"<p><i class=\"fa fa-circle changed-hosts-color\"></i> Changed</p>\n" +
|
"<p><i class=\"fa fa-circle changed-hosts-color\"></i> Changed</p>\n" +
|
||||||
"<p><i class=\"fa fa-circle unreachable-hosts-color\"></i> Unreachable</p>\n" +
|
"<p><i class=\"fa fa-circle unreachable-hosts-color\"></i> Unreachable</p>\n" +
|
||||||
"<p><i class=\"fa fa-circle failed-hosts-color\"></i> Failed</p>\n";
|
"<p><i class=\"fa fa-circle failed-hosts-color\"></i> Failed</p>\n";
|
||||||
function openSocket() {
|
|
||||||
$rootScope.event_socket.on("job_events-" + job_id, function(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 });
|
|
||||||
});
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
openSocket();
|
|
||||||
|
|
||||||
if ($rootScope.removeJobStatusChange) {
|
scope.$on(`ws-job_events-${job_id}`, function(e, data) {
|
||||||
$rootScope.removeJobStatusChange();
|
// update elapsed time on each event received
|
||||||
}
|
scope.job_status.elapsed = GetElapsed({
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobDetails', function(e, data) {
|
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
|
// if we receive a status change event for the current job indicating the job
|
||||||
// is finished, stop event queue processing and reload
|
// is finished, stop event queue processing and reload
|
||||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
||||||
@@ -236,10 +226,7 @@ export default
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($rootScope.removeJobSummaryComplete) {
|
scope.$on('ws-jobs-summary', function() {
|
||||||
$rootScope.removeJobSummaryComplete();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobSummaryComplete = $rootScope.$on('JobSummaryComplete', function() {
|
|
||||||
// the job host summary should now be available from the API
|
// the job host summary should now be available from the API
|
||||||
$log.debug('Trigging reload of job_host_summaries');
|
$log.debug('Trigging reload of job_host_summaries');
|
||||||
scope.$emit('InitialLoadComplete');
|
scope.$emit('InitialLoadComplete');
|
||||||
|
|||||||
@@ -13,21 +13,11 @@ export default {
|
|||||||
parent: 'jobs',
|
parent: 'jobs',
|
||||||
label: "{{ job.id }} - {{ job.name }}"
|
label: "{{ job.id }} - {{ job.name }}"
|
||||||
},
|
},
|
||||||
resolve: {
|
socket: {
|
||||||
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
"groups":{
|
||||||
if (!$rootScope.event_socket) {
|
"jobs": ["status_changed", "summary"],
|
||||||
$rootScope.event_socket = Socket({
|
"job_events": []
|
||||||
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'),
|
templateUrl: templateUrl('job-detail/job-detail'),
|
||||||
controller: 'JobDetailController'
|
controller: 'JobDetailController'
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export default {
|
|||||||
parent: "jobTemplates",
|
parent: "jobTemplates",
|
||||||
label: "CREATE JOB TEMPLATE"
|
label: "CREATE JOB TEMPLATE"
|
||||||
},
|
},
|
||||||
|
socket:{
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
onExit: function(){
|
onExit: function(){
|
||||||
// close the survey maker modal
|
// 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"
|
// using an onExit event to handle cases where the user navs away using the url bar / back and not modal "X"
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export default {
|
|||||||
data: {
|
data: {
|
||||||
activityStreamId: 'id'
|
activityStreamId: 'id'
|
||||||
},
|
},
|
||||||
|
socket:{
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: 'jobTemplates',
|
parent: 'jobTemplates',
|
||||||
label: "{{name}}"
|
label: "{{name}}"
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ export default
|
|||||||
view.inject(list, { mode: mode, scope: $scope });
|
view.inject(list, { mode: mode, scope: $scope });
|
||||||
$rootScope.flashMessage = null;
|
$rootScope.flashMessage = null;
|
||||||
|
|
||||||
|
$scope.$on(`ws-jobs`, function () {
|
||||||
|
$scope.search(list.iterator);
|
||||||
|
});
|
||||||
|
|
||||||
if ($scope.removePostRefresh) {
|
if ($scope.removePostRefresh) {
|
||||||
$scope.removePostRefresh();
|
$scope.removePostRefresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,10 @@ export default {
|
|||||||
},
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
label: "JOB TEMPLATES"
|
label: "JOB TEMPLATES"
|
||||||
|
},
|
||||||
|
socket:{
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export default
|
|||||||
|
|
||||||
var x,
|
var x,
|
||||||
ConfigService = $injector.get('ConfigService'),
|
ConfigService = $injector.get('ConfigService'),
|
||||||
|
SocketService = $injector.get('SocketService'),
|
||||||
scope = angular.element(document.getElementById('main-view')).scope();
|
scope = angular.element(document.getElementById('main-view')).scope();
|
||||||
|
|
||||||
if(scope){
|
if(scope){
|
||||||
@@ -94,6 +95,7 @@ export default
|
|||||||
$rootScope.lastUser = $cookieStore.get('current_user').id;
|
$rootScope.lastUser = $cookieStore.get('current_user').id;
|
||||||
}
|
}
|
||||||
ConfigService.delete();
|
ConfigService.delete();
|
||||||
|
SocketService.disconnect();
|
||||||
$cookieStore.remove('token_expires');
|
$cookieStore.remove('token_expires');
|
||||||
$cookieStore.remove('current_user');
|
$cookieStore.remove('current_user');
|
||||||
$cookieStore.remove('token');
|
$cookieStore.remove('token');
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {templateUrl} from '../shared/template-url/template-url.factory';
|
|||||||
export default {
|
export default {
|
||||||
name: 'signIn',
|
name: 'signIn',
|
||||||
route: '/login',
|
route: '/login',
|
||||||
|
socket: null,
|
||||||
templateUrl: templateUrl('login/loginBackDrop'),
|
templateUrl: templateUrl('login/loginBackDrop'),
|
||||||
resolve: {
|
resolve: {
|
||||||
obj: ['$rootScope', 'Authorization',
|
obj: ['$rootScope', 'Authorization',
|
||||||
|
|||||||
@@ -57,10 +57,11 @@
|
|||||||
export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope',
|
export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope',
|
||||||
'$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait', 'Timer',
|
'$location', 'Authorization', 'ToggleClass', 'Alert', 'Wait', 'Timer',
|
||||||
'Empty', 'ClearScope', '$scope', 'pendoService', 'ConfigService',
|
'Empty', 'ClearScope', '$scope', 'pendoService', 'ConfigService',
|
||||||
'CheckLicense', 'FeaturesService',
|
'CheckLicense', 'FeaturesService', 'SocketService',
|
||||||
function ($log, $cookieStore, $compile, $window, $rootScope, $location,
|
function ($log, $cookieStore, $compile, $window, $rootScope, $location,
|
||||||
Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope,
|
Authorization, ToggleClass, Alert, Wait, Timer, Empty, ClearScope,
|
||||||
scope, pendoService, ConfigService, CheckLicense, FeaturesService) {
|
scope, pendoService, ConfigService, CheckLicense, FeaturesService,
|
||||||
|
SocketService) {
|
||||||
var lastPath, lastUser, sessionExpired, loginAgain;
|
var lastPath, lastUser, sessionExpired, loginAgain;
|
||||||
|
|
||||||
loginAgain = function() {
|
loginAgain = function() {
|
||||||
@@ -135,7 +136,7 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope',
|
|||||||
Authorization.setUserInfo(data);
|
Authorization.setUserInfo(data);
|
||||||
Timer.init().then(function(timer){
|
Timer.init().then(function(timer){
|
||||||
$rootScope.sessionTimer = timer;
|
$rootScope.sessionTimer = timer;
|
||||||
$rootScope.$emit('OpenSocket');
|
SocketService.init();
|
||||||
$rootScope.user_is_superuser = data.results[0].is_superuser;
|
$rootScope.user_is_superuser = data.results[0].is_superuser;
|
||||||
$rootScope.user_is_system_auditor = data.results[0].is_system_auditor;
|
$rootScope.user_is_system_auditor = data.results[0].is_system_auditor;
|
||||||
scope.$emit('AuthorizationGetLicense');
|
scope.$emit('AuthorizationGetLicense');
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export default ['$scope', '$rootScope', '$location', '$log',
|
|||||||
generator = GenerateList,
|
generator = GenerateList,
|
||||||
orgBase = GetBasePath('organizations');
|
orgBase = GetBasePath('organizations');
|
||||||
|
|
||||||
|
$scope.$on(`ws-jobs`, function () {
|
||||||
|
$scope.search(list.iterator);
|
||||||
|
});
|
||||||
|
|
||||||
Rest.setUrl(orgBase + $stateParams.organization_id);
|
Rest.setUrl(orgBase + $stateParams.organization_id);
|
||||||
Rest.get()
|
Rest.get()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
|
|||||||
@@ -86,11 +86,7 @@ export default ['$scope', '$rootScope', '$location', '$log',
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle project update status changes
|
$scope.$on(`ws-jobs`, function(e, data) {
|
||||||
if ($rootScope.removeJobStatusChange) {
|
|
||||||
$rootScope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-projects', function(e, data) {
|
|
||||||
var project;
|
var project;
|
||||||
$log.debug(data);
|
$log.debug(data);
|
||||||
if ($scope.projects) {
|
if ($scope.projects) {
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ export default [
|
|||||||
features: ['FeaturesService', function(FeaturesService) {
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
return FeaturesService.get();
|
return FeaturesService.get();
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -121,6 +126,11 @@ export default [
|
|||||||
features: ['FeaturesService', function(FeaturesService) {
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
return FeaturesService.get();
|
return FeaturesService.get();
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,12 +13,9 @@ export function PortalModeJobsController($scope, $rootScope, GetBasePath, Genera
|
|||||||
defaultUrl = GetBasePath('jobs') + '?created_by=' + $rootScope.current_user.id,
|
defaultUrl = GetBasePath('jobs') + '?created_by=' + $rootScope.current_user.id,
|
||||||
pageSize = 12;
|
pageSize = 12;
|
||||||
|
|
||||||
if ($rootScope.removeJobStatusChange) {
|
$scope.$on('ws-jobs', function() {
|
||||||
$rootScope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-portal', function() {
|
|
||||||
$scope.search('job');
|
$scope.search('job');
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.iterator = list.iterator;
|
$scope.iterator = list.iterator;
|
||||||
$scope.activeFilter = 'user';
|
$scope.activeFilter = 'user';
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ export default {
|
|||||||
url: '/portal',
|
url: '/portal',
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
label: "MY VIEW"
|
label: "MY VIEW"
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
views: {
|
views: {
|
||||||
// the empty parent ui-view
|
// the empty parent ui-view
|
||||||
|
|||||||
@@ -1,226 +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
|
|
||||||
*/
|
|
||||||
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(),
|
|
||||||
endpoint = params.endpoint,
|
|
||||||
protocol = $location.protocol(),
|
|
||||||
io = require('socket.io-client'),
|
|
||||||
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 = protocol + '://' + host + ':' + socketPort + '/socket.io/' + endpoint;
|
|
||||||
$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 = 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
on: function (eventName, callback) {
|
|
||||||
var self = this;
|
|
||||||
if(self){
|
|
||||||
if(self.socket){
|
|
||||||
self.socket.on(eventName, function () {
|
|
||||||
var args = arguments;
|
|
||||||
self.scope.$apply(function () {
|
|
||||||
callback.apply(self.socket, args);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
emit: function (eventName, data, callback) {
|
|
||||||
var self = this;
|
|
||||||
self.socket.emit(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.removeAllListeners(eventName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
@@ -3,5 +3,5 @@ import icon from './icon.directive';
|
|||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('awIcon', [])
|
angular.module('awIcon', [])
|
||||||
.directive('awIcon', icon)
|
.directive('awIcon', icon);
|
||||||
//.directive('includeSvg', includeSvg);
|
//.directive('includeSvg', includeSvg);
|
||||||
|
|||||||
@@ -6,21 +6,21 @@
|
|||||||
|
|
||||||
import listGenerator from './list-generator/main';
|
import listGenerator from './list-generator/main';
|
||||||
import pagination from './pagination/main';
|
import pagination from './pagination/main';
|
||||||
import title from './title.directive';
|
|
||||||
import lodashAsPromised from './lodash-as-promised';
|
import lodashAsPromised from './lodash-as-promised';
|
||||||
import stringFilters from './string-filters/main';
|
import stringFilters from './string-filters/main';
|
||||||
import truncatedText from './truncated-text.directive';
|
import truncatedText from './truncated-text.directive';
|
||||||
import stateExtender from './stateExtender.provider';
|
import stateExtender from './stateExtender.provider';
|
||||||
import rbacUiControl from './rbacUiControl';
|
import rbacUiControl from './rbacUiControl';
|
||||||
|
import socket from './socket/main';
|
||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('shared', [listGenerator.name,
|
angular.module('shared', [listGenerator.name,
|
||||||
pagination.name,
|
pagination.name,
|
||||||
stringFilters.name,
|
stringFilters.name,
|
||||||
'ui.router',
|
'ui.router',
|
||||||
rbacUiControl.name
|
rbacUiControl.name,
|
||||||
|
socket.name
|
||||||
])
|
])
|
||||||
.factory('lodashAsPromised', lodashAsPromised)
|
.factory('lodashAsPromised', lodashAsPromised)
|
||||||
.directive('truncatedText', truncatedText)
|
.directive('truncatedText', truncatedText)
|
||||||
//.directive('title', title)
|
|
||||||
.provider('$stateExtender', stateExtender);
|
.provider('$stateExtender', stateExtender);
|
||||||
|
|||||||
13
awx/ui/client/src/shared/socket/main.js
Normal file
13
awx/ui/client/src/shared/socket/main.js
Normal file
@@ -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);
|
||||||
216
awx/ui/client/src/shared/socket/socket.service.js
Normal file
216
awx/ui/client/src/shared/socket/socket.service.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
import ReconnectingWebSocket from 'reconnectingwebsocket';
|
||||||
|
export default
|
||||||
|
['$rootScope', '$location', '$log','$state', '$q',
|
||||||
|
function ($rootScope, $location, $log, $state, $q) {
|
||||||
|
var needsResubscribing = false,
|
||||||
|
socketPromise = $q.defer();
|
||||||
|
return {
|
||||||
|
init: function() {
|
||||||
|
var self = this,
|
||||||
|
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, {
|
||||||
|
timeoutInterval: 3000,
|
||||||
|
maxReconnectAttempts: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
self.socket.onopen = function () {
|
||||||
|
$log.debug("Websocket connection opened.");
|
||||||
|
socketPromise.resolve();
|
||||||
|
self.checkStatus();
|
||||||
|
if(needsResubscribing){
|
||||||
|
self.subscribe(self.getLast());
|
||||||
|
needsResubscribing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
self.socket.onerror = function (error) {
|
||||||
|
self.checkStatus();
|
||||||
|
$log.debug('Websocket Error Logged: ' + error); //log errors
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
return self.socket;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// encountered expired token, redirect to login page
|
||||||
|
$rootScope.sessionTimer.expireSession('idle');
|
||||||
|
$location.url('/login');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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.
|
||||||
|
// 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.$broadcast('ws-jobs-summary', data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(data.group_name==="job_events"){
|
||||||
|
// The naming scheme is "ws" then a
|
||||||
|
// dash (-) and the group_name, then the job ID
|
||||||
|
// ex: 'ws-jobs-<jobId>'
|
||||||
|
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-<jobId>'
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The naming scheme is "ws" then a
|
||||||
|
// dash (-) and the group_name.
|
||||||
|
// ex: 'ws-jobs'
|
||||||
|
str = `ws-${data.group_name}`;
|
||||||
|
}
|
||||||
|
$rootScope.$broadcast(str, data);
|
||||||
|
},
|
||||||
|
disconnect: function(){
|
||||||
|
if(this.socket){
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
this.setLast(state);
|
||||||
|
},
|
||||||
|
setLast: function(state){
|
||||||
|
this.last = state;
|
||||||
|
},
|
||||||
|
getLast: function(){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkStatus: function() {
|
||||||
|
// Function for changing the socket indicator icon in the nav bar
|
||||||
|
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.";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
socketPromise.promise.then(function(){
|
||||||
|
self.socket.send(data, function () {
|
||||||
|
var args = arguments;
|
||||||
|
self.scope.$apply(function () {
|
||||||
|
if (callback) {
|
||||||
|
callback.apply(self.socket, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}];
|
||||||
@@ -1,10 +1,24 @@
|
|||||||
export default function($stateProvider) {
|
export default function($stateProvider) {
|
||||||
this.$get = function() {
|
this.$get = function() {
|
||||||
return {
|
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', '$stateParams',
|
||||||
|
function(SocketService, $stateParams) {
|
||||||
|
SocketService.addStateResolve(state, $stateParams.id);
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
addState: function(state) {
|
addState: function(state) {
|
||||||
var route = state.route || state.url;
|
var route = state.route || state.url;
|
||||||
|
this.addSocket(state);
|
||||||
$stateProvider.state(state.name, {
|
$stateProvider.state(state.name, {
|
||||||
url: route,
|
url: route,
|
||||||
controller: state.controller,
|
controller: state.controller,
|
||||||
|
|||||||
@@ -11,25 +11,17 @@ export default {
|
|||||||
route: '/ad_hoc_commands/:id',
|
route: '/ad_hoc_commands/:id',
|
||||||
templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'),
|
templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'),
|
||||||
controller: 'JobStdoutController',
|
controller: 'JobStdoutController',
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"],
|
||||||
|
"ad_hoc_command_events": []
|
||||||
|
}
|
||||||
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "jobs",
|
parent: "jobs",
|
||||||
label: "{{ job.module_name }}"
|
label: "{{ job.module_name }}"
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
jobType: 'ad_hoc_commands'
|
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;
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,17 +13,16 @@ export default {
|
|||||||
route: '/inventory_sync/:id',
|
route: '/inventory_sync/:id',
|
||||||
templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'),
|
templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'),
|
||||||
controller: 'JobStdoutController',
|
controller: 'JobStdoutController',
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "jobs",
|
parent: "jobs",
|
||||||
label: "{{ inventory_source_name }}"
|
label: "{{ inventory_source_name }}"
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
jobType: 'inventory_updates'
|
jobType: 'inventory_updates'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
inventorySyncSocket: [function() {
|
|
||||||
// TODO: determine whether or not we have socket support for inventory sync standard out
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,31 +22,21 @@ export default ['$log', '$rootScope', '$scope', '$state', '$stateParams', 'Proce
|
|||||||
function openSockets() {
|
function openSockets() {
|
||||||
if ($state.current.name === 'jobDetail') {
|
if ($state.current.name === 'jobDetail') {
|
||||||
$log.debug("socket watching on job_events-" + job_id);
|
$log.debug("socket watching on job_events-" + job_id);
|
||||||
$rootScope.event_socket.on("job_events-" + job_id, function() {
|
$scope.$on(`ws-job_events-${job_id}`, function() {
|
||||||
$log.debug("socket fired on job_events-" + job_id);
|
$log.debug("socket fired on job_events-" + job_id);
|
||||||
if (api_complete) {
|
if (api_complete) {
|
||||||
event_queue++;
|
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.event_socket.removeAllListeners("job_events-" + job_id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if ($state.current.name === 'adHocJobStdout') {
|
if ($state.current.name === 'adHocJobStdout') {
|
||||||
$log.debug("socket watching on ad_hoc_command_events-" + job_id);
|
$log.debug("socket watching on ad_hoc_command_events-" + job_id);
|
||||||
$rootScope.adhoc_event_socket.on("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);
|
$log.debug("socket fired on ad_hoc_command_events-" + job_id);
|
||||||
if (api_complete) {
|
if (api_complete) {
|
||||||
event_queue++;
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,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
|
// 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.
|
// stdout interval and kill the live_event_processing flag.
|
||||||
if ($scope.removeJobStatusChange) {
|
$scope.$on(`ws-jobs`, function(e, data) {
|
||||||
$scope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$scope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobStdout', function(e, data) {
|
|
||||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) {
|
||||||
if (data.status === 'failed' || data.status === 'canceled' ||
|
if (data.status === 'failed' || data.status === 'canceled' ||
|
||||||
data.status === 'error' || data.status === 'successful') {
|
data.status === 'error' || data.status === 'successful') {
|
||||||
|
|||||||
@@ -11,17 +11,16 @@ export default {
|
|||||||
route: '/management_jobs/:id',
|
route: '/management_jobs/:id',
|
||||||
templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'),
|
templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'),
|
||||||
controller: 'JobStdoutController',
|
controller: 'JobStdoutController',
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "jobs",
|
parent: "jobs",
|
||||||
label: "{{ job.name }}"
|
label: "{{ job.name }}"
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
jobType: 'system_jobs'
|
jobType: 'system_jobs'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
managementJobSocket: [function() {
|
|
||||||
// TODO: determine whether or not we have socket support for management job standard out
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ export default {
|
|||||||
parent: "jobs",
|
parent: "jobs",
|
||||||
label: "{{ project_name }}"
|
label: "{{ project_name }}"
|
||||||
},
|
},
|
||||||
|
socket: {
|
||||||
|
"groups":{
|
||||||
|
"jobs": ["status_changed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
jobType: 'project_updates'
|
jobType: 'project_updates'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
scmUpdateSocket: [function() {
|
|
||||||
// TODO: determine whether or not we have socket support for scm update standard out
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// to see whethere the updated job is the one that we're currently looking at.
|
||||||
if ($scope.removeJobStatusChange) {
|
$scope.$on(`ws-jobs`, function(e, data) {
|
||||||
$scope.removeJobStatusChange();
|
|
||||||
}
|
|
||||||
$scope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobStdout', function(e, data) {
|
|
||||||
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) {
|
if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) {
|
||||||
$scope.job.status = data.status;
|
$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
|
// Set the parse type so that CodeMirror knows how to display extra params YAML/JSON
|
||||||
$scope.parseType = 'yaml';
|
$scope.parseType = 'yaml';
|
||||||
|
|
||||||
|
|||||||
4865
awx/ui/npm-shrinkwrap.json
generated
4865
awx/ui/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"django_port": "8013",
|
"django_port": "8013",
|
||||||
"websocket_port": "8080",
|
|
||||||
"django_host": "0.0.0.0",
|
"django_host": "0.0.0.0",
|
||||||
"sauce_username": "leigh-johnson",
|
"sauce_username": "leigh-johnson",
|
||||||
"sauce_access_key": "f740c3ad-c706-4e10-bb95-46e2cc50c2ac"
|
"sauce_access_key": "f740c3ad-c706-4e10-bb95-46e2cc50c2ac"
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
"npm": "^3.10.3"
|
"npm": "^3.10.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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-docker-cid": "ip=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' $DOCkER_CID` | npm set config ansible-tower:django_host ${ip}; grunt dev",
|
||||||
"build-release": "grunt release",
|
"build-release": "grunt release",
|
||||||
"pretest": "grunt clean:coverage",
|
"pretest": "grunt clean:coverage",
|
||||||
@@ -99,7 +98,7 @@
|
|||||||
"moment": "^2.10.2",
|
"moment": "^2.10.2",
|
||||||
"ng-toast": "leigh-johnson/ngToast#2.0.1",
|
"ng-toast": "leigh-johnson/ngToast#2.0.1",
|
||||||
"nvd3": "leigh-johnson/nvd3#1.7.1",
|
"nvd3": "leigh-johnson/nvd3#1.7.1",
|
||||||
"select2": "^4.0.2",
|
"reconnectingwebsocket": "^1.0.0",
|
||||||
"socket.io-client": "^0.9.17"
|
"select2": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
awx/ui/tests/spec/socket/socket.service-test.js
Normal file
30
awx/ui/tests/spec/socket/socket.service-test.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
'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);
|
||||||
|
SocketService.onMessage(event);
|
||||||
|
expect(rootScope.$emit).toHaveBeenCalledWith('ws-jobs-summary', event.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -27,7 +27,7 @@ var vendorPkgs = [
|
|||||||
'ng-toast',
|
'ng-toast',
|
||||||
'nvd3',
|
'nvd3',
|
||||||
'select2',
|
'select2',
|
||||||
'socket.io-client',
|
'reconnectingwebsocket'
|
||||||
];
|
];
|
||||||
|
|
||||||
var dev = {
|
var dev = {
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ pycparser==2.14
|
|||||||
pygerduty==0.32.1
|
pygerduty==0.32.1
|
||||||
PyJWT==1.4.0
|
PyJWT==1.4.0
|
||||||
pymongo==2.8
|
pymongo==2.8
|
||||||
pyOpenSSL==0.15.1
|
pyOpenSSL==16.0.0
|
||||||
|
cffi==1.7.0
|
||||||
pyparsing==2.0.7
|
pyparsing==2.0.7
|
||||||
pyrad==2.0
|
pyrad==2.0
|
||||||
pyrax==1.9.7
|
pyrax==1.9.7
|
||||||
@@ -131,3 +132,6 @@ wheel==0.24.0
|
|||||||
wrapt==1.10.6
|
wrapt==1.10.6
|
||||||
wsgiref==0.1.2
|
wsgiref==0.1.2
|
||||||
xmltodict==0.9.2
|
xmltodict==0.9.2
|
||||||
|
channels==0.17.2
|
||||||
|
asgi_amqp==0.3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user