resolve conflict: index.html

This commit is contained in:
sundeep-co-in 2016-10-31 23:40:56 +05:30
commit 4237b2e207
314 changed files with 11615 additions and 15591 deletions

View File

@ -189,8 +189,6 @@ class TaskPermission(ModelAccessPermission):
# token.
if view.model == Inventory and request.method.lower() in ('head', 'get'):
return bool(not obj or obj.pk == unified_job.inventory_id)
elif view.model in (JobEvent, AdHocCommandEvent) and request.method.lower() == 'post':
return bool(not obj or obj.pk == unified_job.pk)
else:
return False

View File

@ -2244,10 +2244,10 @@ class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer)
pass
class WorkflowNodeBaseSerializer(BaseSerializer):
job_type = serializers.SerializerMethodField()
job_tags = serializers.SerializerMethodField()
limit = serializers.SerializerMethodField()
skip_tags = serializers.SerializerMethodField()
job_type = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
success_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
failure_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
always_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
@ -2263,17 +2263,12 @@ class WorkflowNodeBaseSerializer(BaseSerializer):
res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
return res
def get_job_type(self, obj):
return obj.char_prompts.get('job_type', None)
def get_job_tags(self, obj):
return obj.char_prompts.get('job_tags', None)
def get_skip_tags(self, obj):
return obj.char_prompts.get('skip_tags', None)
def get_limit(self, obj):
return obj.char_prompts.get('limit', None)
def validate(self, attrs):
# char_prompts go through different validation, so remove them here
for fd in ['job_type', 'job_tags', 'skip_tags', 'limit']:
if fd in attrs:
attrs.pop(fd)
return super(WorkflowNodeBaseSerializer, self).validate(attrs)
class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer):
@ -2421,7 +2416,9 @@ class JobEventSerializer(BaseSerializer):
model = JobEvent
fields = ('*', '-name', '-description', 'job', 'event', 'counter',
'event_display', 'event_data', 'event_level', 'failed',
'changed', 'host', 'host_name', 'parent', 'play', 'task', 'role')
'changed', 'uuid', 'host', 'host_name', 'parent', 'playbook',
'play', 'task', 'role', 'stdout', 'start_line', 'end_line',
'verbosity')
def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj)
@ -2457,16 +2454,8 @@ class AdHocCommandEventSerializer(BaseSerializer):
model = AdHocCommandEvent
fields = ('*', '-name', '-description', 'ad_hoc_command', 'event',
'counter', 'event_display', 'event_data', 'failed',
'changed', 'host', 'host_name')
def to_internal_value(self, data):
ret = super(AdHocCommandEventSerializer, self).to_internal_value(data)
# AdHocCommandAdHocCommandEventsList should be the only view creating
# AdHocCommandEvent instances, so keep the ad_hoc_command it sets, even
# though ad_hoc_command is a read-only field.
if 'ad_hoc_command' in data:
ret['ad_hoc_command'] = data['ad_hoc_command']
return ret
'changed', 'uuid', 'host', 'host_name', 'stdout',
'start_line', 'end_line', 'verbosity')
def get_related(self, obj):
res = super(AdHocCommandEventSerializer, self).get_related(obj)

View File

@ -0,0 +1,15 @@
# Workflow Job Template Workflow Node List
Workflow nodes reference templates to execute and define the ordering
in which to execute them. After a job in this workflow finishes,
the subsequent actions are to:
- run nodes contained in "failure_nodes" or "always_nodes" if job failed
- run nodes contained in "success_nodes" or "always_nodes" if job succeeded
The workflow job is marked as `successful` if all of the jobs running as
a part of the workflow job have completed, and the workflow job has not
been canceled. Even if a job within the workflow has failed, the workflow
job will not be marked as failed.
{% include "api/sub_list_create_api_view.md" %}

View File

@ -2406,6 +2406,7 @@ class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDeta
serializer_class = LabelSerializer
parent_model = JobTemplate
relationship = 'labels'
new_in_300 = True
def post(self, request, *args, **kwargs):
# If a label already exists in the database, attach it instead of erroring out
@ -2699,6 +2700,7 @@ class WorkflowJobTemplateList(ListCreateAPIView):
model = WorkflowJobTemplate
serializer_class = WorkflowJobTemplateListSerializer
always_allow_superuser = False
new_in_310 = True
# TODO: RBAC
'''
@ -2716,10 +2718,12 @@ class WorkflowJobTemplateDetail(RetrieveUpdateDestroyAPIView):
model = WorkflowJobTemplate
serializer_class = WorkflowJobTemplateSerializer
always_allow_superuser = False
new_in_310 = True
class WorkflowJobTemplateLabelList(JobTemplateLabelList):
parent_model = WorkflowJobTemplate
new_in_310 = True
# TODO:
@ -2727,6 +2731,7 @@ class WorkflowJobTemplateLaunch(GenericAPIView):
model = WorkflowJobTemplate
serializer_class = EmptySerializer
new_in_310 = True
def get(self, request, *args, **kwargs):
data = {}
@ -2752,6 +2757,12 @@ class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView):
parent_model = WorkflowJobTemplate
relationship = 'workflow_job_template_nodes'
parent_key = 'workflow_job_template'
new_in_310 = True
def update_raw_data(self, data):
for fd in ['job_type', 'job_tags', 'skip_tags', 'limit', 'skip_tags']:
data[fd] = None
return super(WorkflowJobTemplateWorkflowNodesList, self).update_raw_data(data)
# TODO:
class WorkflowJobTemplateJobsList(SubListAPIView):
@ -2767,12 +2778,14 @@ class WorkflowJobList(ListCreateAPIView):
model = WorkflowJob
serializer_class = WorkflowJobListSerializer
new_in_310 = True
# TODO:
class WorkflowJobDetail(RetrieveDestroyAPIView):
model = WorkflowJob
serializer_class = WorkflowJobSerializer
new_in_310 = True
class WorkflowJobWorkflowNodesList(SubListAPIView):
@ -2782,6 +2795,7 @@ class WorkflowJobWorkflowNodesList(SubListAPIView):
parent_model = WorkflowJob
relationship = 'workflow_job_nodes'
parent_key = 'workflow_job'
new_in_310 = True
class SystemJobTemplateList(ListAPIView):
@ -3052,21 +3066,6 @@ class GroupJobEventsList(BaseJobEventsList):
class JobJobEventsList(BaseJobEventsList):
parent_model = Job
authentication_classes = [TaskAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
permission_classes = (TaskPermission,)
# Post allowed for job event callback only.
def post(self, request, *args, **kwargs):
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
data = request.data.copy()
data['job'] = parent_obj.pk
serializer = self.get_serializer(data=data)
if serializer.is_valid():
self.instance = serializer.save()
headers = {'Location': serializer.data['url']}
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class JobJobPlaysList(BaseJobEventsList):
@ -3457,25 +3456,8 @@ class HostAdHocCommandEventsList(BaseAdHocCommandEventsList):
class AdHocCommandAdHocCommandEventsList(BaseAdHocCommandEventsList):
parent_model = AdHocCommand
authentication_classes = [TaskAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
permission_classes = (TaskPermission,)
new_in_220 = True
# Post allowed for ad hoc event callback only.
def post(self, request, *args, **kwargs):
if request.user:
raise PermissionDenied()
parent_obj = get_object_or_404(self.parent_model, pk=self.kwargs['pk'])
data = request.data.copy()
data['ad_hoc_command'] = parent_obj
serializer = self.get_serializer(data=data)
if serializer.is_valid():
self.instance = serializer.save()
headers = {'Location': serializer.data['url']}
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class AdHocCommandActivityStreamList(SubListAPIView):
@ -3586,7 +3568,11 @@ class UnifiedJobStdout(RetrieveAPIView):
dark_bg = (content_only and dark) or (not content_only and (dark or not dark_val))
content, start, end, absolute_end = unified_job.result_stdout_raw_limited(start_line, end_line)
# Remove any ANSI escape sequences containing job event data.
content = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', content)
body = ansiconv.to_html(cgi.escape(content))
context = {
'title': get_view_name(self.__class__),
'body': mark_safe(body),

22
awx/lib/sitecustomize.py Normal file
View File

@ -0,0 +1,22 @@
# Python
import os
import sys
# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize tower display
# callback as early as possible to wrap ansible.display.Display methods.
def argv_ready(argv):
if argv and os.path.basename(argv[0]) in {'ansible', 'ansible-playbook'}:
import tower_display_callback # noqa
class argv_placeholder(object):
def __del__(self):
argv_ready(sys.argv)
if hasattr(sys, 'argv'):
argv_ready(sys.argv)
else:
sys.argv = argv_placeholder()

View File

@ -0,0 +1,25 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Tower Display Callback
from . import cleanup # noqa (registers control persistent cleanup)
from . import display # noqa (wraps ansible.display.Display methods)
from .module import TowerDefaultCallbackModule, TowerMinimalCallbackModule
__all__ = ['TowerDefaultCallbackModule', 'TowerMinimalCallbackModule']

View File

@ -0,0 +1,72 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import atexit
import glob
import os
import pwd
# PSUtil
import psutil
__all__ = []
@atexit.register
def terminate_ssh_control_masters():
# Determine if control persist is being used and if any open sockets
# exist after running the playbook.
cp_path = os.environ.get('ANSIBLE_SSH_CONTROL_PATH', '')
if not cp_path:
return
cp_dir = os.path.dirname(cp_path)
if not os.path.exists(cp_dir):
return
cp_pattern = os.path.join(cp_dir, 'ansible-ssh-*')
cp_files = glob.glob(cp_pattern)
if not cp_files:
return
# Attempt to find any running control master processes.
username = pwd.getpwuid(os.getuid())[0]
ssh_cm_procs = []
for proc in psutil.process_iter():
try:
pname = proc.name()
pcmdline = proc.cmdline()
pusername = proc.username()
except psutil.NoSuchProcess:
continue
if pusername != username:
continue
if pname != 'ssh':
continue
for cp_file in cp_files:
if pcmdline and cp_file in pcmdline[0]:
ssh_cm_procs.append(proc)
break
# Terminate then kill control master processes. Workaround older
# version of psutil that may not have wait_procs implemented.
for proc in ssh_cm_procs:
proc.terminate()
procs_gone, procs_alive = psutil.wait_procs(ssh_cm_procs, timeout=5)
for proc in procs_alive:
proc.kill()

View File

@ -0,0 +1,92 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import functools
import sys
import uuid
# Ansible
from ansible.utils.display import Display
# Tower Display Callback
from tower_display_callback.events import event_context
__all__ = []
def with_context(**context):
global event_context
def wrap(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
with event_context.set_local(**context):
return f(*args, **kwargs)
return wrapper
return wrap
for attr in dir(Display):
if attr.startswith('_') or 'cow' in attr or 'prompt' in attr:
continue
if attr in ('display', 'v', 'vv', 'vvv', 'vvvv', 'vvvvv', 'vvvvvv', 'verbose'):
continue
if not callable(getattr(Display, attr)):
continue
setattr(Display, attr, with_context(**{attr: True})(getattr(Display, attr)))
def with_verbosity(f):
global event_context
@functools.wraps(f)
def wrapper(*args, **kwargs):
host = args[2] if len(args) >= 3 else kwargs.get('host', None)
caplevel = args[3] if len(args) >= 4 else kwargs.get('caplevel', 2)
context = dict(verbose=True, verbosity=(caplevel + 1))
if host is not None:
context['remote_addr'] = host
with event_context.set_local(**context):
return f(*args, **kwargs)
return wrapper
Display.verbose = with_verbosity(Display.verbose)
def display_with_context(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
log_only = args[5] if len(args) >= 6 else kwargs.get('log_only', False)
stderr = args[3] if len(args) >= 4 else kwargs.get('stderr', False)
fileobj = sys.stderr if stderr else sys.stdout
event_uuid = event_context.get().get('uuid', None)
try:
if not log_only and not event_uuid:
event_context.add_local(uuid=str(uuid.uuid4()))
event_context.dump_begin(fileobj)
return f(*args, **kwargs)
finally:
if not log_only and not event_uuid:
event_context.dump_end(fileobj)
event_context.remove_local(uuid=None)
return wrapper
Display.display = display_with_context(Display.display)

View File

@ -0,0 +1,137 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import base64
import contextlib
import datetime
import json
import os
import threading
import uuid
__all__ = ['event_context']
class EventContext(object):
'''
Store global and local (per thread/process) data associated with callback
events and other display output methods.
'''
def add_local(self, **kwargs):
if not hasattr(self, '_local'):
self._local = threading.local()
self._local._ctx = {}
self._local._ctx.update(kwargs)
def remove_local(self, **kwargs):
if hasattr(self, '_local'):
for key in kwargs.keys():
self._local._ctx.pop(key, None)
@contextlib.contextmanager
def set_local(self, **kwargs):
try:
self.add_local(**kwargs)
yield
finally:
self.remove_local(**kwargs)
def get_local(self):
return getattr(getattr(self, '_local', None), '_ctx', {})
def add_global(self, **kwargs):
if not hasattr(self, '_global_ctx'):
self._global_ctx = {}
self._global_ctx.update(kwargs)
def remove_global(self, **kwargs):
if hasattr(self, '_global_ctx'):
for key in kwargs.keys():
self._global_ctx.pop(key, None)
@contextlib.contextmanager
def set_global(self, **kwargs):
try:
self.add_global(**kwargs)
yield
finally:
self.remove_global(**kwargs)
def get_global(self):
return getattr(self, '_global_ctx', {})
def get(self):
ctx = {}
ctx.update(self.get_global())
ctx.update(self.get_local())
return ctx
def get_begin_dict(self):
event_data = self.get()
if os.getenv('JOB_ID', ''):
event_data['job_id'] = int(os.getenv('JOB_ID', '0'))
if os.getenv('AD_HOC_COMMAND_ID', ''):
event_data['ad_hoc_command_id'] = int(os.getenv('AD_HOC_COMMAND_ID', '0'))
event_data.setdefault('pid', os.getpid())
event_data.setdefault('uuid', str(uuid.uuid4()))
event_data.setdefault('created', datetime.datetime.utcnow().isoformat())
if not event_data.get('parent_uuid', None) and event_data.get('job_id', None):
for key in ('task_uuid', 'play_uuid', 'playbook_uuid'):
parent_uuid = event_data.get(key, None)
if parent_uuid and parent_uuid != event_data.get('uuid', None):
event_data['parent_uuid'] = parent_uuid
break
event = event_data.pop('event', None)
if not event:
event = 'verbose'
for key in ('debug', 'verbose', 'deprecated', 'warning', 'system_warning', 'error'):
if event_data.get(key, False):
event = key
break
event_dict = dict(event=event, event_data=event_data)
for key in event_data.keys():
if key in ('job_id', 'ad_hoc_command_id', 'uuid', 'parent_uuid', 'created', 'artifact_data'):
event_dict[key] = event_data.pop(key)
elif key in ('verbosity', 'pid'):
event_dict[key] = event_data[key]
return event_dict
def get_end_dict(self):
return {}
def dump(self, fileobj, data, max_width=78):
b64data = base64.b64encode(json.dumps(data))
fileobj.write(u'\x1b[K')
for offset in xrange(0, len(b64data), max_width):
chunk = b64data[offset:offset + max_width]
escaped_chunk = u'{}\x1b[{}D'.format(chunk, len(chunk))
fileobj.write(escaped_chunk)
fileobj.write(u'\x1b[K')
def dump_begin(self, fileobj):
self.dump(fileobj, self.get_begin_dict())
def dump_end(self, fileobj):
self.dump(fileobj, self.get_end_dict())
event_context = EventContext()

View File

@ -0,0 +1,28 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import os
# Ansible
import ansible
# Because of the way Ansible loads plugins, it's not possible to import
# ansible.plugins.callback.minimal when being loaded as the minimal plugin. Ugh.
execfile(os.path.join(os.path.dirname(ansible.__file__), 'plugins', 'callback', 'minimal.py'))

View File

@ -0,0 +1,488 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import contextlib
import copy
import re
import sys
import uuid
# Ansible
from ansible.plugins.callback import CallbackBase
from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule
# Tower Display Callback
from tower_display_callback.events import event_context
from tower_display_callback.minimal import CallbackModule as MinimalCallbackModule
class BaseCallbackModule(CallbackBase):
'''
Callback module for logging ansible/ansible-playbook events.
'''
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
# These events should never have an associated play.
EVENTS_WITHOUT_PLAY = [
'playbook_on_start',
'playbook_on_stats',
]
# These events should never have an associated task.
EVENTS_WITHOUT_TASK = EVENTS_WITHOUT_PLAY + [
'playbook_on_setup',
'playbook_on_notify',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
]
CENSOR_FIELD_WHITELIST = [
'msg',
'failed',
'changed',
'results',
'start',
'end',
'delta',
'cmd',
'_ansible_no_log',
'rc',
'failed_when_result',
'skipped',
'skip_reason',
]
def __init__(self):
super(BaseCallbackModule, self).__init__()
self.task_uuids = set()
def censor_result(self, res, no_log=False):
if not isinstance(res, dict):
if no_log:
return "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
return res
if res.get('_ansible_no_log', no_log):
new_res = {}
for k in self.CENSOR_FIELD_WHITELIST:
if k in res:
new_res[k] = res[k]
if k == 'cmd' and k in res:
if isinstance(res['cmd'], list):
res['cmd'] = ' '.join(res['cmd'])
if re.search(r'\s', res['cmd']):
new_res['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$',
r'\1 <censored>',
res['cmd'])
new_res['censored'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
res = new_res
if 'results' in res:
if isinstance(res['results'], list):
for i in xrange(len(res['results'])):
res['results'][i] = self.censor_result(res['results'][i], res.get('_ansible_no_log', no_log))
elif res.get('_ansible_no_log', False):
res['results'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
return res
@contextlib.contextmanager
def capture_event_data(self, event, **event_data):
event_data.setdefault('uuid', str(uuid.uuid4()))
if 'res' in event_data:
event_data['res'] = self.censor_result(copy.deepcopy(event_data['res']))
res = event_data.get('res', None)
if res and isinstance(res, dict):
if 'artifact_data' in res:
event_data['artifact_data'] = res['artifact_data']
if event not in self.EVENTS_WITHOUT_TASK:
task = event_data.pop('task', None)
else:
task = None
try:
event_context.add_local(event=event, **event_data)
if task:
self.set_task(task, local=True)
event_context.dump_begin(sys.stdout)
yield
finally:
event_context.dump_end(sys.stdout)
if task:
self.clear_task(local=True)
event_context.remove_local(event=None, **event_data)
def set_playbook(self, playbook):
# NOTE: Ansible doesn't generate a UUID for playbook_on_start so do it for them.
self.playbook_uuid = str(uuid.uuid4())
file_name = getattr(playbook, '_file_name', '???')
event_context.add_global(playbook=file_name, playbook_uuid=self.playbook_uuid)
self.clear_play()
def set_play(self, play):
if hasattr(play, 'hosts'):
if isinstance(play.hosts, list):
pattern = ','.join(play.hosts)
else:
pattern = play.hosts
else:
pattern = ''
name = play.get_name().strip() or pattern
event_context.add_global(play=name, play_uuid=str(play._uuid), play_pattern=pattern)
self.clear_task()
def clear_play(self):
event_context.remove_global(play=None, play_uuid=None, play_pattern=None)
self.clear_task()
def set_task(self, task, local=False):
# FIXME: Task is "global" unless using free strategy!
task_ctx = dict(
task=(task.name or task.action),
task_path=task.get_path(),
task_uuid=str(task._uuid),
task_action=task.action,
)
if not task.no_log:
task_args = ', '.join(('%s=%s' % a for a in task.args.items()))
task_ctx['task_args'] = task_args
if getattr(task, '_role', None):
task_role = task._role._role_name
else:
task_role = getattr(task, 'role_name', '')
if task_role:
task_ctx['role'] = task_role
if local:
event_context.add_local(**task_ctx)
else:
event_context.add_global(**task_ctx)
def clear_task(self, local=False):
task_ctx = dict(task=None, task_path=None, task_uuid=None, task_action=None, task_args=None, role=None)
if local:
event_context.remove_local(**task_ctx)
else:
event_context.remove_global(**task_ctx)
def v2_playbook_on_start(self, playbook):
self.set_playbook(playbook)
event_data = dict(
uuid=self.playbook_uuid,
)
with self.capture_event_data('playbook_on_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_start(playbook)
def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None,
encrypt=None, confirm=False, salt_size=None,
salt=None, default=None):
event_data = dict(
varname=varname,
private=private,
prompt=prompt,
encrypt=encrypt,
confirm=confirm,
salt_size=salt_size,
salt=salt,
default=default,
)
with self.capture_event_data('playbook_on_vars_prompt', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_vars_prompt(
varname, private, prompt, encrypt, confirm, salt_size, salt,
default,
)
def v2_playbook_on_include(self, included_file):
event_data = dict(
included_file=included_file,
)
with self.capture_event_data('playbook_on_include', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_include(included_file)
def v2_playbook_on_play_start(self, play):
self.set_play(play)
if hasattr(play, 'hosts'):
if isinstance(play.hosts, list):
pattern = ','.join(play.hosts)
else:
pattern = play.hosts
else:
pattern = ''
name = play.get_name().strip() or pattern
event_data = dict(
name=name,
pattern=pattern,
uuid=str(play._uuid),
)
with self.capture_event_data('playbook_on_play_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_play_start(play)
def v2_playbook_on_import_for_host(self, result, imported_file):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_import_for_host'):
super(BaseCallbackModule, self).v2_playbook_on_import_for_host(result, imported_file)
def v2_playbook_on_not_import_for_host(self, result, missing_file):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_not_import_for_host'):
super(BaseCallbackModule, self).v2_playbook_on_not_import_for_host(result, missing_file)
def v2_playbook_on_setup(self):
# NOTE: Not used by Ansible 2.x.
with self.capture_event_data('playbook_on_setup'):
super(BaseCallbackModule, self).v2_playbook_on_setup()
def v2_playbook_on_task_start(self, task, is_conditional):
# FIXME: Flag task path output as vv.
task_uuid = str(task._uuid)
if task_uuid in self.task_uuids:
# FIXME: When this task UUID repeats, it means the play is using the
# free strategy, so different hosts may be running different tasks
# within a play.
return
self.task_uuids.add(task_uuid)
self.set_task(task)
event_data = dict(
task=task,
name=task.get_name(),
is_conditional=is_conditional,
uuid=task_uuid,
)
with self.capture_event_data('playbook_on_task_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_task_start(task, is_conditional)
def v2_playbook_on_cleanup_task_start(self, task):
# NOTE: Not used by Ansible 2.x.
self.set_task(task)
event_data = dict(
task=task,
name=task.get_name(),
uuid=str(task._uuid),
is_conditional=True,
)
with self.capture_event_data('playbook_on_task_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_cleanup_task_start(task)
def v2_playbook_on_handler_task_start(self, task):
# NOTE: Re-using playbook_on_task_start event for this v2-specific
# event, but setting is_conditional=True, which is how v1 identified a
# task run as a handler.
self.set_task(task)
event_data = dict(
task=task,
name=task.get_name(),
uuid=str(task._uuid),
is_conditional=True,
)
with self.capture_event_data('playbook_on_task_start', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_handler_task_start(task)
def v2_playbook_on_no_hosts_matched(self):
with self.capture_event_data('playbook_on_no_hosts_matched'):
super(BaseCallbackModule, self).v2_playbook_on_no_hosts_matched()
def v2_playbook_on_no_hosts_remaining(self):
with self.capture_event_data('playbook_on_no_hosts_remaining'):
super(BaseCallbackModule, self).v2_playbook_on_no_hosts_remaining()
def v2_playbook_on_notify(self, result, handler):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
host=result._host.get_name(),
task=result._task,
handler=handler,
)
with self.capture_event_data('playbook_on_notify', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_notify(result, handler)
def v2_playbook_on_stats(self, stats):
self.clear_play()
# FIXME: Add count of plays/tasks.
event_data = dict(
changed=stats.changed,
dark=stats.dark,
failures=stats.failures,
ok=stats.ok,
processed=stats.processed,
skipped=stats.skipped,
)
with self.capture_event_data('playbook_on_stats', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_stats(stats)
def v2_runner_on_ok(self, result):
# FIXME: Display detailed results or not based on verbosity.
event_data = dict(
host=result._host.get_name(),
remote_addr=result._host.address,
task=result._task,
res=result._result,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
)
with self.capture_event_data('runner_on_ok', **event_data):
super(BaseCallbackModule, self).v2_runner_on_ok(result)
def v2_runner_on_failed(self, result, ignore_errors=False):
# FIXME: Add verbosity for exception/results output.
event_data = dict(
host=result._host.get_name(),
remote_addr=result._host.address,
res=result._result,
task=result._task,
ignore_errors=ignore_errors,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
)
with self.capture_event_data('runner_on_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_on_failed(result, ignore_errors)
def v2_runner_on_skipped(self, result):
event_data = dict(
host=result._host.get_name(),
remote_addr=result._host.address,
task=result._task,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
)
with self.capture_event_data('runner_on_skipped', **event_data):
super(BaseCallbackModule, self).v2_runner_on_skipped(result)
def v2_runner_on_unreachable(self, result):
event_data = dict(
host=result._host.get_name(),
remote_addr=result._host.address,
task=result._task,
res=result._result,
)
with self.capture_event_data('runner_on_unreachable', **event_data):
super(BaseCallbackModule, self).v2_runner_on_unreachable(result)
def v2_runner_on_no_hosts(self, task):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
task=task,
)
with self.capture_event_data('runner_on_no_hosts', **event_data):
super(BaseCallbackModule, self).v2_runner_on_no_hosts(task)
def v2_runner_on_async_poll(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
jid=result._result.get('ansible_job_id'),
)
with self.capture_event_data('runner_on_async_poll', **event_data):
super(BaseCallbackModule, self).v2_runner_on_async_poll(result)
def v2_runner_on_async_ok(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
jid=result._result.get('ansible_job_id'),
)
with self.capture_event_data('runner_on_async_ok', **event_data):
super(BaseCallbackModule, self).v2_runner_on_async_ok(result)
def v2_runner_on_async_failed(self, result):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
jid=result._result.get('ansible_job_id'),
)
with self.capture_event_data('runner_on_async_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_on_async_failed(result)
def v2_runner_on_file_diff(self, result, diff):
# NOTE: Not used by Ansible 2.x.
event_data = dict(
host=result._host.get_name(),
task=result._task,
diff=diff,
)
with self.capture_event_data('runner_on_file_diff', **event_data):
super(BaseCallbackModule, self).v2_runner_on_file_diff(result, diff)
def v2_on_file_diff(self, result):
# NOTE: Logged as runner_on_file_diff.
event_data = dict(
host=result._host.get_name(),
task=result._task,
diff=result._result.get('diff'),
)
with self.capture_event_data('runner_on_file_diff', **event_data):
super(BaseCallbackModule, self).v2_on_file_diff(result)
def v2_runner_item_on_ok(self, result):
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
)
with self.capture_event_data('runner_item_on_ok', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_ok(result)
def v2_runner_item_on_failed(self, result):
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
)
with self.capture_event_data('runner_item_on_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_failed(result)
def v2_runner_item_on_skipped(self, result):
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
)
with self.capture_event_data('runner_item_on_skipped', **event_data):
super(BaseCallbackModule, self).v2_runner_item_on_skipped(result)
def v2_runner_retry(self, result):
event_data = dict(
host=result._host.get_name(),
task=result._task,
res=result._result,
)
with self.capture_event_data('runner_retry', **event_data):
super(BaseCallbackModule, self).v2_runner_retry(result)
class TowerDefaultCallbackModule(BaseCallbackModule, DefaultCallbackModule):
CALLBACK_NAME = 'tower_display'
class TowerMinimalCallbackModule(BaseCallbackModule, MinimalCallbackModule):
CALLBACK_NAME = 'minimal'
def v2_playbook_on_play_start(self, play):
pass
def v2_playbook_on_task_start(self, task, is_conditional):
self.set_task(task)

View File

@ -112,7 +112,7 @@ register(
register(
'AWX_PROOT_ENABLED',
field_class=fields.BooleanField,
label=_('Enable PRoot for Job Execution'),
label=_('Enable job isloation'),
help_text=_('Isolates an Ansible job from protected parts of the Tower system to prevent exposing sensitive information.'),
category=_('Jobs'),
category_slug='jobs',
@ -121,8 +121,8 @@ register(
register(
'AWX_PROOT_BASE_PATH',
field_class=fields.CharField,
label=_('Base PRoot execution path'),
help_text=_('The location that PRoot will create its temporary working directory.'),
label=_('Job isolation execution path'),
help_text=_('Create temporary working directories for isolated jobs in this location.'),
category=_('Jobs'),
category_slug='jobs',
)
@ -130,8 +130,8 @@ register(
register(
'AWX_PROOT_HIDE_PATHS',
field_class=fields.StringListField,
label=_('Paths to hide from PRoot jobs'),
help_text=_('Extra paths to hide from PRoot isolated processes.'),
label=_('Paths to hide from isolated jobs'),
help_text=_('Additional paths to hide from isolated processes.'),
category=_('Jobs'),
category_slug='jobs',
)
@ -139,8 +139,8 @@ register(
register(
'AWX_PROOT_SHOW_PATHS',
field_class=fields.StringListField,
label=_('Paths to expose to PRoot jobs'),
help_text=_('Explicit whitelist of paths to expose to PRoot jobs.'),
label=_('Paths to expose to isolated jobs'),
help_text=_('Whitelist of paths that would otherwise be hidden to expose to isolated jobs.'),
category=_('Jobs'),
category_slug='jobs',
)

View File

@ -2,9 +2,7 @@
# All Rights Reserved.
# Python
import datetime
import logging
import json
from kombu import Connection, Exchange, Queue
from kombu.mixins import ConsumerMixin
@ -12,10 +10,7 @@ from kombu.mixins import ConsumerMixin
# Django
from django.conf import settings
from django.core.management.base import NoArgsCommand
from django.core.cache import cache
from django.db import DatabaseError
from django.utils.dateparse import parse_datetime
from django.utils.timezone import FixedOffset
# AWX
from awx.main.models import * # noqa
@ -36,112 +31,26 @@ class CallbackBrokerWorker(ConsumerMixin):
def process_task(self, body, message):
try:
if "event" not in body:
raise Exception("Payload does not have an event")
if "job_id" not in body:
raise Exception("Payload does not have a job_id")
if 'event' not in body:
raise Exception('Payload does not have an event')
if 'job_id' not in body and 'ad_hoc_command_id' not in body:
raise Exception('Payload does not have a job_id or ad_hoc_command_id')
if settings.DEBUG:
logger.info("Body: {}".format(body))
logger.info("Message: {}".format(message))
self.process_job_event(body)
logger.info('Body: {}'.format(body))
logger.info('Message: {}'.format(message))
try:
if 'job_id' in body:
JobEvent.create_from_data(**body)
elif 'ad_hoc_command_id' in body:
AdHocCommandEvent.create_from_data(**body)
except DatabaseError as e:
logger.error('Database Error Saving Job Event: {}'.format(e))
except Exception as exc:
import traceback
traceback.print_exc()
logger.error('Callback Task Processor Raised Exception: %r', exc)
message.ack()
def process_job_event(self, payload):
# Get the correct "verbose" value from the job.
# If for any reason there's a problem, just use 0.
if 'ad_hoc_command_id' in payload:
event_type_key = 'ad_hoc_command_id'
event_object_type = AdHocCommand
else:
event_type_key = 'job_id'
event_object_type = Job
try:
verbose = event_object_type.objects.get(id=payload[event_type_key]).verbosity
except Exception as e:
verbose=0
# TODO: cache
# Convert the datetime for the job event's creation appropriately,
# and include a time zone for it.
#
# In the event of any issue, throw it out, and Django will just save
# the current time.
try:
if not isinstance(payload['created'], datetime.datetime):
payload['created'] = parse_datetime(payload['created'])
if not payload['created'].tzinfo:
payload['created'] = payload['created'].replace(tzinfo=FixedOffset(0))
except (KeyError, ValueError):
payload.pop('created', None)
event_uuid = payload.get("uuid", '')
parent_event_uuid = payload.get("parent_uuid", '')
artifact_data = payload.get("artifact_data", None)
# Sanity check: Don't honor keys that we don't recognize.
for key in payload.keys():
if key not in (event_type_key, 'event', 'event_data',
'created', 'counter', 'uuid'):
payload.pop(key)
try:
# If we're not in verbose mode, wipe out any module
# arguments.
res = payload['event_data'].get('res', {})
if isinstance(res, dict):
i = res.get('invocation', {})
if verbose == 0 and 'module_args' in i:
i['module_args'] = ''
if 'ad_hoc_command_id' in payload:
AdHocCommandEvent.objects.create(**data)
return
j = JobEvent(**payload)
if payload['event'] == 'playbook_on_start':
j.save()
cache.set("{}_{}".format(payload['job_id'], event_uuid), j.id, 300)
return
else:
if parent_event_uuid:
parent_id = cache.get("{}_{}".format(payload['job_id'], parent_event_uuid), None)
if parent_id is None:
parent_id_obj = JobEvent.objects.filter(uuid=parent_event_uuid, job_id=payload['job_id'])
if parent_id_obj.exists(): # Problematic if not there, means the parent hasn't been written yet... TODO
j.parent_id = parent_id_obj[0].id
print("Settings cache: {}_{} with value {}".format(payload['job_id'], parent_event_uuid, j.parent_id))
cache.set("{}_{}".format(payload['job_id'], parent_event_uuid), j.parent_id, 300)
else:
print("Cache hit")
j.parent_id = parent_id
j.save(post_process=True)
if event_uuid:
cache.set("{}_{}".format(payload['job_id'], event_uuid), j.id, 300)
except DatabaseError as e:
logger.error("Database Error Saving Job Event: {}".format(e))
if artifact_data:
try:
self.process_artifacts(artifact_data, res, payload)
except DatabaseError as e:
logger.error("Database Error Saving Job Artifacts: {}".format(e))
def process_artifacts(self, artifact_data, res, payload):
artifact_dict = json.loads(artifact_data)
if res and isinstance(res, dict):
if res.get('_ansible_no_log', False):
artifact_dict['_ansible_no_log'] = True
if artifact_data is not None:
parent_job = Job.objects.filter(pk=payload['job_id']).first()
if parent_job is not None and parent_job.artifacts != artifact_dict:
parent_job.artifacts = artifact_dict
parent_job.save(update_fields=['artifacts'])
class Command(NoArgsCommand):
'''
@ -158,4 +67,3 @@ class Command(NoArgsCommand):
worker.run()
except KeyboardInterrupt:
print('Terminating Callback Receiver')

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0044_v310_project_playbook_files'),
]
operations = [
migrations.AddField(
model_name='adhoccommandevent',
name='end_line',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AddField(
model_name='adhoccommandevent',
name='start_line',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AddField(
model_name='adhoccommandevent',
name='stdout',
field=models.TextField(default=b'', editable=False),
),
migrations.AddField(
model_name='adhoccommandevent',
name='uuid',
field=models.CharField(default=b'', max_length=1024, editable=False),
),
migrations.AddField(
model_name='adhoccommandevent',
name='verbosity',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AddField(
model_name='jobevent',
name='end_line',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AddField(
model_name='jobevent',
name='playbook',
field=models.CharField(default=b'', max_length=1024, editable=False),
),
migrations.AddField(
model_name='jobevent',
name='start_line',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AddField(
model_name='jobevent',
name='stdout',
field=models.TextField(default=b'', editable=False),
),
migrations.AddField(
model_name='jobevent',
name='verbosity',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AlterField(
model_name='adhoccommandevent',
name='counter',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AlterField(
model_name='adhoccommandevent',
name='event',
field=models.CharField(max_length=100, choices=[(b'runner_on_failed', 'Host Failed'), (b'runner_on_ok', 'Host OK'), (b'runner_on_unreachable', 'Host Unreachable'), (b'runner_on_skipped', 'Host Skipped'), (b'debug', 'Debug'), (b'verbose', 'Verbose'), (b'deprecated', 'Deprecated'), (b'warning', 'Warning'), (b'system_warning', 'System Warning'), (b'error', 'Error')]),
),
migrations.AlterField(
model_name='jobevent',
name='counter',
field=models.PositiveIntegerField(default=0, editable=False),
),
migrations.AlterField(
model_name='jobevent',
name='event',
field=models.CharField(max_length=100, choices=[(b'runner_on_failed', 'Host Failed'), (b'runner_on_ok', 'Host OK'), (b'runner_on_error', 'Host Failure'), (b'runner_on_skipped', 'Host Skipped'), (b'runner_on_unreachable', 'Host Unreachable'), (b'runner_on_no_hosts', 'No Hosts Remaining'), (b'runner_on_async_poll', 'Host Polling'), (b'runner_on_async_ok', 'Host Async OK'), (b'runner_on_async_failed', 'Host Async Failure'), (b'runner_item_on_ok', 'Item OK'), (b'runner_item_on_failed', 'Item Failed'), (b'runner_item_on_skipped', 'Item Skipped'), (b'runner_retry', 'Host Retry'), (b'runner_on_file_diff', 'File Difference'), (b'playbook_on_start', 'Playbook Started'), (b'playbook_on_notify', 'Running Handlers'), (b'playbook_on_include', 'Including File'), (b'playbook_on_no_hosts_matched', 'No Hosts Matched'), (b'playbook_on_no_hosts_remaining', 'No Hosts Remaining'), (b'playbook_on_task_start', 'Task Started'), (b'playbook_on_vars_prompt', 'Variables Prompted'), (b'playbook_on_setup', 'Gathering Facts'), (b'playbook_on_import_for_host', 'internal: on Import for Host'), (b'playbook_on_not_import_for_host', 'internal: on Not Import for Host'), (b'playbook_on_play_start', 'Play Started'), (b'playbook_on_stats', 'Playbook Complete'), (b'debug', 'Debug'), (b'verbose', 'Verbose'), (b'deprecated', 'Deprecated'), (b'warning', 'Warning'), (b'system_warning', 'System Warning'), (b'error', 'Error')]),
),
migrations.AlterUniqueTogether(
name='adhoccommandevent',
unique_together=set([]),
),
migrations.AlterIndexTogether(
name='adhoccommandevent',
index_together=set([('ad_hoc_command', 'event'), ('ad_hoc_command', 'uuid'), ('ad_hoc_command', 'end_line'), ('ad_hoc_command', 'start_line')]),
),
migrations.AlterIndexTogether(
name='jobevent',
index_together=set([('job', 'event'), ('job', 'parent'), ('job', 'start_line'), ('job', 'uuid'), ('job', 'end_line')]),
),
]

View File

@ -2,6 +2,7 @@
# All Rights Reserved.
# Python
import datetime
import hmac
import json
import logging
@ -10,7 +11,9 @@ from urlparse import urljoin
# Django
from django.conf import settings
from django.db import models
from django.utils.dateparse import parse_datetime
from django.utils.text import Truncator
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
@ -257,24 +260,38 @@ class AdHocCommandEvent(CreatedModifiedModel):
('runner_on_ok', _('Host OK'), False),
('runner_on_unreachable', _('Host Unreachable'), True),
# Tower won't see no_hosts (check is done earlier without callback).
#('runner_on_no_hosts', _('No Hosts Matched'), False),
# ('runner_on_no_hosts', _('No Hosts Matched'), False),
# Tower will see skipped (when running in check mode for a module that
# does not support check mode).
('runner_on_skipped', _('Host Skipped'), False),
# Tower does not support async for ad hoc commands.
#('runner_on_async_poll', _('Host Polling'), False),
#('runner_on_async_ok', _('Host Async OK'), False),
#('runner_on_async_failed', _('Host Async Failure'), True),
# Tower does not yet support --diff mode
#('runner_on_file_diff', _('File Difference'), False),
# Tower does not support async for ad hoc commands (not used in v2).
# ('runner_on_async_poll', _('Host Polling'), False),
# ('runner_on_async_ok', _('Host Async OK'), False),
# ('runner_on_async_failed', _('Host Async Failure'), True),
# Tower does not yet support --diff mode.
# ('runner_on_file_diff', _('File Difference'), False),
# Additional event types for captured stdout not directly related to
# runner events.
('debug', _('Debug'), False),
('verbose', _('Verbose'), False),
('deprecated', _('Deprecated'), False),
('warning', _('Warning'), False),
('system_warning', _('System Warning'), False),
('error', _('Error'), False),
]
FAILED_EVENTS = [x[0] for x in EVENT_TYPES if x[2]]
EVENT_CHOICES = [(x[0], x[1]) for x in EVENT_TYPES]
class Meta:
app_label = 'main'
unique_together = [('ad_hoc_command', 'host_name')]
ordering = ('-pk',)
index_together = [
('ad_hoc_command', 'event'),
('ad_hoc_command', 'uuid'),
('ad_hoc_command', 'start_line'),
('ad_hoc_command', 'end_line'),
]
ad_hoc_command = models.ForeignKey(
'AdHocCommand',
@ -311,8 +328,30 @@ class AdHocCommandEvent(CreatedModifiedModel):
default=False,
editable=False,
)
uuid = models.CharField(
max_length=1024,
default='',
editable=False,
)
counter = models.PositiveIntegerField(
default=0,
editable=False,
)
stdout = models.TextField(
default='',
editable=False,
)
verbosity = models.PositiveIntegerField(
default=0,
editable=False,
)
start_line = models.PositiveIntegerField(
default=0,
editable=False,
)
end_line = models.PositiveIntegerField(
default=0,
editable=False,
)
def get_absolute_url(self):
@ -350,3 +389,28 @@ class AdHocCommandEvent(CreatedModifiedModel):
except (IndexError, AttributeError):
pass
super(AdHocCommandEvent, self).save(*args, **kwargs)
@classmethod
def create_from_data(self, **kwargs):
# Convert the datetime for the ad hoc command event's creation
# appropriately, and include a time zone for it.
#
# In the event of any issue, throw it out, and Django will just save
# the current time.
try:
if not isinstance(kwargs['created'], datetime.datetime):
kwargs['created'] = parse_datetime(kwargs['created'])
if not kwargs['created'].tzinfo:
kwargs['created'] = kwargs['created'].replace(tzinfo=utc)
except (KeyError, ValueError):
kwargs.pop('created', None)
# Sanity check: Don't honor keys that we don't recognize.
valid_keys = {'ad_hoc_command_id', 'event', 'event_data', 'created',
'counter', 'uuid', 'stdout', 'start_line', 'end_line',
'verbosity'}
for key in kwargs.keys():
if key not in valid_keys:
kwargs.pop(key)
return AdHocCommandEvent.objects.create(**kwargs)

View File

@ -2,6 +2,7 @@
# All Rights Reserved.
# Python
import datetime
import hmac
import json
import yaml
@ -11,8 +12,12 @@ from urlparse import urljoin
# Django
from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.db.models import Q, Count
from django.utils.dateparse import parse_datetime
from django.utils.encoding import force_text
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
@ -931,24 +936,29 @@ class JobEvent(CreatedModifiedModel):
# - playbook_on_vars_prompt (for each play, but before play starts, we
# currently don't handle responding to these prompts)
# - playbook_on_play_start (once for each play)
# - playbook_on_import_for_host
# - playbook_on_not_import_for_host
# - playbook_on_import_for_host (not logged, not used for v2)
# - playbook_on_not_import_for_host (not logged, not used for v2)
# - playbook_on_no_hosts_matched
# - playbook_on_no_hosts_remaining
# - playbook_on_setup
# - playbook_on_include (only v2 - only used for handlers?)
# - playbook_on_setup (not used for v2)
# - runner_on*
# - playbook_on_task_start (once for each task within a play)
# - runner_on_failed
# - runner_on_ok
# - runner_on_error
# - runner_on_error (not used for v2)
# - runner_on_skipped
# - runner_on_unreachable
# - runner_on_no_hosts
# - runner_on_async_poll
# - runner_on_async_ok
# - runner_on_async_failed
# - runner_on_file_diff
# - playbook_on_notify (once for each notification from the play)
# - runner_on_no_hosts (not used for v2)
# - runner_on_async_poll (not used for v2)
# - runner_on_async_ok (not used for v2)
# - runner_on_async_failed (not used for v2)
# - runner_on_file_diff (v2 event is v2_on_file_diff)
# - runner_item_on_ok (v2 only)
# - runner_item_on_failed (v2 only)
# - runner_item_on_skipped (v2 only)
# - runner_retry (v2 only)
# - playbook_on_notify (once for each notification from the play, not used for v2)
# - playbook_on_stats
EVENT_TYPES = [
@ -962,22 +972,34 @@ class JobEvent(CreatedModifiedModel):
(3, 'runner_on_async_poll', _('Host Polling'), False),
(3, 'runner_on_async_ok', _('Host Async OK'), False),
(3, 'runner_on_async_failed', _('Host Async Failure'), True),
# AWX does not yet support --diff mode
(3, 'runner_item_on_ok', _('Item OK'), False),
(3, 'runner_item_on_failed', _('Item Failed'), True),
(3, 'runner_item_on_skipped', _('Item Skipped'), False),
(3, 'runner_retry', _('Host Retry'), False),
# Tower does not yet support --diff mode.
(3, 'runner_on_file_diff', _('File Difference'), False),
(0, 'playbook_on_start', _('Playbook Started'), False),
(2, 'playbook_on_notify', _('Running Handlers'), False),
(2, 'playbook_on_include', _('Including File'), False),
(2, 'playbook_on_no_hosts_matched', _('No Hosts Matched'), False),
(2, 'playbook_on_no_hosts_remaining', _('No Hosts Remaining'), False),
(2, 'playbook_on_task_start', _('Task Started'), False),
# AWX does not yet support vars_prompt (and will probably hang :)
# Tower does not yet support vars_prompt (and will probably hang :)
(1, 'playbook_on_vars_prompt', _('Variables Prompted'), False),
(2, 'playbook_on_setup', _('Gathering Facts'), False),
# callback will not record this
(2, 'playbook_on_import_for_host', _('internal: on Import for Host'), False),
# callback will not record this
(2, 'playbook_on_not_import_for_host', _('internal: on Not Import for Host'), False),
(1, 'playbook_on_play_start', _('Play Started'), False),
(1, 'playbook_on_stats', _('Playbook Complete'), False),
# Additional event types for captured stdout not directly related to
# playbook or runner events.
(0, 'debug', _('Debug'), False),
(0, 'verbose', _('Verbose'), False),
(0, 'deprecated', _('Deprecated'), False),
(0, 'warning', _('Warning'), False),
(0, 'system_warning', _('System Warning'), False),
(0, 'error', _('Error'), True),
]
FAILED_EVENTS = [x[1] for x in EVENT_TYPES if x[3]]
EVENT_CHOICES = [(x[1], x[2]) for x in EVENT_TYPES]
@ -986,6 +1008,13 @@ class JobEvent(CreatedModifiedModel):
class Meta:
app_label = 'main'
ordering = ('pk',)
index_together = [
('job', 'event'),
('job', 'uuid'),
('job', 'start_line'),
('job', 'end_line'),
('job', 'parent'),
]
job = models.ForeignKey(
'Job',
@ -1032,12 +1061,17 @@ class JobEvent(CreatedModifiedModel):
related_name='job_events',
editable=False,
)
playbook = models.CharField(
max_length=1024,
default='',
editable=False,
)
play = models.CharField(
max_length=1024,
default='',
editable=False,
)
role = models.CharField( # FIXME: Determine from callback or task name.
role = models.CharField(
max_length=1024,
default='',
editable=False,
@ -1057,8 +1091,24 @@ class JobEvent(CreatedModifiedModel):
)
counter = models.PositiveIntegerField(
default=0,
editable=False,
)
stdout = models.TextField(
default='',
editable=False,
)
verbosity = models.PositiveIntegerField(
default=0,
editable=False,
)
start_line = models.PositiveIntegerField(
default=0,
editable=False,
)
end_line = models.PositiveIntegerField(
default=0,
editable=False,
)
def get_absolute_url(self):
return reverse('api:job_event_detail', args=(self.pk,))
@ -1119,7 +1169,8 @@ class JobEvent(CreatedModifiedModel):
pass
return msg
def _find_parent(self):
def _find_parent_id(self):
# Find the (most likely) parent event for this event.
parent_events = set()
if self.event in ('playbook_on_play_start', 'playbook_on_stats',
'playbook_on_vars_prompt'):
@ -1135,101 +1186,55 @@ class JobEvent(CreatedModifiedModel):
parent_events.add('playbook_on_setup')
parent_events.add('playbook_on_task_start')
if parent_events:
try:
qs = JobEvent.objects.filter(job_id=self.job_id)
if self.pk:
qs = qs.filter(pk__lt=self.pk, event__in=parent_events)
else:
qs = qs.filter(event__in=parent_events)
return qs.order_by('-pk')[0]
except IndexError:
pass
return None
qs = JobEvent.objects.filter(job_id=self.job_id, event__in=parent_events).order_by('-pk')
if self.pk:
qs = qs.filter(pk__lt=self.pk)
return qs.only('id').values_list('id', flat=True).first()
def save(self, *args, **kwargs):
from awx.main.models.inventory import Host
# If update_fields has been specified, add our field names to it,
# if it hasn't been specified, then we're just doing a normal save.
update_fields = kwargs.get('update_fields', [])
# Skip normal checks on save if we're only updating failed/changed
# flags triggered from a child event.
from_parent_update = kwargs.pop('from_parent_update', False)
if not from_parent_update:
res = self.event_data.get('res', None)
# Workaround for Ansible 1.2, where the runner_on_async_ok event is
# created even when the async task failed. Change the event to be
# correct.
if self.event == 'runner_on_async_ok':
try:
if res.get('failed', False) or res.get('rc', 0) != 0:
self.event = 'runner_on_async_failed'
except (AttributeError, TypeError):
pass
if self.event in self.FAILED_EVENTS:
if not self.event_data.get('ignore_errors', False):
self.failed = True
if 'failed' not in update_fields:
update_fields.append('failed')
if isinstance(res, dict) and res.get('changed', False):
def _update_from_event_data(self):
# Update job event model fields from event data.
updated_fields = set()
job = self.job
verbosity = job.verbosity
event_data = self.event_data
res = event_data.get('res', None)
if self.event in self.FAILED_EVENTS and not event_data.get('ignore_errors', False):
self.failed = True
updated_fields.add('failed')
if isinstance(res, dict):
if res.get('changed', False):
self.changed = True
if 'changed' not in update_fields:
update_fields.append('changed')
if self.event == 'playbook_on_stats':
try:
failures_dict = self.event_data.get('failures', {})
dark_dict = self.event_data.get('dark', {})
self.failed = bool(sum(failures_dict.values()) +
sum(dark_dict.values()))
if 'failed' not in update_fields:
update_fields.append('failed')
changed_dict = self.event_data.get('changed', {})
self.changed = bool(sum(changed_dict.values()))
if 'changed' not in update_fields:
update_fields.append('changed')
except (AttributeError, TypeError):
pass
self.play = self.event_data.get('play', '').strip()
if 'play' not in update_fields:
update_fields.append('play')
self.task = self.event_data.get('task', '').strip()
if 'task' not in update_fields:
update_fields.append('task')
self.role = self.event_data.get('role', '').strip()
if 'role' not in update_fields:
update_fields.append('role')
self.host_name = self.event_data.get('host', '').strip()
if 'host_name' not in update_fields:
update_fields.append('host_name')
# Only update job event hierarchy and related models during post
# processing (after running job).
post_process = kwargs.pop('post_process', False)
if post_process:
updated_fields.add('changed')
# If we're not in verbose mode, wipe out any module arguments.
invocation = res.get('invocation', None)
if isinstance(invocation, dict) and verbosity == 0 and 'module_args' in invocation:
event_data['res']['invocation']['module_args'] = ''
self.event_data = event_data
update_fields.add('event_data')
if self.event == 'playbook_on_stats':
try:
if not self.host_id and self.host_name:
host_qs = Host.objects.filter(inventory__jobs__id=self.job_id, name=self.host_name)
host_id = host_qs.only('id').values_list('id', flat=True)
if host_id.exists():
self.host_id = host_id[0]
if 'host_id' not in update_fields:
update_fields.append('host_id')
except (IndexError, AttributeError):
failures_dict = event_data.get('failures', {})
dark_dict = event_data.get('dark', {})
self.failed = bool(sum(failures_dict.values()) +
sum(dark_dict.values()))
updated_fields.add('failed')
changed_dict = event_data.get('changed', {})
self.changed = bool(sum(changed_dict.values()))
updated_fields.add('changed')
except (AttributeError, TypeError):
pass
if self.parent is None:
self.parent = self._find_parent()
if 'parent' not in update_fields:
update_fields.append('parent')
super(JobEvent, self).save(*args, **kwargs)
if post_process and not from_parent_update:
self.update_parent_failed_and_changed()
# FIXME: The update_hosts() call (and its queries) are the current
# performance bottleneck....
if getattr(settings, 'CAPTURE_JOB_EVENT_HOSTS', False):
self.update_hosts()
self.update_host_summary_from_stats()
for field in ('playbook', 'play', 'task', 'role', 'host'):
value = force_text(event_data.get(field, '')).strip()
if field == 'host':
field = 'host_name'
if value != getattr(self, field):
setattr(self, field, value)
updated_fields.add(field)
return updated_fields
def update_parent_failed_and_changed(self):
# Propagage failed and changed flags to parent events.
if self.parent:
def _update_parent_failed_and_changed(self):
# Propagate failed and changed flags to parent events.
if self.parent_id:
parent = self.parent
update_fields = []
if self.failed and not parent.failed:
@ -1240,9 +1245,10 @@ class JobEvent(CreatedModifiedModel):
update_fields.append('changed')
if update_fields:
parent.save(update_fields=update_fields, from_parent_update=True)
parent.update_parent_failed_and_changed()
parent._update_parent_failed_and_changed()
def update_hosts(self, extra_host_pks=None):
def _update_hosts(self, extra_host_pks=None):
# Update job event hosts m2m from host_name, propagate to parent events.
from awx.main.models.inventory import Host
extra_host_pks = set(extra_host_pks or [])
hostnames = set()
@ -1256,16 +1262,14 @@ class JobEvent(CreatedModifiedModel):
pass
qs = Host.objects.filter(inventory__jobs__id=self.job_id)
qs = qs.filter(Q(name__in=hostnames) | Q(pk__in=extra_host_pks))
qs = qs.exclude(job_events__pk=self.id)
for host in qs.only('id'):
qs = qs.exclude(job_events__pk=self.id).only('id')
for host in qs:
self.hosts.add(host)
if self.parent:
self.parent.update_hosts(self.hosts.only('id').values_list('id', flat=True))
if self.parent_id:
self.parent._update_hosts(qs.values_list('id', flat=True))
def update_host_summary_from_stats(self):
def _update_host_summary_from_stats(self):
from awx.main.models.inventory import Host
if self.event != 'playbook_on_stats':
return
hostnames = set()
try:
for v in self.event_data.values():
@ -1276,7 +1280,6 @@ class JobEvent(CreatedModifiedModel):
qs = Host.objects.filter(inventory__jobs__id=self.job_id,
name__in=hostnames)
job = self.job
#for host in qs.only('id', 'name'):
for host in hostnames:
host_stats = {}
for stat in ('changed', 'dark', 'failures', 'ok', 'processed', 'skipped'):
@ -1300,6 +1303,112 @@ class JobEvent(CreatedModifiedModel):
job.inventory.update_computed_fields()
emit_channel_notification('jobs-summary', dict(group_name='jobs', unified_job_id=job.id))
def save(self, *args, **kwargs):
from awx.main.models.inventory import Host
# If update_fields has been specified, add our field names to it,
# if it hasn't been specified, then we're just doing a normal save.
update_fields = kwargs.get('update_fields', [])
# Update model fields and related objects unless we're only updating
# failed/changed flags triggered from a child event.
from_parent_update = kwargs.pop('from_parent_update', False)
if not from_parent_update:
# Update model fields from event data.
updated_fields = self._update_from_event_data()
for field in updated_fields:
if field not in update_fields:
update_fields.append(field)
# Update host related field from host_name.
if not self.host_id and self.host_name:
host_qs = Host.objects.filter(inventory__jobs__id=self.job_id, name=self.host_name)
host_id = host_qs.only('id').values_list('id', flat=True).first()
if host_id != self.host_id:
self.host_id = host_id
if 'host_id' not in update_fields:
update_fields.append('host_id')
# Update parent related field if not set.
if self.parent_id is None:
self.parent_id = self._find_parent_id()
if self.parent_id and 'parent_id' not in update_fields:
update_fields.append('parent_id')
super(JobEvent, self).save(*args, **kwargs)
# Update related objects after this event is saved.
if not from_parent_update:
if self.parent_id:
self._update_parent_failed_and_changed()
# FIXME: The update_hosts() call (and its queries) are the current
# performance bottleneck....
if getattr(settings, 'CAPTURE_JOB_EVENT_HOSTS', False):
self._update_hosts()
if self.event == 'playbook_on_stats':
self._update_host_summary_from_stats()
@classmethod
def create_from_data(self, **kwargs):
# Must have a job_id specified.
if not kwargs.get('job_id', None):
return
# Convert the datetime for the job event's creation appropriately,
# and include a time zone for it.
#
# In the event of any issue, throw it out, and Django will just save
# the current time.
try:
if not isinstance(kwargs['created'], datetime.datetime):
kwargs['created'] = parse_datetime(kwargs['created'])
if not kwargs['created'].tzinfo:
kwargs['created'] = kwargs['created'].replace(tzinfo=utc)
except (KeyError, ValueError):
kwargs.pop('created', None)
# Save UUID and parent UUID for determining parent-child relationship.
job_event_uuid = kwargs.get('uuid', None)
parent_event_uuid = kwargs.get('parent_uuid', None)
artifact_data = kwargs.get('artifact_data', None)
# Sanity check: Don't honor keys that we don't recognize.
valid_keys = {'job_id', 'event', 'event_data', 'playbook', 'play',
'role', 'task', 'created', 'counter', 'uuid', 'stdout',
'start_line', 'end_line', 'verbosity'}
for key in kwargs.keys():
if key not in valid_keys:
kwargs.pop(key)
# Try to find a parent event based on UUID.
if parent_event_uuid:
cache_key = '{}_{}'.format(kwargs['job_id'], parent_event_uuid)
parent_id = cache.get(cache_key)
if parent_id is None:
parent_id = JobEvent.objects.filter(job_id=kwargs['job_id'], uuid=parent_event_uuid).only('id').values_list('id', flat=True).first()
if parent_id:
print("Settings cache: {} with value {}".format(cache_key, parent_id))
cache.set(cache_key, parent_id, 300)
if parent_id:
kwargs['parent_id'] = parent_id
job_event = JobEvent.objects.create(**kwargs)
# Cache this job event ID vs. UUID for future parent lookups.
if job_event_uuid:
cache_key = '{}_{}'.format(kwargs['job_id'], job_event_uuid)
cache.set(cache_key, job_event.id, 300)
# Save artifact data to parent job (if provided).
if artifact_data:
artifact_dict = json.loads(artifact_data)
event_data = kwargs.get('event_data', None)
if event_data and isinstance(event_data, dict):
res = event_data.get('res', None)
if res and isinstance(res, dict):
if res.get('_ansible_no_log', False):
artifact_dict['_ansible_no_log'] = True
parent_job = Job.objects.filter(pk=kwargs['job_id']).first()
if parent_job and parent_job.artifacts != artifact_dict:
parent_job.artifacts = artifact_dict
parent_job.save(update_fields=['artifacts'])
return job_event
@classmethod
def get_startevent_queryset(cls, parent_task, starting_events, ordering=None):
'''

View File

@ -696,8 +696,11 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
return StringIO(msg['missing' if self.finished else 'pending'])
def _escape_ascii(self, content):
ansi_escape = re.compile(r'\x1b[^m]*m')
return ansi_escape.sub('', content)
# Remove ANSI escape sequences used to embed event data.
content = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', content)
# Remove ANSI color escape sequences.
content = re.sub(r'\x1b[^m]*m', '', content)
return content
def _result_stdout_raw(self, redact_sensitive=False, escape_ascii=False):
content = self.result_stdout_raw_handle().read()

View File

@ -93,6 +93,22 @@ class WorkflowNodeBase(CreatedModifiedModel):
data[fd] = self.char_prompts[fd]
return data
@property
def job_type(self):
return self.char_prompts.get('job_type', None)
@property
def job_tags(self):
return self.char_prompts.get('job_tags', None)
@property
def skip_tags(self):
return self.char_prompts.get('skip_tags', None)
@property
def limit(self):
return self.char_prompts.get('limit', None)
def get_prompts_warnings(self):
ujt_obj = self.unified_job_template
if ujt_obj is None:
@ -382,6 +398,9 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, JobNotificationMixin, Workflow
from awx.main.tasks import RunWorkflowJob
return RunWorkflowJob
def _has_failed(self):
return False
def socketio_emit_data(self):
return {}

View File

@ -1,9 +1,19 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import json
import logging
import os
# Django
from django.conf import settings
# Kombu
from kombu import Connection, Exchange, Producer
__all__ = ['FifoQueue', 'CallbackQueueDispatcher']
__all__ = ['FifoQueue']
# TODO: Figure out wtf to do with this class
class FifoQueue(object):
@ -33,3 +43,39 @@ class FifoQueue(object):
answer = None
if answer:
return json.loads(answer)
class CallbackQueueDispatcher(object):
def __init__(self):
self.callback_connection = getattr(settings, 'CALLBACK_CONNECTION', None)
self.connection_queue = getattr(settings, 'CALLBACK_QUEUE', '')
self.connection = None
self.exchange = None
self.logger = logging.getLogger('awx.main.queue.CallbackQueueDispatcher')
def dispatch(self, obj):
if not self.callback_connection or not self.connection_queue:
return
active_pid = os.getpid()
for retry_count in xrange(4):
try:
if not hasattr(self, 'connection_pid'):
self.connection_pid = active_pid
if self.connection_pid != active_pid:
self.connection = None
if self.connection is None:
self.connection = Connection(self.callback_connection)
self.exchange = Exchange(self.connection_queue, type='direct')
producer = Producer(self.connection)
producer.publish(obj,
serializer='json',
compression='bzip2',
exchange=self.exchange,
declare=[self.exchange],
routing_key=self.connection_queue)
return
except Exception, e:
self.logger.info('Publish Job Event Exception: %r, retry=%d', e,
retry_count, exc_info=True)

View File

@ -73,10 +73,12 @@ def process_finished_workflow_jobs(workflow_jobs):
dag = WorkflowDAG(workflow_job)
if dag.is_workflow_done():
with transaction.atomic():
# TODO: detect if wfj failed
workflow_job.status = 'completed'
if workflow_job._has_failed():
workflow_job.status = 'failed'
else:
workflow_job.status = 'successful'
workflow_job.save()
workflow_job.websocket_emit_status('completed')
workflow_job.websocket_emit_status(workflow_job.status)
def rebuild_graph():
"""Regenerate the task graph by refreshing known tasks from Tower, purging

View File

@ -48,9 +48,11 @@ from django.utils.translation import ugettext_lazy as _
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.models import * # noqa
from awx.main.models import UnifiedJob
from awx.main.queue import CallbackQueueDispatcher
from awx.main.task_engine import TaskEnhancer
from awx.main.utils import (get_ansible_version, get_ssh_version, decrypt_field, update_scm_url,
check_proot_installed, build_proot_temp_dir, wrap_args_with_proot)
check_proot_installed, build_proot_temp_dir, wrap_args_with_proot,
OutputEventFilter)
from awx.main.consumers import emit_channel_notification
__all__ = ['RunJob', 'RunSystemJob', 'RunProjectUpdate', 'RunInventoryUpdate',
@ -398,6 +400,8 @@ class BaseTask(Task):
if os.path.isdir(os.path.join(venv_libdir, python_ver)):
env['PYTHONPATH'] = os.path.join(venv_libdir, python_ver, "site-packages") + ":"
break
# Add awx/lib to PYTHONPATH.
env['PYTHONPATH'] = ':'.join(filter(None, [self.get_path_to('..', 'lib'), env.get('PYTHONPATH', '')]))
return env
def add_tower_venv(self, env):
@ -495,6 +499,17 @@ class BaseTask(Task):
'''
return OrderedDict()
def get_stdout_handle(self, instance):
'''
Return an open file object for capturing stdout.
'''
if not os.path.exists(settings.JOBOUTPUT_ROOT):
os.makedirs(settings.JOBOUTPUT_ROOT)
stdout_filename = os.path.join(settings.JOBOUTPUT_ROOT, "%d-%s.out" % (instance.pk, str(uuid.uuid1())))
stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
assert stdout_handle.name == stdout_filename
return stdout_handle
def run_pexpect(self, instance, args, cwd, env, passwords, stdout_handle,
output_replacements=None, extra_update_fields=None):
'''
@ -644,10 +659,7 @@ class BaseTask(Task):
cwd = self.build_cwd(instance, **kwargs)
env = self.build_env(instance, **kwargs)
safe_env = self.build_safe_env(instance, **kwargs)
if not os.path.exists(settings.JOBOUTPUT_ROOT):
os.makedirs(settings.JOBOUTPUT_ROOT)
stdout_filename = os.path.join(settings.JOBOUTPUT_ROOT, "%d-%s.out" % (pk, str(uuid.uuid1())))
stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
stdout_handle = self.get_stdout_handle(instance)
if self.should_use_proot(instance, **kwargs):
if not check_proot_installed():
raise RuntimeError('proot is not installed')
@ -661,7 +673,7 @@ class BaseTask(Task):
args = self.wrap_args_with_ssh_agent(args, ssh_key_path, ssh_auth_sock)
safe_args = self.wrap_args_with_ssh_agent(safe_args, ssh_key_path, ssh_auth_sock)
instance = self.update_model(pk, job_args=json.dumps(safe_args),
job_cwd=cwd, job_env=safe_env, result_stdout_file=stdout_filename)
job_cwd=cwd, job_env=safe_env, result_stdout_file=stdout_handle.name)
status, rc = self.run_pexpect(instance, args, cwd, env, kwargs['passwords'], stdout_handle,
extra_update_fields=extra_update_fields)
except Exception:
@ -780,6 +792,7 @@ class RunJob(BaseTask):
if job.project:
env['PROJECT_REVISION'] = job.project.scm_revision
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_path
env['ANSIBLE_STDOUT_CALLBACK'] = 'tower_display'
env['REST_API_URL'] = settings.INTERNAL_API_URL
env['REST_API_TOKEN'] = job.task_auth_token or ''
env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE
@ -975,6 +988,25 @@ class RunJob(BaseTask):
d[re.compile(r'^Vault password:\s*?$', re.M)] = 'vault_password'
return d
def get_stdout_handle(self, instance):
'''
Wrap stdout file object to capture events.
'''
stdout_handle = super(RunJob, self).get_stdout_handle(instance)
if getattr(settings, 'USE_CALLBACK_QUEUE', False):
dispatcher = CallbackQueueDispatcher()
def job_event_callback(event_data):
event_data.setdefault('job_id', instance.id)
dispatcher.dispatch(event_data)
else:
def job_event_callback(event_data):
event_data.setdefault('job_id', instance.id)
JobEvent.create_from_data(**event_data)
return OutputEventFilter(stdout_handle, job_event_callback)
def get_ssh_key_path(self, instance, **kwargs):
'''
If using an SSH key, return the path for use by ssh-agent.
@ -1020,11 +1052,6 @@ class RunJob(BaseTask):
pass
else:
update_inventory_computed_fields.delay(inventory.id, True)
# Update job event fields after job has completed (only when using REST
# API callback).
if not getattr(settings, 'CALLBACK_CONSUMER_PORT', None) and not getattr(settings, 'CALLBACK_QUEUE', None):
for job_event in job.job_events.order_by('pk'):
job_event.save(post_process=True)
class RunProjectUpdate(BaseTask):
@ -1598,6 +1625,7 @@ class RunAdHocCommand(BaseTask):
env['INVENTORY_HOSTVARS'] = str(True)
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir
env['ANSIBLE_LOAD_CALLBACK_PLUGINS'] = '1'
env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' # Hardcoded by Ansible for ad-hoc commands (either minimal or oneline).
env['REST_API_URL'] = settings.INTERNAL_API_URL
env['REST_API_TOKEN'] = ad_hoc_command.task_auth_token or ''
env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE
@ -1694,6 +1722,25 @@ class RunAdHocCommand(BaseTask):
d[re.compile(r'^Password:\s*?$', re.M)] = 'ssh_password'
return d
def get_stdout_handle(self, instance):
'''
Wrap stdout file object to capture events.
'''
stdout_handle = super(RunAdHocCommand, self).get_stdout_handle(instance)
if getattr(settings, 'USE_CALLBACK_QUEUE', False):
dispatcher = CallbackQueueDispatcher()
def ad_hoc_command_event_callback(event_data):
event_data.setdefault('ad_hoc_command_id', instance.id)
dispatcher.dispatch(event_data)
else:
def ad_hoc_command_event_callback(event_data):
event_data.setdefault('ad_hoc_command_id', instance.id)
AdHocCommandEvent.create_from_data(**event_data)
return OutputEventFilter(stdout_handle, ad_hoc_command_event_callback)
def get_ssh_key_path(self, instance, **kwargs):
'''
If using an SSH key, return the path for use by ssh-agent.

View File

@ -90,3 +90,35 @@ class TestWorkflowJobTemplate:
assert len(parent_qs) == 1
assert parent_qs[0] == wfjt.workflow_job_template_nodes.all()[1]
@pytest.mark.django_db
class TestWorkflowJobFailure:
"""
Tests to re-implement if workflow failure status is introduced in
a future Tower version.
"""
@pytest.fixture
def wfj(self):
return WorkflowJob.objects.create(name='test-wf-job')
def test_workflow_not_failed_unran_job(self, wfj):
"""
Test that an un-ran node will not mark workflow job as failed
"""
WorkflowJobNode.objects.create(workflow_job=wfj)
assert not wfj._has_failed()
def test_workflow_not_failed_successful_job(self, wfj):
"""
Test that a sucessful node will not mark workflow job as failed
"""
job = Job.objects.create(name='test-job', status='successful')
WorkflowJobNode.objects.create(workflow_job=wfj, job=job)
assert not wfj._has_failed()
def test_workflow_not_failed_failed_job_but_okay(self, wfj):
"""
Test that a failed node will not mark workflow job as failed
"""
job = Job.objects.create(name='test-job', status='failed')
WorkflowJobNode.objects.create(workflow_job=wfj, job=job)
assert not wfj._has_failed()

View File

@ -319,19 +319,19 @@ class RunAdHocCommandTest(BaseAdHocCommandTest):
self.assertIn('ssh-agent', ad_hoc_command.job_args)
self.assertNotIn('Bad passphrase', ad_hoc_command.result_stdout)
def test_run_with_proot(self):
# Only run test if proot is installed
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '--version']
def test_run_with_bubblewrap(self):
# Only run test if bubblewrap is installed
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--version']
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.communicate()
has_proot = bool(proc.returncode == 0)
has_bubblewrap = bool(proc.returncode == 0)
except (OSError, ValueError):
has_proot = False
if not has_proot:
self.skipTest('proot is not installed')
# Enable proot for this test.
has_bubblewrap = False
if not has_bubblewrap:
self.skipTest('bubblewrap is not installed')
# Enable bubblewrap for this test.
settings.AWX_PROOT_ENABLED = True
# Hide local settings path.
settings.AWX_PROOT_HIDE_PATHS = [os.path.join(settings.BASE_DIR, 'settings')]
@ -362,8 +362,8 @@ class RunAdHocCommandTest(BaseAdHocCommandTest):
self.check_ad_hoc_command_events(ad_hoc_command, 'ok')
@mock.patch('awx.main.tasks.BaseTask.run_pexpect', return_value=('failed', 0))
def test_run_with_proot_not_installed(self, ignore):
# Enable proot for this test, specify invalid proot cmd.
def test_run_with_bubblewrap_not_installed(self, ignore):
# Enable bubblewrap for this test, specify invalid bubblewrap cmd.
settings.AWX_PROOT_ENABLED = True
settings.AWX_PROOT_CMD = 'PR00T'
ad_hoc_command = self.create_test_ad_hoc_command()

View File

@ -150,7 +150,7 @@ TEST_ASYNC_NOWAIT_PLAYBOOK = '''
'''
TEST_PROOT_PLAYBOOK = '''
- name: test proot environment
- name: test bubblewrap environment
hosts: test-group
gather_facts: false
connection: local
@ -1177,19 +1177,19 @@ class RunJobTest(BaseJobExecutionTest):
@unittest.skipUnless(settings.BROKER_URL == 'redis://localhost/',
'Non-default Redis setup.')
def test_run_job_with_proot(self):
# Only run test if proot is installed
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '--version']
def test_run_job_with_bubblewrap(self):
# Only run test if bubblewrap is installed
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--version']
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.communicate()
has_proot = bool(proc.returncode == 0)
has_bubblewrap = bool(proc.returncode == 0)
except (OSError, ValueError):
has_proot = False
if not has_proot:
self.skipTest('proot is not installed')
# Enable proot for this test.
has_bubblewrap = False
if not has_bubblewrap:
self.skipTest('bubblewrap is not installed')
# Enable bubblewrap for this test.
settings.AWX_PROOT_ENABLED = True
# Hide local settings path.
settings.AWX_PROOT_HIDE_PATHS = [os.path.join(settings.BASE_DIR, 'settings')]
@ -1227,8 +1227,8 @@ class RunJobTest(BaseJobExecutionTest):
job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'successful')
def test_run_job_with_proot_not_installed(self):
# Enable proot for this test, specify invalid proot cmd.
def test_run_job_with_bubblewrap_not_installed(self):
# Enable bubblewrap for this test, specify invalid bubblewrap cmd.
settings.AWX_PROOT_ENABLED = True
settings.AWX_PROOT_CMD = 'PR00T'
self.create_test_credential()

View File

@ -215,7 +215,7 @@ class TestWorkflowWarnings:
def test_warn_scan_errors_node_prompts(self, job_node_with_prompts):
job_node_with_prompts.unified_job_template.job_type = 'scan'
job_node_with_prompts.job_type = 'run'
job_node_with_prompts.char_prompts['job_type'] = 'run'
job_node_with_prompts.inventory = Inventory(name='different-inventory', pk=23)
assert 'ignored' in job_node_with_prompts.get_prompts_warnings()
assert 'job_type' in job_node_with_prompts.get_prompts_warnings()['ignored']

View File

@ -4,6 +4,7 @@
# Python
import base64
import hashlib
import json
import logging
import os
import re
@ -39,7 +40,7 @@ __all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore',
'get_type_for_model', 'get_model_for_type', 'cache_list_capabilities', 'to_python_boolean',
'ignore_inventory_computed_fields', 'ignore_inventory_group_removal',
'_inventory_updates', 'get_pk_from_dict', 'getattrd', 'NoDefaultProvided',
'get_current_apps', 'set_current_apps']
'get_current_apps', 'set_current_apps', 'OutputEventFilter']
def get_object_or_400(klass, *args, **kwargs):
@ -528,7 +529,7 @@ def check_proot_installed():
Check that proot is installed.
'''
from django.conf import settings
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '--version']
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--version']
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@ -556,8 +557,7 @@ def wrap_args_with_proot(args, cwd, **kwargs):
- /tmp (except for own tmp files)
'''
from django.conf import settings
new_args = [getattr(settings, 'AWX_PROOT_CMD', 'proot'), '-v',
str(getattr(settings, 'AWX_PROOT_VERBOSITY', '0')), '-r', '/']
new_args = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--dev-bind', '/', '/']
hide_paths = ['/etc/tower', '/var/lib/awx', '/var/log',
tempfile.gettempdir(), settings.PROJECTS_ROOT,
settings.JOBOUTPUT_ROOT]
@ -572,7 +572,7 @@ def wrap_args_with_proot(args, cwd, **kwargs):
handle, new_path = tempfile.mkstemp(dir=kwargs['proot_temp_dir'])
os.close(handle)
os.chmod(new_path, stat.S_IRUSR | stat.S_IWUSR)
new_args.extend(['-b', '%s:%s' % (new_path, path)])
new_args.extend(['--bind', '%s' %(new_path,), '%s' % (path,)])
if 'private_data_dir' in kwargs:
show_paths = [cwd, kwargs['private_data_dir']]
else:
@ -585,8 +585,8 @@ def wrap_args_with_proot(args, cwd, **kwargs):
for path in sorted(set(show_paths)):
if not os.path.exists(path):
continue
new_args.extend(['-b', '%s:%s' % (path, path)])
new_args.extend(['-w', cwd])
new_args.extend(['--bind', '%s' % (path,), '%s' % (path,)])
new_args.extend(['--chdir', cwd])
new_args.extend(args)
return new_args
@ -644,3 +644,71 @@ def set_current_apps(apps):
def get_current_apps():
global current_apps
return current_apps
class OutputEventFilter(object):
'''
File-like object that looks for encoded job events in stdout data.
'''
EVENT_DATA_RE = re.compile(r'\x1b\[K((?:[A-Za-z0-9+/=]+\x1b\[\d+D)+)\x1b\[K')
def __init__(self, fileobj=None, event_callback=None):
self._fileobj = fileobj
self._event_callback = event_callback
self._counter = 1
self._start_line = 0
self._buffer = ''
self._current_event_data = None
def __getattr__(self, attr):
return getattr(self._fileobj, attr)
def write(self, data):
if self._fileobj:
self._fileobj.write(data)
self._buffer += data
while True:
match = self.EVENT_DATA_RE.search(self._buffer)
if not match:
break
try:
base64_data = re.sub(r'\x1b\[\d+D', '', match.group(1))
event_data = json.loads(base64.b64decode(base64_data))
except ValueError:
event_data = {}
self._emit_event(self._buffer[:match.start()], event_data)
self._buffer = self._buffer[match.end():]
def close(self):
if self._fileobj:
self._fileobj.close()
if self._buffer:
self._emit_event(self._buffer)
self._buffer = ''
def _emit_event(self, buffered_stdout, next_event_data=None):
if self._current_event_data:
event_data = self._current_event_data
stdout_chunks = [buffered_stdout]
elif buffered_stdout:
event_data = dict(event='verbose')
stdout_chunks = buffered_stdout.splitlines(True)
else:
stdout_chunks = []
for stdout_chunk in stdout_chunks:
event_data['counter'] = self._counter
self._counter += 1
event_data['stdout'] = stdout_chunk
n_lines = stdout_chunk.count('\n')
event_data['start_line'] = self._start_line
event_data['end_line'] = self._start_line + n_lines
self._start_line += n_lines
if self._event_callback:
self._event_callback(event_data)
if next_event_data.get('uuid', None):
self._current_event_data = next_event_data
else:
self._current_event_data = None

View File

@ -1,579 +0,0 @@
# Copyright (c) 2015 Ansible, Inc.
# This file is a utility Ansible plugin that is not part of the AWX or Ansible
# packages. It does not import any code from either package, nor does its
# license apply to Ansible or AWX.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of the <ORGANIZATION> nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Python
import datetime
import glob
import json
import logging
import os
import pwd
import urlparse
import re
from copy import deepcopy
from uuid import uuid4
# Kombu
from kombu import Connection, Exchange, Producer
# Requests
import requests
import psutil
CENSOR_FIELD_WHITELIST = [
'msg',
'failed',
'changed',
'results',
'start',
'end',
'delta',
'cmd',
'_ansible_no_log',
'rc',
'failed_when_result',
'skipped',
'skip_reason',
]
def censor(obj, no_log=False):
if not isinstance(obj, dict):
if no_log:
return "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
return obj
if obj.get('_ansible_no_log', no_log):
new_obj = {}
for k in CENSOR_FIELD_WHITELIST:
if k in obj:
new_obj[k] = obj[k]
if k == 'cmd' and k in obj:
if isinstance(obj['cmd'], list):
obj['cmd'] = ' '.join(obj['cmd'])
if re.search(r'\s', obj['cmd']):
new_obj['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$',
r'\1 <censored>',
obj['cmd'])
new_obj['censored'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
obj = new_obj
if 'results' in obj:
if isinstance(obj['results'], list):
for i in xrange(len(obj['results'])):
obj['results'][i] = censor(obj['results'][i], obj.get('_ansible_no_log', no_log))
elif obj.get('_ansible_no_log', False):
obj['results'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
return obj
class TokenAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, request):
request.headers['Authorization'] = 'Token %s' % self.token
return request
# TODO: non v2_ events are deprecated and should be purge/refactored out
class BaseCallbackModule(object):
'''
Callback module for logging ansible-playbook job events via the REST API.
'''
def __init__(self):
self.base_url = os.getenv('REST_API_URL', '')
self.auth_token = os.getenv('REST_API_TOKEN', '')
self.callback_connection = os.getenv('CALLBACK_CONNECTION', None)
self.connection_queue = os.getenv('CALLBACK_QUEUE', '')
self.connection = None
self.exchange = None
self._init_logging()
self._init_connection()
self.counter = 0
self.active_playbook = None
self.active_play = None
self.active_task = None
def _init_logging(self):
try:
self.job_callback_debug = int(os.getenv('JOB_CALLBACK_DEBUG', '0'))
except ValueError:
self.job_callback_debug = 0
self.logger = logging.getLogger('awx.plugins.callback.job_event_callback')
if self.job_callback_debug >= 2:
self.logger.setLevel(logging.DEBUG)
elif self.job_callback_debug >= 1:
self.logger.setLevel(logging.INFO)
else:
self.logger.setLevel(logging.WARNING)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(levelname)-8s %(process)-8d %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.propagate = False
def _init_connection(self):
self.connection = None
def _start_connection(self):
self.connection = Connection(self.callback_connection)
self.exchange = Exchange(self.connection_queue, type='direct')
def _post_job_event_queue_msg(self, event, event_data):
self.counter += 1
msg = {
'event': event,
'event_data': event_data,
'counter': self.counter,
'created': datetime.datetime.utcnow().isoformat(),
}
if event in ('playbook_on_play_start',
'playbook_on_stats',
'playbook_on_vars_prompt'):
msg['parent_uuid'] = str(self.active_playbook)
elif event in ('playbook_on_notify',
'playbook_on_setup',
'playbook_on_task_start',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
'playbook_on_include',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host'):
msg['parent_uuid'] = str(self.active_play)
elif event.startswith('runner_on_') or event.startswith('runner_item_on_'):
msg['parent_uuid'] = str(self.active_task)
else:
msg['parent_uuid'] = ''
if "uuid" in event_data:
msg['uuid'] = str(event_data['uuid'])
else:
msg['uuid'] = ''
if getattr(self, 'job_id', None):
msg['job_id'] = self.job_id
if getattr(self, 'ad_hoc_command_id', None):
msg['ad_hoc_command_id'] = self.ad_hoc_command_id
if getattr(self, 'artifact_data', None):
msg['artifact_data'] = self.artifact_data
active_pid = os.getpid()
if self.job_callback_debug:
msg.update({
'pid': active_pid,
})
for retry_count in xrange(4):
try:
if not hasattr(self, 'connection_pid'):
self.connection_pid = active_pid
if self.connection_pid != active_pid:
self._init_connection()
if self.connection is None:
self._start_connection()
producer = Producer(self.connection)
producer.publish(msg,
serializer='json',
compression='bzip2',
exchange=self.exchange,
declare=[self.exchange],
routing_key=self.connection_queue)
return
except Exception, e:
self.logger.info('Publish Job Event Exception: %r, retry=%d', e,
retry_count, exc_info=True)
retry_count += 1
if retry_count >= 3:
break
def _post_rest_api_event(self, event, event_data):
data = json.dumps({
'event': event,
'event_data': event_data,
})
parts = urlparse.urlsplit(self.base_url)
if parts.username and parts.password:
auth = (parts.username, parts.password)
elif self.auth_token:
auth = TokenAuth(self.auth_token)
else:
auth = None
port = parts.port or (443 if parts.scheme == 'https' else 80)
url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, port),
parts.path, parts.query, parts.fragment])
url = urlparse.urljoin(url, self.rest_api_path)
headers = {'content-type': 'application/json'}
response = requests.post(url, data=data, headers=headers, auth=auth)
response.raise_for_status()
def _log_event(self, event, **event_data):
if 'res' in event_data:
event_data['res'] = censor(deepcopy(event_data['res']))
if self.callback_connection:
self._post_job_event_queue_msg(event, event_data)
else:
self._post_rest_api_event(event, event_data)
def on_any(self, *args, **kwargs):
pass
def runner_on_failed(self, host, res, ignore_errors=False):
self._log_event('runner_on_failed', host=host, res=res,
ignore_errors=ignore_errors)
def v2_runner_on_failed(self, result, ignore_errors=False):
event_is_loop = result._task.loop if hasattr(result._task, 'loop') else None
self._log_event('runner_on_failed', host=result._host.name,
res=result._result, task=result._task,
ignore_errors=ignore_errors, event_loop=event_is_loop)
def runner_on_ok(self, host, res):
self._log_event('runner_on_ok', host=host, res=res)
def v2_runner_on_ok(self, result):
event_is_loop = result._task.loop if hasattr(result._task, 'loop') else None
self._log_event('runner_on_ok', host=result._host.name,
task=result._task, res=result._result,
event_loop=event_is_loop)
def runner_on_error(self, host, msg):
self._log_event('runner_on_error', host=host, msg=msg)
def v2_runner_on_error(self, result):
pass # Currently not implemented in v2
def runner_on_skipped(self, host, item=None):
self._log_event('runner_on_skipped', host=host, item=item)
def v2_runner_on_skipped(self, result):
event_is_loop = result._task.loop if hasattr(result._task, 'loop') else None
self._log_event('runner_on_skipped', host=result._host.name,
task=result._task, event_loop=event_is_loop)
def runner_on_unreachable(self, host, res):
self._log_event('runner_on_unreachable', host=host, res=res)
def v2_runner_on_unreachable(self, result):
self._log_event('runner_on_unreachable', host=result._host.name,
task=result._task, res=result._result)
def runner_on_no_hosts(self):
self._log_event('runner_on_no_hosts')
def v2_runner_on_no_hosts(self, task):
self._log_event('runner_on_no_hosts', task=task)
# V2 does not use the _on_async callbacks (yet).
def runner_on_async_poll(self, host, res, jid, clock):
self._log_event('runner_on_async_poll', host=host, res=res, jid=jid,
clock=clock)
def runner_on_async_ok(self, host, res, jid):
self._log_event('runner_on_async_ok', host=host, res=res, jid=jid)
def runner_on_async_failed(self, host, res, jid):
self._log_event('runner_on_async_failed', host=host, res=res, jid=jid)
def runner_on_file_diff(self, host, diff):
self._log_event('runner_on_file_diff', host=host, diff=diff)
def v2_runner_on_file_diff(self, result, diff):
self._log_event('runner_on_file_diff', host=result._host.name,
task=result._task, diff=diff)
def v2_runner_item_on_ok(self, result):
self._log_event('runner_item_on_ok', res=result._result, host=result._host.name,
task=result._task)
def v2_runner_item_on_failed(self, result):
self._log_event('runner_item_on_failed', res=result._result, host=result._host.name,
task=result._task)
def v2_runner_item_on_skipped(self, result):
self._log_event('runner_item_on_skipped', res=result._result, host=result._host.name,
task=result._task)
@staticmethod
def terminate_ssh_control_masters():
# Determine if control persist is being used and if any open sockets
# exist after running the playbook.
cp_path = os.environ.get('ANSIBLE_SSH_CONTROL_PATH', '')
if not cp_path:
return
cp_dir = os.path.dirname(cp_path)
if not os.path.exists(cp_dir):
return
cp_pattern = os.path.join(cp_dir, 'ansible-ssh-*')
cp_files = glob.glob(cp_pattern)
if not cp_files:
return
# Attempt to find any running control master processes.
username = pwd.getpwuid(os.getuid())[0]
ssh_cm_procs = []
for proc in psutil.process_iter():
try:
pname = proc.name()
pcmdline = proc.cmdline()
pusername = proc.username()
except psutil.NoSuchProcess:
continue
if pusername != username:
continue
if pname != 'ssh':
continue
for cp_file in cp_files:
if pcmdline and cp_file in pcmdline[0]:
ssh_cm_procs.append(proc)
break
# Terminate then kill control master processes. Workaround older
# version of psutil that may not have wait_procs implemented.
for proc in ssh_cm_procs:
proc.terminate()
procs_gone, procs_alive = psutil.wait_procs(ssh_cm_procs, timeout=5)
for proc in procs_alive:
proc.kill()
class JobCallbackModule(BaseCallbackModule):
'''
Callback module for logging ansible-playbook job events via the REST API.
'''
# These events should never have an associated play.
EVENTS_WITHOUT_PLAY = [
'playbook_on_start',
'playbook_on_stats',
]
# These events should never have an associated task.
EVENTS_WITHOUT_TASK = EVENTS_WITHOUT_PLAY + [
'playbook_on_setup',
'playbook_on_notify',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
]
def __init__(self):
self.job_id = int(os.getenv('JOB_ID', '0'))
self.rest_api_path = '/api/v1/jobs/%d/job_events/' % self.job_id
super(JobCallbackModule, self).__init__()
def _log_event(self, event, **event_data):
play = getattr(self, 'play', None)
play_name = getattr(play, 'name', '')
if play_name and event not in self.EVENTS_WITHOUT_PLAY:
event_data['play'] = play_name
task = event_data.pop('task', None) or getattr(self, 'task', None)
task_name = None
role_name = None
if task:
if hasattr(task, 'get_name'):
# in v2, the get_name() method creates the name
task_name = task.get_name()
else:
# v1 datastructure
task_name = getattr(task, 'name', '')
if hasattr(task, '_role') and task._role:
# v2 datastructure
role_name = task._role._role_name
else:
# v1 datastructure
role_name = getattr(task, 'role_name', '')
if task_name and event not in self.EVENTS_WITHOUT_TASK:
event_data['task'] = task_name
if role_name and event not in self.EVENTS_WITHOUT_TASK:
event_data['role'] = role_name
self.artifact_data = None
if 'res' in event_data and 'artifact_data' in event_data['res']:
self.artifact_data = event_data['res']['artifact_data']
super(JobCallbackModule, self)._log_event(event, **event_data)
def playbook_on_start(self):
self._log_event('playbook_on_start')
def v2_playbook_on_start(self, playbook):
# NOTE: the playbook parameter was added late in Ansible 2.0 development
# so we don't currently utilize but could later.
# NOTE: Ansible doesn't generate a UUID for playbook_on_start so we'll do it for them
self.active_playbook = str(uuid4())
self._log_event('playbook_on_start', uuid=self.active_playbook)
def playbook_on_notify(self, host, handler):
self._log_event('playbook_on_notify', host=host, handler=handler)
def v2_playbook_on_notify(self, result, handler):
self._log_event('playbook_on_notify', host=result._host.name,
task=result._task, handler=handler)
def playbook_on_no_hosts_matched(self):
self._log_event('playbook_on_no_hosts_matched')
def v2_playbook_on_no_hosts_matched(self):
# since there is no task/play info, this is currently identical
# to the v1 callback which does the same thing
self.playbook_on_no_hosts_matched()
def playbook_on_no_hosts_remaining(self):
self._log_event('playbook_on_no_hosts_remaining')
def v2_playbook_on_no_hosts_remaining(self):
# since there is no task/play info, this is currently identical
# to the v1 callback which does the same thing
self.playbook_on_no_hosts_remaining()
def playbook_on_task_start(self, name, is_conditional):
self._log_event('playbook_on_task_start', name=name,
is_conditional=is_conditional)
def v2_playbook_on_task_start(self, task, is_conditional):
self.active_task = task._uuid
self._log_event('playbook_on_task_start', task=task, uuid=str(task._uuid),
name=task.get_name(), is_conditional=is_conditional)
def v2_playbook_on_cleanup_task_start(self, task):
# re-using playbook_on_task_start event here for this v2-specific
# event, though we may consider any changes necessary to distinguish
# this from a normal task
self.active_task = task._uuid
self._log_event('playbook_on_task_start', task=task, uuid=str(task._uuid),
name=task.get_name())
def playbook_on_vars_prompt(self, varname, private=True, prompt=None,
encrypt=None, confirm=False, salt_size=None,
salt=None, default=None):
self._log_event('playbook_on_vars_prompt', varname=varname,
private=private, prompt=prompt, encrypt=encrypt,
confirm=confirm, salt_size=salt_size, salt=salt,
default=default)
def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None,
encrypt=None, confirm=False, salt_size=None,
salt=None, default=None):
pass # not currently used in v2 (yet)
def playbook_on_setup(self):
self._log_event('playbook_on_setup')
def v2_playbook_on_setup(self):
pass # not currently used in v2 (yet)
def playbook_on_import_for_host(self, host, imported_file):
# don't care about recording this one
# self._log_event('playbook_on_import_for_host', host=host,
# imported_file=imported_file)
pass
def v2_playbook_on_import_for_host(self, result, imported_file):
pass # not currently used in v2 (yet)
def playbook_on_not_import_for_host(self, host, missing_file):
# don't care about recording this one
#self._log_event('playbook_on_not_import_for_host', host=host,
# missing_file=missing_file)
pass
def v2_playbook_on_not_import_for_host(self, result, missing_file):
pass # not currently used in v2 (yet)
def playbook_on_play_start(self, name):
# Only play name is passed via callback, get host pattern from the play.
pattern = getattr(getattr(self, 'play', None), 'hosts', name)
self._log_event('playbook_on_play_start', name=name, pattern=pattern)
def v2_playbook_on_play_start(self, play):
setattr(self, 'play', play)
# Ansible 2.0.0.2 doesn't default .name to hosts like it did in 1.9.4,
# though that default will likely return in a future version of Ansible.
if (not hasattr(play, 'name') or not play.name) and hasattr(play, 'hosts'):
if isinstance(play.hosts, list):
play.name = ','.join(play.hosts)
else:
play.name = play.hosts
self.active_play = play._uuid
self._log_event('playbook_on_play_start', name=play.name, uuid=str(play._uuid),
pattern=play.hosts)
def playbook_on_stats(self, stats):
d = {}
for attr in ('changed', 'dark', 'failures', 'ok', 'processed', 'skipped'):
d[attr] = getattr(stats, attr)
self._log_event('playbook_on_stats', **d)
self.terminate_ssh_control_masters()
def v2_playbook_on_stats(self, stats):
self.playbook_on_stats(stats)
def v2_playbook_on_include(self, included_file):
self._log_event('playbook_on_include', included_file=included_file)
class AdHocCommandCallbackModule(BaseCallbackModule):
'''
Callback module for logging ansible ad hoc events via ZMQ or the REST API.
'''
def __init__(self):
self.ad_hoc_command_id = int(os.getenv('AD_HOC_COMMAND_ID', '0'))
self.rest_api_path = '/api/v1/ad_hoc_commands/%d/events/' % self.ad_hoc_command_id
self.skipped_hosts = set()
super(AdHocCommandCallbackModule, self).__init__()
def _log_event(self, event, **event_data):
# Ignore task for ad hoc commands (with v2).
event_data.pop('task', None)
super(AdHocCommandCallbackModule, self)._log_event(event, **event_data)
def runner_on_file_diff(self, host, diff):
pass # Ignore file diff for ad hoc commands.
def runner_on_ok(self, host, res):
# When running in check mode using a module that does not support check
# mode, Ansible v1.9 will call runner_on_skipped followed by
# runner_on_ok for the same host; only capture the skipped event and
# ignore the ok event.
if host not in self.skipped_hosts:
super(AdHocCommandCallbackModule, self).runner_on_ok(host, res)
def runner_on_skipped(self, host, item=None):
super(AdHocCommandCallbackModule, self).runner_on_skipped(host, item)
self.skipped_hosts.add(host)
if os.getenv('JOB_ID', ''):
CallbackModule = JobCallbackModule
elif os.getenv('AD_HOC_COMMAND_ID', ''):
CallbackModule = AdHocCommandCallbackModule

View File

@ -0,0 +1,30 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import os
import sys
# Add awx/lib to sys.path.
awx_lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
if awx_lib_path not in sys.path:
sys.path.insert(0, awx_lib_path)
# Tower Display Callback
from tower_display_callback import TowerMinimalCallbackModule as CallbackModule # noqa

View File

@ -0,0 +1,30 @@
# Copyright (c) 2016 Ansible by Red Hat, Inc.
#
# This file is part of Ansible Tower, but depends on code imported from Ansible.
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
# Python
import os
import sys
# Add awx/lib to sys.path.
awx_lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
if awx_lib_path not in sys.path:
sys.path.insert(0, awx_lib_path)
# Tower Display Callback
from tower_display_callback import TowerDefaultCallbackModule as CallbackModule # noqa

View File

@ -511,25 +511,25 @@ JOB_EVENT_MAX_QUEUE_SIZE = 100
# Flag to enable/disable updating hosts M2M when saving job events.
CAPTURE_JOB_EVENT_HOSTS = False
# Enable proot support for running jobs (playbook runs only).
# Enable bubblewrap support for running jobs (playbook runs only).
# Note: This setting may be overridden by database settings.
AWX_PROOT_ENABLED = False
# Command/path to proot.
AWX_PROOT_CMD = 'proot'
# Command/path to bubblewrap.
AWX_PROOT_CMD = 'bwrap'
# Additional paths to hide from jobs using proot.
# Additional paths to hide from jobs using bubblewrap.
# Note: This setting may be overridden by database settings.
AWX_PROOT_HIDE_PATHS = []
# Additional paths to show for jobs using proot.
# Additional paths to show for jobs using bubbelwrap.
# Note: This setting may be overridden by database settings.
AWX_PROOT_SHOW_PATHS = []
# Number of jobs to show as part of the job template history
AWX_JOB_TEMPLATE_HISTORY = 10
# The directory in which proot will create new temporary directories for its root
# The directory in which bubblewrap will create new temporary directories for its root
# Note: This setting may be overridden by database settings.
AWX_PROOT_BASE_PATH = "/tmp"

View File

@ -729,64 +729,7 @@ legend {
.navigation {
margin: 15px 0 15px 0;
}
.page-number {
display: inline-block;
padding: 0;
margin: 0;
}
.page-number-small {
display: inline-block;
margin-left: 10px;
font-size: 11px;
}
/* Pagination */
.page-label {
font-size: 12px;
margin-top: 0;
text-align: right;
}
.pagination {
margin-top: 0;
margin-bottom: 7px;
}
.pagination>li>a,
.pagination>li>span {
border: 1px solid @grey-border;
padding: 3px 6px;
font-size: 10px;
}
.pagination li {
a#next-page {
border-radius: 0 4px 4px 0;
}
a#previous-page {
border-radius: 4px 0 0 4px;
}
}
.modal-body {
.pagination {
margin-top: 15px;
margin-bottom: 0;
}
.pagination > li > a {
border: none;
padding-top: 0;
padding-bottom: 0;
}
.pagination > .active > a {
background-color: @default-bg;
color: #428bca;
border-color: none;
border: 1px solid @default-link;
}
.alert {
padding: 0;
border: none;
@ -1623,6 +1566,10 @@ a.btn-disabled:hover {
/* Sort link styles */
.list-header-noSort:hover.list-header:hover{
cursor: default;
}
.list-header:hover {
cursor: pointer;
}

View File

@ -19,13 +19,7 @@
}
.job-list {
.pagination li {
}
.pagination li a {
font-size: 12px;
padding: 3px 6px;
}
i[class*="icon-job-"] {
font-size: 13px;
}

View File

@ -116,44 +116,6 @@ table, tbody {
margin-left: 15px;
}
/* -- Pagination -- */
.List-pagination {
margin-top: 20px;
font-size: 12px;
color: @list-pagin-text;
text-transform: uppercase;
height: 22px;
display: flex;
}
.List-paginationPagerHolder {
display: flex;
flex: 1 0 auto;
}
.List-paginationPager {
display: flex;
}
.List-paginationPager--pageof {
line-height: 22px;
margin-left: 10px;
}
.List-paginationPager--item {
border-color: @list-pagin-bord;
}
.List-paginationPager--active {
border-color: @list-pagin-bord-act!important;
background-color: @list-pagin-bg-act!important;
}
.List-paginationItemsOf {
display: flex;
justify-content: flex-end;
}
.List-header {
display: flex;
min-height: 34px;

View File

@ -11,23 +11,12 @@
* Controller for handling permissions adding
*/
export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', function (rootScope, scope, GetBasePath, Rest, $q, Wait, ProcessErrors) {
var manuallyUpdateChecklists = function(list, id, isSelected) {
var elemScope = angular
.element("#" +
list + "s_table #" + id + ".List-tableRow input")
.scope();
if (elemScope) {
elemScope.isSelected = !!isSelected;
}
};
export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', function(rootScope, scope, GetBasePath, Rest, $q, Wait, ProcessErrors) {
scope.allSelected = [];
// the object permissions are being added to
scope.object = scope[scope.$parent.list
.iterator + "_obj"];
scope.object = scope.resourceData.data;
// array for all possible roles for the object
scope.roles = Object
.keys(scope.object.summary_fields.object_roles)
@ -36,7 +25,8 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
value: scope.object.summary_fields
.object_roles[key].id,
label: scope.object.summary_fields
.object_roles[key].name };
.object_roles[key].name
};
});
// TODO: get working with api
@ -48,7 +38,8 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
name: scope.object.summary_fields
.object_roles[key].name,
description: scope.object.summary_fields
.object_roles[key].description };
.object_roles[key].description
};
});
scope.showKeyPane = false;
@ -63,90 +54,44 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
scope.teamsSelected = !scope.usersSelected;
};
// manually handle selection/deselection of user/team checkboxes
scope.$on("selectedOrDeselected", function(e, val) {
val = val.value;
if (val.isSelected) {
// deselected, so remove from the allSelected list
scope.allSelected = scope.allSelected.filter(function(i) {
// return all but the object who has the id and type
// of the element to deselect
return (!(val.id === i.id && val.type === i.type));
});
// pop/push into unified collection of selected users & teams
scope.$on("selectedOrDeselected", function(e, value) {
let item = value.value;
function buildName(user) {
return (user.first_name &&
user.last_name) ?
user.first_name + " " +
user.last_name :
user.username;
}
if (item.isSelected) {
if (item.type === 'user') {
item.name = buildName(item);
}
scope.allSelected.push(item);
} else {
// selected, so add to the allSelected list
var getName = function(val) {
if (val.type === "user") {
return (val.first_name &&
val.last_name) ?
val.first_name + " " +
val.last_name :
val.username;
} else {
return val.name;
}
};
scope.allSelected.push({
name: getName(val),
type: val.type,
roles: [],
id: val.id
});
scope.allSelected = _.remove(scope.allSelected, { id: item.id });
}
});
// used to handle changes to the itemsSelected scope var on "next page",
// "sorting etc."
scope.$on("itemsSelected", function(e, inList) {
// compile a list of objects that needed to be checked in the lists
scope.updateLists = scope.allSelected.filter(function(inMemory) {
var notInList = true;
inList.forEach(function(val) {
// if the object is part of the allSelected list and is
// selected,
// you don't need to add it updateLists
if (inMemory.id === val.id &&
inMemory.type === val.type) {
notInList = false;
}
});
return notInList;
});
});
// handle changes to the updatedLists by manually selected those values in
// the UI
scope.$watch("updateLists", function(toUpdate) {
(toUpdate || []).forEach(function(obj) {
manuallyUpdateChecklists(obj.type, obj.id, true);
});
delete scope.updateLists;
});
// remove selected user/team
scope.removeObject = function(obj) {
manuallyUpdateChecklists(obj.type, obj.id, false);
scope.allSelected = scope.allSelected.filter(function(i) {
return (!(obj.id === i.id && obj.type === i.type));
});
};
// update post url list
scope.$watch("allSelected", function(val) {
scope.posts = _
.flatten((val || [])
.map(function (owner) {
var url = GetBasePath(owner.type + "s") + owner.id +
"/roles/";
.map(function(owner) {
var url = GetBasePath(owner.type + "s") + owner.id +
"/roles/";
return (owner.roles || [])
.map(function (role) {
return {url: url,
id: role.value};
});
}));
return (owner.roles || [])
.map(function(role) {
return {
url: url,
id: role.value
};
});
}));
}, true);
// post roles to api
@ -156,22 +101,22 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
var requests = scope.posts
.map(function(post) {
Rest.setUrl(post.url);
return Rest.post({"id": post.id});
return Rest.post({ "id": post.id });
});
$q.all(requests)
.then(function () {
.then(function() {
Wait('stop');
rootScope.$broadcast("refreshList", "permission");
scope.closeModal();
}, function (error) {
}, function(error) {
Wait('stop');
rootScope.$broadcast("refreshList", "permission");
scope.closeModal();
ProcessErrors(null, error.data, error.status, null, {
hdr: 'Error!',
msg: 'Failed to post role(s): POST returned status' +
error.status
error.status
});
});
};

View File

@ -6,55 +6,28 @@
import addPermissionsController from './addPermissions.controller';
/* jshint unused: vars */
export default
[ 'templateUrl',
'Wait',
function(templateUrl, Wait) {
return {
restrict: 'E',
scope: true,
controller: addPermissionsController,
templateUrl: templateUrl('access/addPermissions/addPermissions'),
link: function(scope, element, attrs, ctrl) {
scope.withoutTeamPermissions = attrs.withoutTeamPermissions;
scope.toggleFormTabs('users');
export default ['templateUrl', '$state',
'Wait', 'addPermissionsUsersList', 'addPermissionsTeamsList',
function(templateUrl, $state, Wait, usersList, teamsList) {
return {
restrict: 'E',
scope: {
usersDataset: '=',
teamsDataset: '=',
resourceData: '=',
},
controller: addPermissionsController,
templateUrl: templateUrl('access/addPermissions/addPermissions'),
link: function(scope, element, attrs) {
scope.toggleFormTabs('users');
$('#add-permissions-modal').modal('show');
$("body").addClass("is-modalOpen");
scope.closeModal = function() {
$state.go('^', null, {reload: true});
};
$("body").append(element);
Wait('start');
scope.$broadcast("linkLists");
setTimeout(function() {
$('#add-permissions-modal').modal("show");
}, 200);
$('.modal[aria-hidden=false]').each(function () {
if ($(this).attr('id') !== 'add-permissions-modal') {
$(this).modal('hide');
}
});
scope.closeModal = function() {
$("body").removeClass("is-modalOpen");
$('#add-permissions-modal').on('hidden.bs.modal',
function () {
$('.AddPermissions').remove();
});
$('#add-permissions-modal').modal('hide');
};
scope.$on('closePermissionsModal', function() {
scope.closeModal();
});
Wait('stop');
window.scrollTo(0,0);
}
};
}
];
window.scrollTo(0, 0);
}
};
}
];

View File

@ -45,13 +45,11 @@
</div>
</div>
<div class="AddPermissions-list" ng-show="usersSelected">
<add-permissions-list all-selected="allSelected" type="users">
</add-permissions-list>
<div id="AddPermissions-users" class="AddPermissions-list" ng-show="usersSelected">
<add-permissions-list view="Users" all-selected="allSelected" dataset="usersDataset"></add-permissions-list>
</div>
<div class="AddPermissions-list" ng-show="teamsSelected">
<add-permissions-list all-selected="allSelected" type="teams">
</add-permissions-list>
<div id="AddPermissions-teams" class="AddPermissions-list" ng-show="teamsSelected">
<add-permissions-list view="Teams" all-selected="allSelected" dataset="teamsDataset"></add-permissions-list>
</div>
<div class="AddPermissions-separator"

View File

@ -5,55 +5,43 @@
*************************************************/
/* jshint unused: vars */
export default
['addPermissionsTeamsList', 'addPermissionsUsersList', 'generateList', 'GetBasePath', 'SelectionInit', 'SearchInit',
'PaginateInit', function(addPermissionsTeamsList,
addPermissionsUsersList, generateList,
GetBasePath, SelectionInit, SearchInit, PaginateInit) {
return {
restrict: 'E',
scope: {
allSelected: '='
},
template: "<div class='addPermissionsList-inner'></div>",
link: function(scope, element, attrs, ctrl) {
scope.$on("linkLists", function(e) {
var generator = generateList,
list = addPermissionsTeamsList,
url = GetBasePath("teams"),
set = "teams",
id = "addPermissionsTeamsList",
mode = "edit";
export default ['addPermissionsTeamsList', 'addPermissionsUsersList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit', function(addPermissionsTeamsList,
addPermissionsUsersList, $compile, generateList,
GetBasePath, SelectionInit) {
return {
restrict: 'E',
scope: {
allSelected: '=',
view: '@',
dataset: '='
},
template: "<div class='addPermissionsList-inner'></div>",
link: function(scope, element, attrs, ctrl) {
let listMap, list, list_html;
if (attrs.type === 'users') {
list = addPermissionsUsersList;
url = GetBasePath("users") + "?is_superuser=false";
set = "users";
id = "addPermissionsUsersList";
mode = "edit";
}
listMap = {Teams: addPermissionsTeamsList, Users: addPermissionsUsersList};
list = listMap[scope.view];
list_html = generateList.build({
mode: 'edit',
list: list
});
scope.id = id;
scope.list = listMap[scope.view];
scope[`${list.iterator}_dataset`] = scope.dataset.data;
scope[`${list.name}`] = scope[`${list.iterator}_dataset`].results;
scope.$watch("selectedItems", function() {
scope.$emit("itemsSelected", scope.selectedItems);
});
scope.$watch(list.name, function(){
_.forEach(scope[`${list.name}`], isSelected);
});
element.find(".addPermissionsList-inner")
.attr("id", id);
generator.inject(list, { id: id,
title: false, mode: mode, scope: scope });
SearchInit({ scope: scope, set: set,
list: list, url: url });
PaginateInit({ scope: scope,
list: list, url: url, pageSize: 5 });
scope.search(list.iterator);
});
function isSelected(item){
if(_.find(scope.allSelected, {id: item.id})){
item.isSelected = true;
}
};
return item;
}
element.append(list_html);
$compile(element.contents())(scope);
}
];
};
}];

View File

@ -7,9 +7,14 @@
export default function() {
return {
searchSize: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
name: 'users',
iterator: 'user',
defaultSearchParams: function(term){
return {or__username__icontains: term,
or__first_name__icontains: term,
or__last_name__icontains: term
};
},
title: false,
listTitleBadge: false,
multiSelect: true,
@ -17,7 +22,6 @@
index: false,
hover: true,
emptyListText : 'No Users exist',
fields: {
first_name: {
label: 'First Name',

View File

@ -8,22 +8,31 @@
* @ngdoc function
* @name controllers.function:Activity Stream
* @description This controller controls the activity stream.
*/
function activityStreamController($scope, $state, subTitle, Stream, GetTargetTitle) {
*/
function activityStreamController($scope, $state, subTitle, Stream, GetTargetTitle, list, Dataset) {
// subTitle is passed in via a resolve on the route. If there is no subtitle
// generated in the resolve then we go get the targets generic title.
init();
// Get the streams sub-title based on the target. This scope variable is leveraged
// when we define the activity stream list. Specifically it is included in the list
// title.
$scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target);
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
// Open the stream
Stream({
scope: $scope
});
// subTitle is passed in via a resolve on the route. If there is no subtitle
// generated in the resolve then we go get the targets generic title.
// Get the streams sub-title based on the target. This scope variable is leveraged
// when we define the activity stream list. Specifically it is included in the list
// title.
$scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target);
// Open the stream
Stream({
scope: $scope
});
}
}
export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle', activityStreamController];
export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle', 'StreamList', 'Dataset', activityStreamController];

View File

@ -1,3 +0,0 @@
<div class="Panel" id="stream-container">
<div id="stream-content"></div>
</div>

View File

@ -4,49 +4,73 @@
* All Rights Reserved
*************************************************/
import {templateUrl} from '../shared/template-url/template-url.factory';
export default {
name: 'activityStream',
route: '/activity_stream?target&id',
templateUrl: templateUrl('activity-stream/activitystream'),
controller: 'activityStreamController',
searchPrefix: 'activity',
data: {
activityStream: true
},
params: {
activity_search: {
value: {
// default params will not generate search tags
order_by: '-timestamp',
or__object1: null,
or__object2: null
}
}
},
ncyBreadcrumb: {
label: "ACTIVITY STREAM"
},
onExit: function(){
onExit: function() {
$('#stream-detail-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
},
resolve: {
features: ['FeaturesService', 'ProcessErrors', '$state', '$rootScope',
function(FeaturesService, ProcessErrors, $state, $rootScope) {
var features = FeaturesService.get();
if(features){
if(FeaturesService.featureEnabled('activity_streams')) {
return features;
}
else {
$state.go('dashboard');
}
views: {
'list@': {
controller: 'activityStreamController',
templateProvider: function(StreamList, generateList) {
let html = generateList.build({
list: StreamList,
mode: 'edit'
});
html = generateList.wrapPanel(html);
return html;
}
$rootScope.featuresConfigured.promise.then(function(features){
if(features){
if(FeaturesService.featureEnabled('activity_streams')) {
}
},
resolve: {
Dataset: ['StreamList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
features: ['FeaturesService', 'ProcessErrors', '$state', '$rootScope',
function(FeaturesService, ProcessErrors, $state, $rootScope) {
var features = FeaturesService.get();
if (features) {
if (FeaturesService.featureEnabled('activity_streams')) {
return features;
}
else {
} else {
$state.go('dashboard');
}
}
});
}],
subTitle:
[ '$stateParams',
$rootScope.featuresConfigured.promise.then(function(features) {
if (features) {
if (FeaturesService.featureEnabled('activity_streams')) {
return features;
} else {
$state.go('dashboard');
}
}
});
}
],
subTitle: ['$stateParams',
'Rest',
'ModelToBasePathKey',
'GetBasePath',
@ -65,15 +89,14 @@ export default {
.then(function(data) {
// Return the name or the username depending on which is available.
return (data.data.name || data.data.username);
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get title info. GET returned status: ' +
response.status
}).catch(function(response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get title info. GET returned status: ' +
response.status
});
});
});
}
else {
} else {
return null;
}
}

View File

@ -10,7 +10,7 @@ export default ['templateUrl', function(templateUrl) {
scope: true,
replace: true,
templateUrl: templateUrl('activity-stream/streamDropdownNav/stream-dropdown-nav'),
controller: ['$scope', '$state', 'CreateSelect2', function($scope, $state, CreateSelect2) {
controller: ['$scope', '$state', '$stateParams','CreateSelect2', function($scope, $state, $stateParams, CreateSelect2) {
$scope.streamTarget = ($state.params && $state.params.target) ? $state.params.target : 'dashboard';
@ -35,14 +35,17 @@ export default ['templateUrl', function(templateUrl) {
});
$scope.changeStreamTarget = function(){
if($scope.streamTarget && $scope.streamTarget === 'dashboard') {
// Just navigate to the base activity stream
$state.go('activityStream', {}, {inherit: false});
$state.go('activityStream');
}
else {
let search = _.merge($stateParams.activity_search, {
or__object1: $scope.streamTarget,
or__object2: $scope.streamTarget
});
// Attach the taget to the query parameters
$state.go('activityStream', {target: $scope.streamTarget}, {inherit: false});
$state.go('activityStream', {target: $scope.streamTarget, activity_search: search});
}
};

View File

@ -15,6 +15,12 @@ import 'jquery.resize';
import 'codemirror';
import 'js-yaml';
import 'select2';
import uiRouter from 'angular-ui-router';
// backwards compatibility for $stateChange* events
import 'angular-ui-router/release/stateEvents';
// ui-router debugging
//import { trace } from 'angular-ui-router';
//trace.enable();
// Configuration dependencies
global.$AnsibleConfig = null;
@ -27,7 +33,7 @@ if ($basePath) {
// Modules
import './helpers';
import './forms';
import * as forms from './forms';
import './lists';
import './widgets';
import './filters';
@ -40,12 +46,10 @@ import systemTracking from './system-tracking/main';
import inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main';
import permissions from './permissions/main';
import managementJobs from './management-jobs/main';
import jobDetail from './job-detail/main';
import jobSubmission from './job-submission/main';
import notifications from './notifications/main';
import access from './access/main';
import about from './about/main';
import license from './license/main';
import setupMenu from './setup-menu/main';
@ -54,23 +58,17 @@ import breadCrumb from './bread-crumb/main';
import browserData from './browser-data/main';
import dashboard from './dashboard/main';
import moment from './shared/moment/main';
import templateUrl from './shared/template-url/main';
import login from './login/main';
import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
import JobTemplates from './job-templates/main';
import search from './search/main';
import credentials from './credentials/main';
import { ProjectsList, ProjectsAdd, ProjectsEdit } from './controllers/Projects';
import OrganizationsList from './organizations/list/organizations-list.controller';
import OrganizationsAdd from './organizations/add/organizations-add.controller';
import { UsersList, UsersAdd, UsersEdit } from './controllers/Users';
import { TeamsList, TeamsAdd, TeamsEdit } from './controllers/Teams';
import RestServices from './rest/main';
import './lookup/main';
import './shared/api-loader';
import './shared/form-generator';
import access from './access/main';
import './shared/Modal';
import './shared/prompt-dialog';
import './shared/directives';
@ -80,7 +78,7 @@ import config from './shared/config/main';
import './login/authenticationServices/pendo/ng-pendo';
import footer from './footer/main';
import scheduler from './scheduler/main';
import {N_} from './i18n';
import { N_ } from './i18n';
var tower = angular.module('Tower', [
// how to add CommonJS / AMD third-party dependencies:
@ -88,17 +86,17 @@ var tower = angular.module('Tower', [
// 2. add package name to ./grunt-tasks/webpack.vendorFiles
require('angular-breadcrumb'),
require('angular-codemirror'),
require('angular-cookies'),
require('angular-drag-and-drop-lists'),
require('angular-ui-router'),
require('angular-sanitize'),
require('angular-scheduler').name,
require('angular-tz-extensions'),
require('lr-infinite-scroll'),
require('ng-toast'),
uiRouter,
'ui.router.state.events',
about.name,
access.name,
license.name,
RestServices.name,
browserData.name,
@ -106,14 +104,13 @@ var tower = angular.module('Tower', [
inventories.name,
inventoryScripts.name,
organizations.name,
permissions.name,
//permissions.name,
managementJobs.name,
setupMenu.name,
mainMenu.name,
breadCrumb.name,
dashboard.name,
moment.name,
templateUrl.name,
login.name,
activityStream.name,
footer.name,
@ -121,27 +118,19 @@ var tower = angular.module('Tower', [
jobSubmission.name,
notifications.name,
standardOut.name,
access.name,
JobTemplates.name,
portalMode.name,
search.name,
config.name,
credentials.name,
//'templates',
'Utilities',
'OrganizationFormDefinition',
'UserFormDefinition',
'FormGenerator',
'OrganizationListDefinition',
'jobTemplates',
'UserListDefinition',
'UserHelper',
'PromptDialog',
'ApiLoader',
'RelatedSearchHelper',
'SearchHelper',
'PaginationHelpers',
'RefreshHelper',
'AWDirectives',
'InventoriesListDefinition',
'InventoryFormDefinition',
@ -161,7 +150,6 @@ var tower = angular.module('Tower', [
'TeamHelper',
'CredentialsListDefinition',
'CredentialFormDefinition',
'LookUpHelper',
'JobTemplatesListDefinition',
'PortalJobTemplatesListDefinition',
'JobTemplateFormDefinition',
@ -223,10 +211,12 @@ var tower = angular.module('Tower', [
timeout: 4000
});
}])
.config(['$stateProvider', '$urlRouterProvider', '$breadcrumbProvider',
'$urlMatcherFactoryProvider',
function($stateProvider, $urlRouterProvider, $breadcrumbProvider,
$urlMatcherFactoryProvider) {
.config(['$urlRouterProvider', '$breadcrumbProvider', 'QuerySetProvider',
'$urlMatcherFactoryProvider', 'stateDefinitionsProvider', '$stateProvider', '$stateExtenderProvider',
function($urlRouterProvider, $breadcrumbProvider, QuerySet,
$urlMatcherFactoryProvider, stateDefinitionsProvider, $stateProvider, $stateExtenderProvider) {
let $stateExtender = $stateExtenderProvider.$get(),
stateDefinitions = stateDefinitionsProvider.$get();
$urlMatcherFactoryProvider.strictMode(false);
$breadcrumbProvider.setOptions({
templateUrl: urlPrefix + 'partials/breadcrumb.html'
@ -234,202 +224,133 @@ var tower = angular.module('Tower', [
// route to the details pane of /job/:id/host-event/:eventId if no other child specified
$urlRouterProvider.when('/jobs/*/host-event/*', '/jobs/*/host-event/*/details');
$urlRouterProvider.otherwise('/home');
// $urlRouterProvider.otherwise("/home");
$urlRouterProvider.otherwise(function($injector) {
var $state = $injector.get("$state");
$state.go('dashboard');
$urlMatcherFactoryProvider.type('queryset', {
// encoding
// from {operator__key1__comparator=value, ... }
// to "_search=operator:key:compator=value& ... "
encode: function(item) {
return QuerySet.$get().encodeArr(item);
},
// decoding
// from "_search=operator:key:compator=value& ... "
// to "_search=operator:key:compator=value& ... "
decode: function(item) {
return QuerySet.$get().decodeArr(item);
},
// directionality - are we encoding or decoding?
is: function(item) {
// true: encode to uri
// false: decode to $stateParam
return angular.isObject(item);
}
});
/* Mark translatable strings with N_() and
* extract them by 'grunt nggettext_extract'
* but angular.config() cannot get gettextCatalog.
*/
$stateProvider.
state('teams', {
url: '/teams',
templateUrl: urlPrefix + 'partials/teams.html',
controller: TeamsList,
data: {
activityStream: true,
activityStreamTarget: 'team'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_("TEAMS")
}
}).
state('teams.add', {
url: '/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: TeamsAdd,
ncyBreadcrumb: {
parent: "teams",
label: N_("CREATE TEAM")
}
}).
// Handy hook for debugging register/deregister of lazyLoad'd states
// $stateProvider.stateRegistry.onStatesChanged((event, states) =>{
// console.log(event, states)
// })
state('teams.edit', {
url: '/:team_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: TeamsEdit,
data: {
activityStreamId: 'team_id'
},
ncyBreadcrumb: {
parent: "teams",
label: "{{team_obj.name}}"
}
}).
state('teamUsers', {
url: '/teams/:team_id/users',
templateUrl: urlPrefix + 'partials/teams.html',
controller: UsersList
}).
// lazily generate a tree of substates which will replace this node in ui-router's stateRegistry
// see: stateDefinition.factory for usage documentation
$stateProvider.state({
name: 'projects',
url: '/projects',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'projects', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'ProjectList',
form: 'ProjectsForm',
controllers: {
list: ProjectsList, // DI strings or objects
add: ProjectsAdd,
edit: ProjectsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'project',
socket: {
"groups": {
"jobs": ["status_changed"]
}
}
}
})
});
state('teamUserEdit', {
url: '/teams/:team_id/users/:user_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: UsersEdit
}).
state('teamProjects', {
url: '/teams/:team_id/projects',
templateUrl: urlPrefix + 'partials/teams.html',
controller: ProjectsList
}).
state('teamProjectAdd', {
url: '/teams/:team_id/projects/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: ProjectsAdd
}).
state('teamProjectEdit', {
url: '/teams/:team_id/projects/:project_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: ProjectsEdit
}).
state('teamCredentials', {
url: '/teams/:team_id/credentials',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsList
}).
state('teamCredentialAdd', {
url: '/teams/:team_id/credentials/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsAdd
}).
state('teamCredentialEdit', {
url: '/teams/:team_id/credentials/:credential_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsEdit
}).
state('credentials', {
$stateProvider.state({
name: 'credentials',
url: '/credentials',
templateUrl: urlPrefix + 'partials/credentials.html',
controller: CredentialsList,
data: {
activityStream: true,
activityStreamTarget: 'credential'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_("CREDENTIALS")
}
}).
lazyLoad: () => stateDefinitions.generateTree({
parent: 'credentials',
modes: ['add', 'edit'],
list: 'CredentialList',
form: 'CredentialForm',
controllers: {
list: CredentialsList,
add: CredentialsAdd,
edit: CredentialsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'credential'
},
ncyBreadcrumb: {
parent: 'setup',
label: 'CREDENTIALS'
}
})
});
state('credentials.add', {
url: '/add',
templateUrl: urlPrefix + 'partials/credentials.html',
controller: CredentialsAdd,
ncyBreadcrumb: {
parent: "credentials",
label: N_("CREATE CREDENTIAL")
}
}).
$stateProvider.state({
name: 'teams',
url: '/teams',
lazyLoad: () => stateDefinitions.generateTree({
parent: 'teams',
modes: ['add', 'edit'],
list: 'TeamList',
form: 'TeamForm',
controllers: {
list: TeamsList,
add: TeamsAdd,
edit: TeamsEdit
},
data: {
activityStream: true,
activityStreamTarget: 'team'
},
ncyBreadcrumb: {
parent: 'setup',
label: 'TEAMS'
}
})
});
state('credentials.edit', {
url: '/:credential_id',
templateUrl: urlPrefix + 'partials/credentials.html',
controller: CredentialsEdit,
data: {
activityStreamId: 'credential_id'
},
ncyBreadcrumb: {
parent: "credentials",
label: "{{credential_obj.name}}"
}
}).
state('users', {
$stateProvider.state({
name: 'users',
url: '/users',
templateUrl: urlPrefix + 'partials/users.html',
controller: UsersList,
data: {
activityStream: true,
activityStreamTarget: 'user'
},
ncyBreadcrumb: {
parent: 'setup',
label: N_("USERS")
}
}).
state('users.add', {
url: '/add',
templateUrl: urlPrefix + 'partials/users.html',
controller: UsersAdd,
ncyBreadcrumb: {
parent: "users",
label: N_("CREATE USER")
}
}).
state('users.edit', {
url: '/:user_id',
templateUrl: urlPrefix + 'partials/users.html',
controller: UsersEdit,
data: {
activityStreamId: 'user_id'
},
ncyBreadcrumb: {
parent: "users",
label: "{{user_obj.username}}"
}
}).
state('userCredentials', {
url: '/users/:user_id/credentials',
templateUrl: urlPrefix + 'partials/users.html',
controller: CredentialsList
}).
state('userCredentialAdd', {
url: '/users/:user_id/credentials/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsAdd
}).
state('teamUserCredentialEdit', {
url: '/teams/:user_id/credentials/:credential_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsEdit
}).
state('sockets', {
url: '/sockets',
templateUrl: urlPrefix + 'partials/sockets.html',
controller: SocketsController,
ncyBreadcrumb: {
label: N_("SOCKETS")
}
lazyLoad: () => stateDefinitions.generateTree({
parent: 'users',
modes: ['add', 'edit'],
list: 'UserList',
form: 'UserForm',
controllers: {
list: UsersList,
add: UsersAdd,
edit: UsersEdit
},
data: {
activityStream: true,
activityStreamTarget: 'user'
},
ncyBreadcrumb: {
parent: 'setup',
label: 'USERS'
}
})
});
}
])
@ -447,17 +368,23 @@ var tower = angular.module('Tower', [
}]);
}])
.run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log',
.run(['$stateExtender', '$q', '$compile', '$cookieStore', '$rootScope', '$log', '$stateParams',
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
'ClearScope', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
'FeaturesService', '$filter', 'SocketService', 'I18NInit',
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log,
function($stateExtender, $q, $compile, $cookieStore, $rootScope, $log, $stateParams,
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
ClearScope, LoadConfig, Store, pendoService, Prompt, Rest, Wait,
ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService,
$filter, SocketService, I18NInit) {
$rootScope.$state = $state;
$rootScope.$state.matches = function(stateName) {
return $state.current.name.search(stateName) > 0;
};
$rootScope.$stateParams = $stateParams;
I18NInit();
$stateExtender.addState({
name: 'dashboard',
@ -465,14 +392,14 @@ var tower = angular.module('Tower', [
templateUrl: urlPrefix + 'partials/home.html',
controller: Home,
params: { licenseMissing: null },
socket: {
"groups":{
"jobs": ["status_changed"]
}
},
data: {
activityStream: true,
refreshButton: true
refreshButton: true,
socket: {
"groups": {
"jobs": ["status_changed"]
}
},
},
ncyBreadcrumb: {
label: N_("DASHBOARD")
@ -491,94 +418,94 @@ var tower = angular.module('Tower', [
});
$stateExtender.addState({
searchPrefix: 'job',
name: 'jobs',
url: '/jobs',
templateUrl: urlPrefix + 'partials/jobs.html',
controller: JobsListController,
ncyBreadcrumb: {
label: N_("JOBS")
},
params: {
search: {
value: {order_by:'-finished'}
job_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'
socket: {
"groups": {
"jobs": ["status_changed"],
"schedules": ["changed"]
}
}
},
ncyBreadcrumb: {
label: N_("PROJECTS")
resolve: {
Dataset: ['AllJobsList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}]
},
socket: {
"groups":{
"jobs": ["status_changed"]
views: {
'list@': {
templateUrl: urlPrefix + 'partials/jobs.html',
},
'list@jobs': {
templateProvider: function(AllJobsList, generateList) {
let html = generateList.build({
list: AllJobsList,
mode: 'edit'
});
return html;
},
controller: JobsListController
}
}
});
$stateExtender.addState({
name: 'projects.add',
url: '/add',
templateUrl: urlPrefix + 'partials/projects.html',
controller: ProjectsAdd,
ncyBreadcrumb: {
parent: "projects",
label: N_("CREATE PROJECT")
},
socket: {
"groups":{
"jobs": ["status_changed"]
}
name: 'teamUsers',
url: '/teams/:team_id/users',
templateUrl: urlPrefix + 'partials/teams.html',
controller: UsersList,
resolve: {
Users: ['UsersList', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}]
}
});
$stateExtender.addState({
name: 'projects.edit',
url: '/:id',
templateUrl: urlPrefix + 'partials/projects.html',
controller: ProjectsEdit,
data: {
activityStreamId: 'id'
},
name: 'userCredentials',
url: '/users/:user_id/credentials',
templateUrl: urlPrefix + 'partials/users.html',
controller: CredentialsList
});
$stateExtender.addState({
name: 'userCredentialAdd',
url: '/users/:user_id/credentials/add',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsAdd
});
$stateExtender.addState({
name: 'teamUserCredentialEdit',
url: '/teams/:user_id/credentials/:credential_id',
templateUrl: urlPrefix + 'partials/teams.html',
controller: CredentialsEdit
});
$stateExtender.addState({
name: 'sockets',
url: '/sockets',
templateUrl: urlPrefix + 'partials/sockets.html',
controller: SocketsController,
ncyBreadcrumb: {
parent: 'projects',
label: '{{name}}'
},
socket: {
"groups":{
"jobs": ["status_changed"]
}
label: 'SOCKETS'
}
});
$stateExtender.addState({
name: 'projectOrganizations',
url: '/projects/:project_id/organizations',
templateUrl: urlPrefix + 'partials/projects.html',
controller: OrganizationsList
});
$stateExtender.addState({
name: 'projectOrganizationAdd',
url: '/projects/:project_id/organizations/add',
templateUrl: urlPrefix + 'partials/projects.html',
controller: OrganizationsAdd
});
$rootScope.addPermission = function(scope) {
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
};
@ -604,7 +531,7 @@ var tower = angular.module('Tower', [
Rest.post({ "disassociate": true, "id": entry.id })
.success(function() {
Wait('stop');
$rootScope.$broadcast("refreshList", "permission");
$state.go('.', null, { reload: true });
})
.error(function(data, status) {
ProcessErrors($rootScope, data, status, null, {

View File

@ -25,18 +25,29 @@ export default
if(streamConfig && streamConfig.activityStream) {
if(streamConfig.activityStreamTarget) {
stateGoParams.target = streamConfig.activityStreamTarget;
stateGoParams.activity_search = {
or__object1: streamConfig.activityStreamTarget,
or__object2: streamConfig.activityStreamTarget,
order_by: '-timestamp',
page_size: '20',
};
}
else {
stateGoParams.activity_search = {
order_by: '-timestamp',
page_size: '20',
};
}
if(streamConfig.activityStreamId) {
stateGoParams.id = $state.params[streamConfig.activityStreamId];
}
}
originalRoute = $state.current;
$state.go('activityStream', stateGoParams);
}
// The user is navigating away from the activity stream - take them back from whence they came
else {
// Pull the previous state out of local storage
if(originalRoute) {
$state.go(originalRoute.name, originalRoute.fromParams);
}
@ -51,14 +62,13 @@ export default
};
scope.$on("$stateChangeStart", function updateActivityStreamButton(event, toState, toParams, fromState, fromParams) {
if(fromState && !Empty(fromState.name)) {
// Go ahead and attach the from params to the state object so that it can all be stored together
fromState.fromParams = fromParams ? fromParams : {};
// Store the state that we're coming from in local storage to be accessed when navigating away from the
// activity stream
Store('previous_state', fromState);
//Store('previous_state', fromState);
}
streamConfig = (toState && toState.data) ? toState.data : {};

View File

@ -8,114 +8,63 @@
* @ngdoc function
* @name controllers.function:Credentials
* @description This controller's for the credentials page
*/
*/
export function CredentialsList($scope, $rootScope, $location, $log,
$stateParams, Rest, Alert, CredentialList, GenerateList, Prompt, SearchInit,
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
SelectionInit, GetChoices, Wait, $state, $filter, rbacUiControlService) {
$stateParams, Rest, Alert, CredentialList, Prompt, ClearScope,
ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
ClearScope();
rbacUiControlService.canAdd('credentials')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
Wait('start');
var list = CredentialList,
defaultUrl = GetBasePath('credentials'),
view = GenerateList,
base = $location.path().replace(/^\//, '').split('/')[0],
mode = (base === 'credentials') ? 'edit' : 'select',
url;
defaultUrl = GetBasePath('credentials');
view.inject(list, { mode: mode, scope: $scope });
init();
$scope.selected = [];
$scope.credentialLoading = true;
function init() {
rbacUiControlService.canAdd('credentials')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
url = GetBasePath(base) + ( (base === 'users') ? $stateParams.user_id + '/credentials/' : $stateParams.team_id + '/credentials/' );
if (mode === 'select') {
SelectionInit({ scope: $scope, list: list, url: url, returnToCaller: 1 });
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.selected = [];
}
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
var i, j;
// Cleanup after a delete
Wait('stop');
$('#prompt-modal').modal('hide');
list.fields.kind.searchOptions = $scope.credential_kind_options_list;
// Translate the kind value
for (i = 0; i < $scope.credentials.length; i++) {
for (j = 0; j < $scope.credential_kind_options_list.length; j++) {
if ($scope.credential_kind_options_list[j].value === $scope.credentials[i].kind) {
$scope.credentials[i].kind = $scope.credential_kind_options_list[j].label;
break;
}
}
}
});
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function () {
SearchInit({
scope: $scope,
set: 'credentials',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
$scope.search(list.iterator);
});
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options_list',
callback: 'choicesReadyCredential'
});
$scope.addCredential = function () {
$state.transitionTo('credentials.add');
$scope.addCredential = function() {
$state.go('credentials.add');
};
$scope.editCredential = function (id) {
$state.transitionTo('credentials.edit', {credential_id: id});
$scope.editCredential = function(id) {
$state.go('credentials.edit', { credential_id: id });
};
$scope.deleteCredential = function (id, name) {
var action = function () {
$scope.deleteCredential = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function () {
.success(function() {
if (parseInt($state.params.credential_id) === id) {
$state.go("^", null, {reload: true});
$state.go("^", null, { reload: true });
} else {
$scope.search(list.iterator);
// @issue: OLD SEARCH
// $scope.search(list.iterator);
}
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
@ -126,97 +75,74 @@ export function CredentialsList($scope, $rootScope, $location, $log,
actionText: 'DELETE'
});
};
$scope.$emit('choicesReadyCredential');
}
CredentialsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'GetChoices', 'Wait',
'$state', '$filter', 'rbacUiControlService'
'$stateParams', 'Rest', 'Alert', 'CredentialList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset'
];
export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
ReturnToCaller, ClearScope, GenerateList, SearchInit, PaginateInit,
LookUpInit, OrganizationList, GetBasePath, GetChoices, Empty, KindChange,
ClearScope, GetBasePath, GetChoices, Empty, KindChange,
OwnerChange, FormSave, $state, CreateSelect2) {
ClearScope();
// Inject dynamic view
var form = CredentialForm,
generator = GenerateForm,
defaultUrl = GetBasePath('credentials'),
url;
$scope.keyEntered = false;
$scope.permissionsTooltip = 'Please save before assigning permissions';
generator.inject(form, { mode: 'add', related: false, scope: $scope });
generator.reset();
init();
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options'
});
function init() {
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_become_method',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
CreateSelect2({
element: '#credential_kind',
multiple: false
});
$scope.canShareCredential = false;
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$rootScope.$watch('current_user', function(){
try {
if ($rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
$scope.keyEntered = false;
$scope.permissionsTooltip = 'Please save before assigning permissions';
var orgUrl = ($rootScope.current_user.is_superuser) ?
GetBasePath("organizations") :
$rootScope.current_user.url + "admin_of_organizations?";
// Create LookUpInit for organizations
LookUpInit({
scope: $scope,
url: orgUrl,
form: form,
list: OrganizationList,
field: 'organization',
input_type: 'radio',
autopopulateLookup: false
});
// determine if the currently logged-in user may share this credential
// previous commentary said: "$rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet"
// I'm 99% sure this state's will never resolve block will be rejected if setup surrounding config endpoint hasn't completed
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
catch(err){
// $rootScope.current_user isn't available because a call to the config endpoint hasn't finished resolving yet
}
});
}
if (!Empty($stateParams.user_id)) {
// Get the username based on incoming route
@ -226,10 +152,10 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
url = GetBasePath('users') + $stateParams.user_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
.success(function(data) {
$scope.user_username = data.username;
})
.error(function (data, status) {
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user. GET status: ' + status });
});
} else if (!Empty($stateParams.team_id)) {
@ -240,10 +166,10 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
url = GetBasePath('teams') + $stateParams.team_id + '/';
Rest.setUrl(url);
Rest.get()
.success(function (data) {
.success(function(data) {
$scope.team_name = data.name;
})
.error(function (data, status) {
.error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve team. GET status: ' + status });
});
} else {
@ -263,32 +189,30 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
});
// Handle Kind change
$scope.kindChange = function () {
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
// Save
$scope.formSave = function () {
generator.clearApiErrors();
generator.checkAutoFill();
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'add' });
}
};
$scope.formCancel = function () {
$state.transitionTo('credentials');
$scope.formCancel = function() {
$state.go('credentials');
};
// Password change
$scope.clearPWConfirm = function (fld) {
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function (fld, associated) {
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
@ -313,7 +237,7 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
};
// Click clear button
$scope.clear = function (fld, associated) {
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
@ -324,54 +248,88 @@ export function CredentialsAdd($scope, $rootScope, $compile, $location, $log,
CredentialsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
'SearchInit', 'PaginateInit', 'LookUpInit', 'OrganizationList',
'GetBasePath', 'GetChoices', 'Empty', 'KindChange', 'OwnerChange',
'FormSave', '$state', 'CreateSelect2'
'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'Empty', 'KindChange',
'OwnerChange', 'FormSave', '$state', 'CreateSelect2'
];
export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
$stateParams, CredentialForm, GenerateForm, Rest, Alert, ProcessErrors,
RelatedSearchInit, RelatedPaginateInit, ReturnToCaller, ClearScope, Prompt,
GetBasePath, GetChoices, KindChange, OrganizationList, LookUpInit, Empty,
OwnerChange, FormSave, Wait, $state, CreateSelect2, Authorization) {
if (!$rootScope.current_user) {
Authorization.restoreUserInfo();
}
$stateParams, CredentialForm, Rest, Alert, ProcessErrors, ClearScope, Prompt,
GetBasePath, GetChoices, KindChange, Empty, OwnerChange, FormSave, Wait,
$state, CreateSelect2, Authorization) {
ClearScope();
var defaultUrl = GetBasePath('credentials'),
generator = GenerateForm,
form = CredentialForm,
base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.credential_id,
relatedSets = {};
id = $stateParams.credential_id;
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
$scope.id = id;
init();
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
function init() {
$scope.id = id;
$scope.$watch('credential_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.canShareCredential = false;
Wait('start');
if (!$rootScope.current_user) {
Authorization.restoreUserInfo();
}
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
callback: 'choicesReadyCredential'
});
$scope.canShareCredential = false;
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
if ($rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
if ($rootScope.current_user && $rootScope.current_user.is_superuser) {
$scope.canShareCredential = true;
} else {
Rest.setUrl(`/api/v1/users/${$rootScope.current_user.id}/admin_of_organizations`);
Rest.get()
.success(function(data) {
$scope.canShareCredential = (data.count) ? true : false;
}).error(function(data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to find if users is admin of org' + status });
});
}
// if the credential is assigned to an organization, allow permission delegation
// do NOT use $scope.organization in a view directive to determine if a credential is associated with an org
// @todo why not? ^ and what is this type check for a number doing - should this be a type check for undefined?
$scope.disablePermissionAssignment = typeof($scope.organization) === 'number' ? false : true;
if ($scope.disablePermissionAssignment) {
$scope.permissionsTooltip = 'Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.';
}
setAskCheckboxes();
KindChange({
scope: $scope,
form: form,
reset: false
});
OwnerChange({ scope: $scope });
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
}
function setAskCheckboxes() {
@ -398,64 +356,14 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
}
}
}
if ($scope.removeCredentialLoaded) {
$scope.removeCredentialLoaded();
}
$scope.removeCredentialLoaded = $scope.$on('credentialLoaded', function () {
// if the credential is assigned to an organization, allow permission delegation
// do NOT use $scope.organization in a view directive to determine if a credential is associated with an org
$scope.disablePermissionAssignment = typeof($scope.organization) === 'number' ? false : true;
if ($scope.disablePermissionAssignment){
$scope.permissionsTooltip = 'Credentials are only shared within an organization. Assign credentials to an organization to delegate credential permissions. The organization cannot be edited after credentials are assigned.';
}
var set;
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
var orgUrl = ($rootScope.current_user.is_superuser) ?
GetBasePath("organizations") :
$rootScope.current_user.url + "admin_of_organizations?";
// create LookUpInit for organizations
LookUpInit({
scope: $scope,
url: orgUrl,
form: form,
current_item: $scope.organization,
list: OrganizationList,
field: 'organization',
input_type: 'radio',
autopopulateLookup: false
});
setAskCheckboxes();
KindChange({
scope: $scope,
form: form,
reset: false
});
OwnerChange({ scope: $scope });
$scope.$watch("ssh_key_data", function(val) {
if (val === "" || val === null || val === undefined) {
$scope.keyEntered = false;
$scope.ssh_key_unlock_ask = false;
$scope.ssh_key_unlock = "";
} else {
$scope.keyEntered = true;
}
});
Wait('stop');
});
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function () {
$scope.removeChoicesReady = $scope.$on('choicesReadyCredential', function() {
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl + ':id/');
Rest.get({ params: { id: id } })
.success(function (data) {
.success(function(data) {
if (data && data.summary_fields &&
data.summary_fields.organization &&
data.summary_fields.organization.id) {
@ -481,7 +389,6 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
}
relatedSets = form.relatedSets(data.related);
if (!Empty($scope.user)) {
$scope.owner = 'user';
@ -530,118 +437,93 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
});
switch (data.kind) {
case 'aws':
$scope.access_key = data.username;
$scope.secret_key = data.password;
master.access_key = $scope.access_key;
master.secret_key = $scope.secret_key;
break;
case 'ssh':
$scope.ssh_password = data.password;
master.ssh_password = $scope.ssh_password;
break;
case 'rax':
$scope.api_key = data.password;
master.api_key = $scope.api_key;
break;
case 'gce':
$scope.email_address = data.username;
$scope.project = data.project;
break;
case 'azure':
$scope.subscription = data.username;
break;
case 'aws':
$scope.access_key = data.username;
$scope.secret_key = data.password;
master.access_key = $scope.access_key;
master.secret_key = $scope.secret_key;
break;
case 'ssh':
$scope.ssh_password = data.password;
master.ssh_password = $scope.ssh_password;
break;
case 'rax':
$scope.api_key = data.password;
master.api_key = $scope.api_key;
break;
case 'gce':
$scope.email_address = data.username;
$scope.project = data.project;
break;
case 'azure':
$scope.subscription = data.username;
break;
}
$scope.credential_obj = data;
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
$scope.$emit('credentialLoaded');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status });
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to retrieve Credential: ' + $stateParams.id + '. GET status: ' + status
});
});
});
Wait('start');
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
callback: 'choicesReadyCredential'
});
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'become_method',
variable: 'become_options'
});
// Save changes to the parent
$scope.formSave = function () {
generator.clearApiErrors();
generator.checkAutoFill({ scope: $scope });
$scope.formSave = function() {
if ($scope[form.name + '_form'].$valid) {
FormSave({ scope: $scope, mode: 'edit' });
}
};
// Handle Owner change
$scope.ownerChange = function () {
$scope.ownerChange = function() {
OwnerChange({ scope: $scope });
};
// Handle Kind change
$scope.kindChange = function () {
$scope.kindChange = function() {
KindChange({ scope: $scope, form: form, reset: true });
};
$scope.formCancel = function () {
$scope.formCancel = function() {
$state.transitionTo('credentials');
};
// Related set: Add button
$scope.add = function (set) {
$scope.add = function(set) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/add');
};
// Related set: Edit button
$scope.edit = function (set, id) {
$scope.edit = function(set, id) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set + '/' + id);
};
// Related set: Delete button
$scope['delete'] = function (set, itm_id, name, title) {
$scope['delete'] = function(set, itm_id, name, title) {
$rootScope.flashMessage = null;
var action = function () {
var action = function() {
var url = defaultUrl + id + '/' + set + '/';
Rest.setUrl(url);
Rest.post({
id: itm_id,
disassociate: 1
})
.success(function () {
$('#prompt-modal').modal('hide');
$scope.search(form.related[set].iterator);
id: itm_id,
disassociate: 1
})
.error(function (data, status) {
.success(function() {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
// @issue: OLD SEARCH
// $scope.search(form.related[set].iterator);
})
.error(function(data, status) {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. POST returned status: ' + status
});
});
@ -657,14 +539,14 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
};
// Password change
$scope.clearPWConfirm = function (fld) {
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
// Respond to 'Ask at runtime?' checkbox
$scope.ask = function (fld, associated) {
$scope.ask = function(fld, associated) {
if ($scope[fld + '_ask']) {
$scope[fld] = 'ASK';
$("#" + form.name + "_" + fld + "_input").attr("type", "text");
@ -688,7 +570,7 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
}
};
$scope.clear = function (fld, associated) {
$scope.clear = function(fld, associated) {
$scope[fld] = '';
$scope[associated] = '';
$scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
@ -698,9 +580,8 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
}
CredentialsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'CredentialForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'RelatedSearchInit', 'RelatedPaginateInit',
'ReturnToCaller', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices',
'KindChange', 'OrganizationList', 'LookUpInit', 'Empty', 'OwnerChange',
'$log', '$stateParams', 'CredentialForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'Prompt', 'GetBasePath', 'GetChoices',
'KindChange', 'Empty', 'OwnerChange',
'FormSave', 'Wait', '$state', 'CreateSelect2', 'Authorization'
];

View File

@ -12,15 +12,17 @@
export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobEventList, GenerateList,
Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren,
FormatDate, EventView, Refresh, Wait) {
Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren,
FormatDate, EventView, Wait) {
ClearScope();
var list = JobEventList,
defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_events/', //?parent__isnull=1';
generator = GenerateList,
page;
generator = GenerateList;
// @issue: OLD SEARCH
// var defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_events/', //?parent__isnull=1';
// page;
list.base = $location.path();
$scope.job_id = $stateParams.id;
@ -191,30 +193,31 @@ export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log
});
});
SearchInit({
scope: $scope,
set: 'jobevents',
list: list,
url: defaultUrl
});
page = ($stateParams.page) ? parseInt($stateParams.page,10) - 1 : null;
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl,
page: page
});
// Called from Inventories tab, host failed events link:
if ($stateParams.host) {
$scope[list.iterator + 'SearchField'] = 'host';
$scope[list.iterator + 'SearchValue'] = $stateParams.host;
$scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
}
$scope.search(list.iterator, $stateParams.page);
// @issue: OLD SEARCH
// SearchInit({
// scope: $scope,
// set: 'jobevents',
// list: list,
// url: defaultUrl
// });
//
// page = ($stateParams.page) ? parseInt($stateParams.page,10) - 1 : null;
//
// PaginateInit({
// scope: $scope,
// list: list,
// url: defaultUrl,
// page: page
// });
//
// // Called from Inventories tab, host failed events link:
// if ($stateParams.host) {
// $scope[list.iterator + 'SearchField'] = 'host';
// $scope[list.iterator + 'SearchValue'] = $stateParams.host;
// $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
// }
//
// $scope.search(list.iterator, $stateParams.page);
$scope.toggle = function (id) {
ToggleChildren({
@ -231,21 +234,24 @@ export function JobEventsList($sce, $filter, $scope, $rootScope, $location, $log
};
$scope.refresh = function () {
$scope.jobSearchSpin = true;
// @issue: OLD SEARCH
// $scope.jobSearchSpin = true;
$scope.jobLoading = true;
Wait('start');
Refresh({
scope: $scope,
set: 'jobevents',
iterator: 'jobevent',
url: $scope.current_url
});
// @issue: OLD SEARCH
// Refresh({
// scope: $scope,
// set: 'jobevents',
// iterator: 'jobevent',
// url: $scope.current_url
// });
};
}
JobEventsList.$inject = ['$sce', '$filter', '$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobEventList',
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'LookUpInit', 'ToggleChildren', 'FormatDate', 'EventView', 'Refresh', 'Wait'
'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'LookUpInit', 'ToggleChildren', 'FormatDate', 'EventView', 'Wait'
];
export function JobEventsEdit($scope, $rootScope, $compile, $location, $log, $stateParams, JobEventsForm, GenerateForm,

View File

@ -12,13 +12,14 @@
export function JobHostSummaryList($scope, $rootScope, $location, $log, $stateParams, Rest, Alert, JobHostList, GenerateList,
Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Refresh,
Prompt, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
JobStatusToolTip) {
ClearScope();
var list = JobHostList,
defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_host_summaries/',
// @issue: OLD SEARCH
// defaultUrl = GetBasePath('jobs') + $stateParams.id + '/job_host_summaries/',
view = GenerateList,
inventory;
@ -58,26 +59,27 @@ export function JobHostSummaryList($scope, $rootScope, $location, $log, $statePa
$scope.removeJobReady = $scope.$on('JobReady', function() {
view.inject(list, { mode: 'edit', scope: $scope });
SearchInit({
scope: $scope,
set: 'jobhosts',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
// Called from Inventories tab, host failed events link:
if ($stateParams.host_name) {
$scope[list.iterator + 'SearchField'] = 'host';
$scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
$scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
}
$scope.search(list.iterator);
// @issue: OLD SEARCH
// SearchInit({
// scope: $scope,
// set: 'jobhosts',
// list: list,
// url: defaultUrl
// });
//
// PaginateInit({
// scope: $scope,
// list: list,
// url: defaultUrl
// });
//
// // Called from Inventories tab, host failed events link:
// if ($stateParams.host_name) {
// $scope[list.iterator + 'SearchField'] = 'host';
// $scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
// $scope[list.iterator + 'SearchFieldLabel'] = list.fields.host.label;
// }
// $scope.search(list.iterator);
});
Rest.setUrl(GetBasePath('jobs') + $scope.job_id);
@ -107,12 +109,13 @@ export function JobHostSummaryList($scope, $rootScope, $location, $log, $statePa
};
$scope.refresh = function () {
$scope.search(list.iterator);
// @issue: OLD SEARCH
// $scope.search(list.iterator);
};
}
JobHostSummaryList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'JobHostList',
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'Refresh', 'JobStatusToolTip', 'Wait'
'generateList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'JobStatusToolTip', 'Wait'
];

View File

@ -8,148 +8,105 @@
* @ngdoc function
* @name controllers.function:Jobs
* @description This controller's for the jobs page
*/
*/
export function JobsListController ($rootScope, $log, $scope, $compile, $stateParams,
ClearScope, LoadSchedulesScope,
LoadJobsScope, AllJobsList, ScheduledJobsList, GetChoices, GetBasePath, Wait, $state) {
export function JobsListController($state, $rootScope, $log, $scope, $compile, $stateParams,
ClearScope, Find, DeleteJob, RelaunchJob, AllJobsList, ScheduledJobsList, GetBasePath, Dataset) {
ClearScope();
var jobs_scope, scheduled_scope,
choicesCount = 0,
listCount = 0,
api_complete = false,
scheduledJobsList = _.cloneDeep(ScheduledJobsList);
var list = AllJobsList;
$scope.jobsSelected = true;
init();
if ($scope.removeListLoaded) {
$scope.removeListLoaded();
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.showJobType = true;
_.forEach($scope[list.name], buildTooltips);
}
$scope.removeListLoaded = $scope.$on('listLoaded', function() {
listCount++;
if (listCount === 2) {
api_complete = true;
}
});
// After all choices are ready, load up the lists and populate the page
if ($scope.removeBuildJobsList) {
$scope.removeBuildJobsList();
function buildTooltips(job) {
job.status_tip = 'Job ' + job.status + ". Click for details.";
}
$scope.removeBuildJobsList = $scope.$on('buildJobsList', function() {
var opt, search_params={};
if (AllJobsList.fields.type) {
AllJobsList.fields.type.searchOptions = $scope.type_choices;
}
if ($stateParams.status) {
search_params[AllJobsList.iterator + 'SearchField'] = 'status';
search_params[AllJobsList.iterator + 'SelectShow'] = true;
search_params[AllJobsList.iterator + 'SearchSelectOpts'] = AllJobsList.fields.status.searchOptions;
search_params[AllJobsList.iterator + 'SearchFieldLabel'] = AllJobsList.fields.status.label.replace(/<br\>/g,' ');
search_params[AllJobsList.iterator + 'SearchType'] = '';
for (opt in AllJobsList.fields.status.searchOptions) {
if (AllJobsList.fields.status.searchOptions[opt].value === $stateParams.status) {
search_params[AllJobsList.iterator + 'SearchSelectValue'] = AllJobsList.fields.status.searchOptions[opt];
break;
}
}
}
jobs_scope = $scope.$new(true);
$scope.deleteJob = function(id) {
DeleteJob({ scope: $scope, id: id });
};
jobs_scope.viewJob = function (id) {
$state.transitionTo('jobDetail', {id: id});
$scope.relaunchJob = function(event, id) {
var list, job, typeId;
try {
$(event.target).tooltip('hide');
} catch (e) {
//ignore
}
job = Find({ list: list, key: 'id', val: id });
if (job.type === 'inventory_update') {
typeId = job.inventory_source;
} else if (job.type === 'project_update') {
typeId = job.project;
} else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command') {
typeId = job.id;
}
RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name });
};
$scope.refreshJobs = function() {
$state.go('.', null, { reload: true });
};
$scope.viewJobDetails = function(job) {
var goToJobDetails = function(state) {
$state.go(state, { id: job.id }, { reload: true });
};
jobs_scope.showJobType = true;
LoadJobsScope({
parent_scope: $scope,
scope: jobs_scope,
list: AllJobsList,
id: 'active-jobs',
url: GetBasePath('unified_jobs') + '?status__in=pending,waiting,running,completed,failed,successful,error,canceled,new&order_by=-finished',
pageSize: 20,
searchParams: search_params,
spinner: false
});
scheduled_scope = $scope.$new(true);
scheduledJobsList.basePath = GetBasePath('schedules') + '?next_run__isnull=false';
LoadSchedulesScope({
parent_scope: $scope,
scope: scheduled_scope,
list: scheduledJobsList,
pageSize: 20,
id: 'scheduled-jobs-tab',
searchSize: 'col-lg-4 col-md-4 col-sm-4 col-xs-12',
url: scheduledJobsList.basePath
});
$scope.refreshJobs = function() {
jobs_scope.search('all_job');
scheduled_scope.search('schedule');
};
function clearTabs() {
$scope.jobsSelected = false;
$scope.schedulesSelected = false;
}
$scope.toggleTab = function(tab) {
clearTabs();
if (tab === "jobs") {
$scope.jobsSelected = true;
} else if (tab === "scheduled") {
$scope.schedulesSelected = true;
}
};
$scope.$on('ws-jobs', function() {
$scope.refreshJobs();
});
$scope.$on('ws-schedules', function() {
if (api_complete) {
scheduled_scope.search('schedule');
}
});
});
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
choicesCount++;
if (choicesCount === 2) {
$scope.$emit('buildJobsList');
switch (job.type) {
case 'job':
goToJobDetails('jobDetail');
break;
case 'ad_hoc_command':
goToJobDetails('adHocJobStdout');
break;
case 'system_job':
goToJobDetails('managementJobStdout');
break;
case 'project_update':
goToJobDetails('scmUpdateStdout');
break;
case 'inventory_update':
goToJobDetails('inventorySyncStdout');
break;
}
};
$scope.refreshJobs = function() {
$state.reload();
};
if ($rootScope.removeJobStatusChange) {
$rootScope.removeJobStatusChange();
}
$rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange-jobs', function() {
$scope.refreshJobs();
});
Wait('start');
GetChoices({
scope: $scope,
url: GetBasePath('unified_jobs'),
field: 'status',
variable: 'status_choices',
callback: 'choicesReady'
});
GetChoices({
scope: $scope,
url: GetBasePath('unified_jobs'),
field: 'type',
variable: 'type_choices',
callback: 'choicesReady'
if ($rootScope.removeScheduleStatusChange) {
$rootScope.removeScheduleStatusChange();
}
$rootScope.removeScheduleStatusChange = $rootScope.$on('ScheduleStatusChange', function() {
$state.reload();
});
}
JobsListController.$inject = ['$rootScope', '$log', '$scope', '$compile', '$stateParams',
'ClearScope', 'LoadSchedulesScope', 'LoadJobsScope',
'AllJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath', 'Wait', '$state'];
JobsListController.$inject = ['$state', '$rootScope', '$log', '$scope', '$compile', '$stateParams',
'ClearScope', 'Find', 'DeleteJob', 'RelaunchJob', 'AllJobsList', 'ScheduledJobsList', 'GetBasePath', 'Dataset'
];

View File

@ -8,85 +8,64 @@
* @ngdoc function
* @name controllers.function:Projects
* @description This controller's for the projects page
*/
*/
export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, ProjectList, GenerateList, Prompt, SearchInit,
PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath,
SelectionInit, ProjectUpdate, Refresh, Wait, GetChoices, Empty,
Find, GetProjectIcon, GetProjectToolTip, $filter, $state, rbacUiControlService,
i18n) {
ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('projects')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
Wait('start');
export function ProjectsList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, ProjectList, Prompt, ReturnToCaller, ClearScope, ProcessErrors,
GetBasePath, ProjectUpdate, Wait, GetChoices, Empty, Find, GetProjectIcon,
GetProjectToolTip, $filter, $state, rbacUiControlService, Dataset, i18n) {
var list = ProjectList,
defaultUrl = GetBasePath('projects') + ($stateParams.status ? '?status__in=' + $stateParams.status : ''),
view = GenerateList,
base = $location.path().replace(/^\//, '').split('/')[0],
mode = (base === 'projects') ? 'edit' : 'select',
url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl,
choiceCount = 0;
view.inject(list, { mode: mode, scope: $scope });
defaultUrl = GetBasePath('projects');
$rootScope.flashMessage = null;
$scope.projectLoading = true;
init();
if (mode === 'select') {
SelectionInit({
scope: $scope,
list: list,
url: url,
returnToCaller: 1
});
}
function init() {
$scope.canAdd = false;
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
Wait('stop');
if ($scope.projects) {
$scope.projects.forEach(function(project, i) {
$scope.projects[i].statusIcon = GetProjectIcon(project.status);
$scope.projects[i].statusTip = GetProjectToolTip(project.status);
$scope.projects[i].scm_update_tooltip = i18n._("Start an SCM update");
$scope.projects[i].scm_schedule_tooltip = i18n._("Schedule future SCM updates");
$scope.projects[i].scm_type_class = "";
if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') {
$scope.projects[i].statusTip = i18n._('Canceled. Click for details');
}
if (project.status === 'running' || project.status === 'updating') {
$scope.projects[i].scm_update_tooltip = i18n._("SCM update currently running");
$scope.projects[i].scm_type_class = "btn-disabled";
}
$scope.project_scm_type_options.forEach(function(type) {
if (type.value === project.scm_type) {
$scope.projects[i].scm_type = type.label;
if (type.label === 'Manual') {
$scope.projects[i].scm_update_tooltip = i18n._('Manual projects do not require an SCM update');
$scope.projects[i].scm_schedule_tooltip = i18n._('Manual projects do not require a schedule');
$scope.projects[i].scm_type_class = 'btn-disabled';
$scope.projects[i].statusTip = i18n._('Not configured for SCM');
$scope.projects[i].statusIcon = 'none';
}
}
});
rbacUiControlService.canAdd('projects')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
}
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
_.forEach($scope[list.name], buildTooltips);
$rootScope.flashMessage = null;
}
$scope.$watch(`${list.name}`, function() {
_.forEach($scope[list.name], buildTooltips);
});
function buildTooltips(project) {
project.statusIcon = GetProjectIcon(project.status);
project.statusTip = GetProjectToolTip(project.status);
project.scm_update_tooltip = "Start an SCM update";
project.scm_schedule_tooltip = i18n._("Schedule future SCM updates");
project.scm_type_class = "";
if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') {
project.statusTip = i18n._('Canceled. Click for details');
}
if (project.status === 'running' || project.status === 'updating') {
project.scm_update_tooltip = i18n._("SCM update currently running");
project.scm_type_class = "btn-disabled";
}
if (project.scm_type === 'manual') {
project.scm_update_tooltip = i18n._('Manual projects do not require an SCM update');
project.scm_schedule_tooltip = i18n._('Manual projects do not require a schedule');
project.scm_type_class = 'btn-disabled';
project.statusTip = i18n._('Not configured for SCM');
project.statusIcon = 'none';
}
}
$scope.$on(`ws-jobs`, function(e, data) {
var project;
$log.debug(data);
@ -98,9 +77,9 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
$log.debug('Received event for project: ' + project.name);
$log.debug('Status changed to: ' + data.status);
if (data.status === 'successful' || data.status === 'failed') {
$scope.search(list.iterator, null, null, null, null, false);
}
else {
// @issue: OLD SEARCH
// $scope.search(list.iterator, null, null, null, null, false);
} else {
project.scm_update_tooltip = "SCM update currently running";
project.scm_type_class = "btn-disabled";
}
@ -111,95 +90,12 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
}
});
if ($scope.removeChoicesHere) {
$scope.removeChoicesHere();
}
$scope.removeChoicesHere = $scope.$on('choicesCompleteProjectList', function () {
var opt;
list.fields.scm_type.searchOptions = $scope.project_scm_type_options;
list.fields.status.searchOptions = $scope.project_status_options;
if ($stateParams.scm_type && $stateParams.status) {
// Request coming from home page. User wants all errors for an scm_type
defaultUrl += '?status=' + $stateParams.status;
}
SearchInit({
scope: $scope,
set: 'projects',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
if ($stateParams.scm_type) {
$scope[list.iterator + 'SearchType'] = '';
$scope[list.iterator + 'SearchField'] = 'scm_type';
$scope[list.iterator + 'SelectShow'] = true;
$scope[list.iterator + 'SearchSelectOpts'] = list.fields.scm_type.searchOptions;
$scope[list.iterator + 'SearchFieldLabel'] = list.fields.scm_type.label.replace(/<br\>/g, ' ');
for (opt in list.fields.scm_type.searchOptions) {
if (list.fields.scm_type.searchOptions[opt].value === $stateParams.scm_type) {
$scope[list.iterator + 'SearchSelectValue'] = list.fields.scm_type.searchOptions[opt];
break;
}
}
} else if ($stateParams.status) {
$scope[list.iterator + 'SearchType'] = '';
$scope[list.iterator + 'SearchValue'] = $stateParams.status;
$scope[list.iterator + 'SearchField'] = 'status';
$scope[list.iterator + 'SelectShow'] = true;
$scope[list.iterator + 'SearchFieldLabel'] = list.fields.status.label;
$scope[list.iterator + 'SearchSelectOpts'] = list.fields.status.searchOptions;
for (opt in list.fields.status.searchOptions) {
if (list.fields.status.searchOptions[opt].value === $stateParams.status) {
$scope[list.iterator + 'SearchSelectValue'] = list.fields.status.searchOptions[opt];
break;
}
}
}
$scope.search(list.iterator);
});
if ($scope.removeChoicesReadyList) {
$scope.removeChoicesReadyList();
}
$scope.removeChoicesReadyList = $scope.$on('choicesReadyProjectList', function () {
choiceCount++;
if (choiceCount === 2) {
$scope.$emit('choicesCompleteProjectList');
}
});
// Load options for status --used in search
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'status',
variable: 'project_status_options',
callback: 'choicesReadyProjectList'
});
// Load the list of options for Kind
GetChoices({
scope: $scope,
url: defaultUrl,
field: 'scm_type',
variable: 'project_scm_type_options',
callback: 'choicesReadyProjectList'
});
$scope.addProject = function () {
$state.transitionTo('projects.add');
$scope.addProject = function() {
$state.go('projects.add');
};
$scope.editProject = function (id) {
$state.transitionTo('projects.edit', {id: id});
$scope.editProject = function(id) {
$state.go('projects.edit', { project_id: id });
};
if ($scope.removeGoToJobDetails) {
@ -213,7 +109,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
// Grab the id from summary_fields
var id = (data.summary_fields.current_update) ? data.summary_fields.current_update.id : data.summary_fields.last_update.id;
$state.go('scmUpdateStdout', {id: id});
$state.go('scmUpdateStdout', { id: id });
} else {
Alert(i18n._('No Updates Available'), i18n._('There is no SCM update information available for this project. An update has not yet been ' +
@ -221,7 +117,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
}
});
$scope.showSCMStatus = function (id) {
$scope.showSCMStatus = function(id) {
// Refresh the project list
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (Empty(project.scm_type) || project.scm_type === 'Manual') {
@ -241,18 +137,19 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
}
};
$scope.deleteProject = function (id, name) {
var action = function () {
$scope.deleteProject = function(id, name) {
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function () {
if (parseInt($state.params.id) === id) {
$state.go("^", null, {reload: true});
.success(function() {
if (parseInt($state.params.project_id) === id) {
$state.go("^", null, { reload: true });
} else {
$scope.search(list.iterator);
// @issue: OLD SEARCH
// $scope.search(list.iterator);
}
})
.error(function (data, status) {
@ -272,7 +169,7 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
if ($scope.removeCancelUpdate) {
$scope.removeCancelUpdate();
}
$scope.removeCancelUpdate = $scope.$on('Cancel_Update', function (e, url) {
$scope.removeCancelUpdate = $scope.$on('Cancel_Update', function(e, url) {
// Cancel the project update process
Rest.setUrl(url);
Rest.post()
@ -288,12 +185,12 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
if ($scope.removeCheckCancel) {
$scope.removeCheckCancel();
}
$scope.removeCheckCancel = $scope.$on('Check_Cancel', function (e, data) {
$scope.removeCheckCancel = $scope.$on('Check_Cancel', function(e, data) {
// Check that we 'can' cancel the update
var url = data.related.cancel;
Rest.setUrl(url);
Rest.get()
.success(function (data) {
.success(function(data) {
if (data.can_cancel) {
$scope.$emit('Cancel_Update', url);
} else {
@ -306,14 +203,14 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
});
});
$scope.cancelUpdate = function (id, name) {
$scope.cancelUpdate = function(id, name) {
Rest.setUrl(GetBasePath("projects") + id);
Rest.get()
.success(function (data) {
.success(function(data) {
if (data.related.current_update) {
Rest.setUrl(data.related.current_update);
Rest.get()
.success(function (data) {
.success(function(data) {
$scope.$emit('Check_Cancel', data);
})
.error(function (data, status) {
@ -331,15 +228,10 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
});
};
$scope.refresh = function () {
$scope.search(list.iterator);
};
$scope.SCMUpdate = function (project_id, event) {
$scope.SCMUpdate = function(project_id, event) {
try {
$(event.target).tooltip('hide');
}
catch(e) {
} catch (e) {
// ignore
}
$scope.projects.every(function(project) {
@ -362,62 +254,52 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
$scope.editSchedules = function(id) {
var project = Find({ list: $scope.projects, key: 'id', val: id });
if (!(project.scm_type === "Manual" || Empty(project.scm_type)) && !(project.status === 'updating' || project.status === 'running' || project.status === 'pending')) {
$state.go('projectSchedules', {id: id});
$state.go('projectSchedules', { id: id });
}
};
}
ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'ProjectList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'ProjectUpdate',
'Refresh', 'Wait', 'GetChoices', 'Empty', 'Find',
'GetProjectIcon', 'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService',
'i18n'
ProjectsList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams',
'Rest', 'Alert', 'ProjectList', 'Prompt', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'ProjectUpdate', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon',
'GetProjectToolTip', '$filter', '$state', 'rbacUiControlService', 'Dataset', 'i18n'
];
export function ProjectsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, GenerateForm, ProjectsForm, Rest, Alert, ProcessErrors,
GetBasePath, GetProjectPath, GetChoices, Wait, $state, CreateSelect2) {
export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $log,
$stateParams, ProjectsForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ReturnToCaller, GetProjectPath, LookUpInit,
OrganizationList, CredentialList, GetChoices, DebugForm, Wait, $state,
CreateSelect2, i18n) {
Rest.setUrl(GetBasePath('projects'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a project.', 'alert-info');
}
});
ClearScope();
// Inject dynamic view
var form = ProjectsForm(),
generator = GenerateForm,
base = $location.path().replace(/^\//, '').split('/')[0],
defaultUrl = GetBasePath('projects'),
master = {};
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
init();
generator.inject(form, { mode: 'add', related: false, scope: $scope });
generator.reset();
function init() {
Rest.setUrl(GetBasePath('projects'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a project.', 'alert-info');
}
});
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
}
GetProjectPath({ scope: $scope, master: master });
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function () {
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
var i;
for (i = 0; i < $scope.scm_type_options.length; i++) {
if ($scope.scm_type_options[i].value === '') {
$scope.scm_type_options[i].value="manual";
$scope.scm_type_options[i].value = "manual";
//$scope.scm_type = $scope.scm_type_options[i];
break;
}
@ -440,33 +322,14 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
variable: 'scm_type_options',
callback: 'choicesReady'
});
LookUpInit({
scope: $scope,
form: form,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
LookUpInit({
scope: $scope,
url: GetBasePath('credentials') + '?kind=scm',
form: form,
list: CredentialList,
field: 'credential',
input_type: "radio"
});
CreateSelect2({
element: '#local-path-select',
multiple: false
});
// Save
$scope.formSave = function () {
var i, fld, url, data={};
generator.clearApiErrors();
$scope.formSave = function() {
var i, fld, url, data = {};
data = {};
for (fld in form.fields) {
if (form.fields[fld].type === 'checkbox_group') {
@ -480,8 +343,8 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
}
}
if($scope.scm_type.value === "manual"){
data.scm_type = "" ;
if ($scope.scm_type.value === "manual") {
data.scm_type = "";
data.local_path = $scope.local_path.value;
} else {
data.scm_type = $scope.scm_type.value;
@ -492,26 +355,18 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
Wait('start');
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
.success(function(data) {
$scope.addedItem = data.id;
Refresh({
scope: $scope,
set: 'projects',
iterator: 'project',
url: $scope.current_url
});
$state.go('projects.edit', {id: data.id}, {reload: true});
$state.go('projects.edit', { id: data.id }, { reload: true });
})
.error(function (data, status) {
.error(function(data, status) {
Wait('stop');
ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'),
msg: i18n._('Failed to create new project. POST returned status: ') + status });
});
};
$scope.scmChange = function () {
$scope.scmChange = function() {
// When an scm_type is set, path is not required
if ($scope.scm_type) {
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
@ -520,7 +375,7 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
}
// Dynamically update popover values
if($scope.scm_type.value) {
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.urlPopover = i18n._('<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
@ -528,7 +383,7 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
'<p><strong>Note:</strong> When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.');
break;
break;
case 'svn':
$scope.urlPopover = i18n._('<p>Example URLs for Subversion SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
@ -548,89 +403,47 @@ export function ProjectsAdd(Refresh, $scope, $rootScope, $compile, $location, $l
}
};
$scope.formCancel = function () {
$state.transitionTo('projects');
$scope.formCancel = function() {
$state.go('projects');
};
}
ProjectsAdd.$inject = ['Refresh', '$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'ProjectsForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ReturnToCaller',
'GetProjectPath', 'LookUpInit', 'OrganizationList', 'CredentialList',
'GetChoices', 'DebugForm', 'Wait', '$state', 'CreateSelect2', 'i18n'
];
ProjectsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'GenerateForm', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath',
'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n'];
export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
$stateParams, ProjectsForm, GenerateForm, Rest, Alert, ProcessErrors,
RelatedSearchInit, RelatedPaginateInit, Prompt, ClearScope, GetBasePath,
ReturnToCaller, GetProjectPath, Authorization, CredentialList, LookUpInit,
GetChoices, Empty, DebugForm, Wait, SchedulesControllerInit,
SchedulesListInit, SchedulesList, ProjectUpdate, $state, CreateSelect2,
OrganizationList, NotificationsListInit, ToggleNotification, i18n) {
$stateParams, ProjectsForm, Rest, Alert, ProcessErrors,
Prompt, ClearScope, GetBasePath, GetProjectPath, Authorization,
GetChoices, Empty, DebugForm, Wait, ProjectUpdate, $state, CreateSelect2, ToggleNotification, i18n) {
ClearScope('htmlTemplate');
var form = ProjectsForm(),
defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/',
master = {},
id = $stateParams.project_id;
init();
function init() {
$scope.project_local_paths = [];
$scope.base_dir = '';
}
$scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
// Inject dynamic view
var form = ProjectsForm(),
generator = GenerateForm,
defaultUrl = GetBasePath('projects') + $stateParams.id + '/',
base = $location.path().replace(/^\//, '').split('/')[0],
master = {}, i,
id = $stateParams.id,
relatedSets = {};
// remove "type" field from search options
CredentialList = _.cloneDeep(CredentialList);
CredentialList.fields.kind.noSearch = true;
SchedulesList.well = false;
generator.inject(form, {
mode: 'edit',
related: true,
scope: $scope
});
generator.reset();
$scope.project_local_paths = [];
$scope.base_dir = '';
if ($scope.removerelatedschedules) {
$scope.removerelatedschedules();
}
$scope.removerelatedschedules = $scope.$on('relatedschedules', function() {
SchedulesListInit({
scope: $scope,
list: SchedulesList,
choices: null,
related: true
});
});
// After the project is loaded, retrieve each related set
if ($scope.projectLoadedRemove) {
$scope.projectLoadedRemove();
}
$scope.projectLoadedRemove = $scope.$on('projectLoaded', function () {
var set, opts=[];
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
SchedulesControllerInit({
scope: $scope,
parent_scope: $scope,
iterator: 'schedule'
});
$scope.projectLoadedRemove = $scope.$on('projectLoaded', function() {
var opts = [];
if (Authorization.getUserInfo('is_superuser') === true) {
GetProjectPath({ scope: $scope, master: master });
@ -644,42 +457,19 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
$scope.base_dir = 'You do not have access to view this property';
}
LookUpInit({
url: GetBasePath('credentials') + '?kind=scm',
scope: $scope,
form: form,
list: CredentialList,
field: 'credential',
input_type: 'radio'
});
LookUpInit({
scope: $scope,
form: form,
current_item: $scope.organization,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
Wait('stop');
NotificationsListInit({
scope: $scope,
url: GetBasePath('projects'),
id: $scope.project_obj.id
});
$scope.scmChange();
});
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('choicesReady', function () {
$scope.removeChoicesReady = $scope.$on('choicesReady', function() {
let i;
for (i = 0; i < $scope.scm_type_options.length; i++) {
if ($scope.scm_type_options[i].value === '') {
$scope.scm_type_options[i].value = "manual";
@ -689,7 +479,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl);
Rest.get({ params: { id: id } })
.success(function (data) {
.success(function(data) {
var fld, i;
for (fld in form.fields) {
if (form.fields[fld].type === 'checkbox_group') {
@ -705,15 +495,12 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
}
if (form.fields[fld].sourceModel && data.summary_fields &&
data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
relatedSets = form.relatedSets(data.related);
data.scm_type = (Empty(data.scm_type)) ? 'manual' : data.scm_type;
for (i = 0; i < $scope.scm_type_options.length; i++) {
@ -743,18 +530,6 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
});
$scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch';
// Initialize related search functions. Doing it here to make sure relatedSets object is populated.
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
$scope.scm_update_tooltip = "Start an SCM update";
$scope.scm_type_class = "";
if (data.status === 'running' || data.status === 'updating') {
@ -791,14 +566,12 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
var notifier = this.notification;
try {
$(event.target).tooltip('hide');
}
catch(e) {
} catch (e) {
// ignore
}
ToggleNotification({
scope: $scope,
url: $scope.project_url,
id: $scope.project_obj.id,
url: $scope.project_obj.url,
notifier: notifier,
column: column,
callback: 'NotificationRefresh'
@ -806,9 +579,9 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
};
// Save changes to the parent
$scope.formSave = function () {
$scope.formSave = function() {
var fld, i, params;
generator.clearApiErrors();
//generator.clearApiErrors();
Wait('start');
$rootScope.flashMessage = null;
params = {};
@ -824,8 +597,8 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
}
}
if($scope.scm_type.value === "manual"){
params.scm_type = "" ;
if ($scope.scm_type.value === "manual") {
params.scm_type = "";
params.local_path = $scope.local_path.value;
} else {
params.scm_type = $scope.scm_type.value;
@ -836,37 +609,26 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
Rest.put(params)
.success(function() {
Wait('stop');
$state.go($state.current, {}, {reload: true});
$state.go($state.current, {}, { reload: true });
})
.error(function (data, status) {
.error(function(data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to update project: ' + id + '. PUT status: ' + status });
});
};
// Related set: Add button
$scope.add = function (set) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.id + '/' + set);
};
// Related set: Edit button
$scope.edit = function (set, id) {
$rootScope.flashMessage = null;
$location.path('/' + set + '/' + id);
};
// Related set: Delete button
$scope['delete'] = function (set, itm_id, name, title) {
var action = function () {
$scope['delete'] = function(set, itm_id, name, title) {
var action = function() {
var url = GetBasePath('projects') + id + '/' + set + '/';
$rootScope.flashMessage = null;
Rest.setUrl(url);
Rest.post({ id: itm_id, disassociate: 1 })
.success(function () {
.success(function() {
$('#prompt-modal').modal('hide');
$scope.search(form.related[set].iterator);
// @issue: OLD SEARCH
// $scope.search(form.related[set].iterator);
})
.error(function (data, status) {
.error(function(data, status) {
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST returned status: ' + status });
});
@ -880,7 +642,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
});
};
$scope.scmChange = function () {
$scope.scmChange = function() {
if ($scope.scm_type) {
$scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false;
$scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false;
@ -888,7 +650,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
}
// Dynamically update popover values
if($scope.scm_type.value) {
if ($scope.scm_type.value) {
switch ($scope.scm_type.value) {
case 'git':
$scope.urlPopover = i18n._('<p>Example URLs for GIT SCM include:</p><ul class=\"no-bullets\"><li>https://github.com/ansible/ansible.git</li>' +
@ -896,7 +658,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
'<p><strong>Note:</strong> When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' +
'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' +
'SSH. GIT read only protocol (git://) does not use username or password information.');
break;
break;
case 'svn':
$scope.urlPopover = i18n._('<p>Example URLs for Subversion SCM include:</p>' +
'<ul class=\"no-bullets\"><li>https://github.com/ansible/ansible</li><li>svn://servername.example.com/path</li>' +
@ -916,7 +678,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
}
};
$scope.SCMUpdate = function () {
$scope.SCMUpdate = function() {
if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) {
// ignore
} else if ($scope.project_obj.status === 'updating' || $scope.project_obj.status === 'running' || $scope.project_obj.status === 'pending') {
@ -926,17 +688,12 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log,
}
};
$scope.formCancel = function () {
$scope.formCancel = function() {
$state.transitionTo('projects');
};
}
ProjectsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'ProjectsForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt',
'ClearScope', 'GetBasePath', 'ReturnToCaller', 'GetProjectPath',
'Authorization', 'CredentialList', 'LookUpInit', 'GetChoices', 'Empty',
'DebugForm', 'Wait', 'SchedulesControllerInit', 'SchedulesListInit',
'SchedulesList', 'ProjectUpdate', '$state', 'CreateSelect2',
'OrganizationList', 'NotificationsListInit', 'ToggleNotification', 'i18n'
];
'$stateParams', 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'Prompt',
'ClearScope', 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty',
'DebugForm', 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', 'i18n'];

View File

@ -18,33 +18,33 @@ GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices) {
var base, id, url, parentObject;
base = $location.path().replace(/^\//, '').split('/')[0];
// base = $location.path().replace(/^\//, '').split('/')[0];
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function() {
var list = $scope.schedules;
list.forEach(function(element, idx) {
list[idx].play_tip = (element.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.';
});
});
// if ($scope.removePostRefresh) {
// $scope.removePostRefresh();
// }
// $scope.removePostRefresh = $scope.$on('PostRefresh', function() {
// var list = $scope.schedules;
// list.forEach(function(element, idx) {
// list[idx].play_tip = (element.enabled) ? 'Schedule is Active. Click to temporarily stop.' : 'Schedule is temporarily stopped. Click to activate.';
// });
// });
if ($scope.removeParentLoaded) {
$scope.removeParentLoaded();
}
$scope.removeParentLoaded = $scope.$on('ParentLoaded', function() {
url += "schedules/";
SchedulesList.well = true;
LoadSchedulesScope({
parent_scope: $scope,
scope: $scope,
list: SchedulesList,
id: 'schedule-list-target',
url: url,
pageSize: 20
});
});
// if ($scope.removeParentLoaded) {
// $scope.removeParentLoaded();
// }
// $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() {
// url += "schedules/";
// SchedulesList.well = true;
// LoadSchedulesScope({
// parent_scope: $scope,
// scope: $scope,
// list: SchedulesList,
// id: 'schedule-list-target',
// url: url,
// pageSize: 20
// });
// });
if ($scope.removeChoicesReady) {
@ -67,7 +67,8 @@ GetBasePath, Wait, Find, LoadSchedulesScope, GetChoices) {
});
$scope.refreshJobs = function() {
$scope.search(SchedulesList.iterator);
// @issue: OLD SEARCH
// $scope.search(SchedulesList.iterator);
};
Wait('start');

View File

@ -8,108 +8,62 @@
* @ngdoc function
* @name controllers.function:Teams
* @description This controller's for teams
*/
*/
export function TeamsList($scope, $rootScope, $log, $stateParams,
Rest, Alert, TeamList, Prompt, ClearScope, ProcessErrors,
GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset) {
export function TeamsList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, TeamList, GenerateList, Prompt, SearchInit, PaginateInit,
ReturnToCaller, ClearScope, ProcessErrors, SetTeamListeners, GetBasePath,
SelectionInit, Wait, $state, Refresh, $filter, rbacUiControlService) {
ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('teams')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = TeamList,
defaultUrl = GetBasePath('teams'),
generator = GenerateList,
paths = $location.path().replace(/^\//, '').split('/'),
mode = (paths[0] === 'teams') ? 'edit' : 'select',
url;
defaultUrl = GetBasePath('teams');
var injectForm = function() {
generator.inject(list, { mode: mode, scope: $scope });
};
init();
injectForm();
function init() {
$scope.canAdd = false;
$scope.$on("RefreshTeamsList", function() {
injectForm();
Refresh({
scope: $scope,
set: 'teams',
iterator: 'team',
url: GetBasePath('teams') + "?order_by=name&page_size=" + $scope.team_page_size
rbacUiControlService.canAdd('teams')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
_.forEach($scope[list.name], (team) => {
team.organization_name = team.summary_fields.organization.name;
});
});
$scope.selected = [];
url = GetBasePath('base') + $location.path() + '/';
SelectionInit({
scope: $scope,
list: list,
url: url,
returnToCaller: 1
});
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
$scope.selected = [];
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
// After a refresh, populate the organization name on each row
var i;
if ($scope.teams) {
for (i = 0; i < $scope.teams.length; i++) {
if ($scope.teams[i].summary_fields.organization) {
$scope.teams[i].organization_name = $scope.teams[i].summary_fields.organization.name;
}
}
}
});
SearchInit({
scope: $scope,
set: 'teams',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
$scope.search(list.iterator);
$scope.addTeam = function () {
$state.transitionTo('teams.add');
$scope.addTeam = function() {
$state.go('teams.add');
};
$scope.editTeam = function (id) {
$state.transitionTo('teams.edit', {team_id: id});
$scope.editTeam = function(id) {
$state.go('teams.edit', { team_id: id });
};
$scope.deleteTeam = function (id, name) {
$scope.deleteTeam = function(id, name) {
var action = function () {
var action = function() {
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function () {
.success(function() {
Wait('stop');
$('#prompt-modal').modal('hide');
if (parseInt($state.params.team_id) === id) {
$state.go("^", null, {reload: true});
$state.go('^', null, { reload: true });
} else {
$scope.search(list.iterator);
$state.go('.', null, { reload: true });
}
})
.error(function (data, status) {
.error(function(data, status) {
Wait('stop');
$('#prompt-modal').modal('hide');
ProcessErrors($scope, data, status, null, {
@ -128,18 +82,15 @@ export function TeamsList($scope, $rootScope, $location, $log, $stateParams,
};
}
TeamsList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'TeamList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'SetTeamListeners', 'GetBasePath', 'SelectionInit', 'Wait',
'$state', 'Refresh', '$filter', 'rbacUiControlService'
TeamsList.$inject = ['$scope', '$rootScope', '$log',
'$stateParams', 'Rest', 'Alert', 'TeamList', 'Prompt', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset'
];
export function TeamsAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors,
ReturnToCaller, ClearScope, GenerateList, OrganizationList, SearchInit,
PaginateInit, GetBasePath, LookUpInit, Wait, $state) {
export function TeamsAdd($scope, $rootScope, $stateParams, TeamForm, GenerateForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, Wait, $state) {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//$scope.
@ -155,176 +106,128 @@ export function TeamsAdd($scope, $rootScope, $compile, $location, $log,
// Inject dynamic view
var defaultUrl = GetBasePath('teams'),
form = TeamForm,
generator = GenerateForm,
scope = generator.inject(form, { mode: 'add', related: false });
generator = GenerateForm;
$rootScope.flashMessage = null;
generator.reset();
init();
LookUpInit({
scope: $scope,
form: form,
current_item: null,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
function init() {
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$rootScope.flashMessage = null;
}
// Save
$scope.formSave = function () {
$scope.formSave = function() {
var fld, data;
generator.clearApiErrors();
Wait('start');
Rest.setUrl(defaultUrl);
data = {};
for (fld in form.fields) {
data[fld] = scope[fld];
data[fld] = $scope[fld];
}
Rest.post(data)
.success(function (data) {
.success(function(data) {
Wait('stop');
$rootScope.flashMessage = "New team successfully created!";
$rootScope.$broadcast("EditIndicatorChange", "users", data.id);
$state.go('teams.edit', {team_id: data.id}, {reload: true});
$state.go('teams.edit', { team_id: data.id }, { reload: true });
})
.error(function (data, status) {
.error(function(data, status) {
Wait('stop');
ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to add new team. Post returned status: ' +
status });
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to add new team. Post returned status: ' +
status
});
});
};
$scope.formCancel = function () {
$state.transitionTo('teams');
$scope.formCancel = function() {
$state.go('teams');
};
}
TeamsAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'TeamForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
'OrganizationList', 'SearchInit', 'PaginateInit', 'GetBasePath',
'LookUpInit', 'Wait', '$state'
TeamsAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state'
];
export function TeamsEdit($scope, $rootScope, $location,
$stateParams, TeamForm, GenerateForm, Rest, ProcessErrors,
RelatedSearchInit, RelatedPaginateInit, ClearScope,
LookUpInit, GetBasePath, OrganizationList, Wait, $state) {
export function TeamsEdit($scope, $rootScope, $stateParams,
TeamForm, Rest, ProcessErrors, ClearScope, GetBasePath, Wait, $state) {
ClearScope();
var defaultUrl = GetBasePath('teams'),
generator = GenerateForm,
form = TeamForm,
var form = TeamForm,
id = $stateParams.team_id,
relatedSets = {},
set;
defaultUrl = GetBasePath('teams') + id;
$scope.team_id = id;
init();
$scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
function init() {
$scope.team_id = id;
Rest.setUrl(defaultUrl);
Wait('start');
Rest.get(defaultUrl).success(function(data) {
setScopeFields(data);
$scope.organization_name = data.summary_fields.organization.name;
$scope.team_obj = data;
});
$scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
}
var setScopeFields = function(data){
// @issue I think all this really want to do is _.forEach(form.fields, (field) =>{ $scope[field] = data[field]})
function setScopeFields(data) {
_(data)
.pick(function(value, key){
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key){
$scope[key] = value;
})
.value();
.pick(function(value, key) {
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key) {
$scope[key] = value;
})
.value();
return;
};
var setScopeRelated = function(data, related){
_(related)
.pick(function(value, key){
return data.related.hasOwnProperty(key) === true;
})
.forEach(function(value, key){
relatedSets[key] = {
url: data.related[key],
iterator: value.iterator
};
})
.value();
};
}
// prepares a data payload for a PUT request to the API
var processNewData = function(fields){
function processNewData(fields) {
var data = {};
_.forEach(fields, function(value, key){
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined){
data[key] = $scope[key];
_.forEach(fields, function(value, key) {
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) {
data[key] = $scope[key];
}
});
return data;
}
$scope.formCancel = function() {
$state.go('teams', null, { reload: true });
};
var init = function(){
var url = defaultUrl + id;
Rest.setUrl(url);
Wait('start');
Rest.get(url).success(function(data){
setScopeFields(data);
setScopeRelated(data, form.related);
$scope.organization_name = data.summary_fields.organization.name;
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
$scope.team_obj = data;
LookUpInit({
url: GetBasePath('organizations'),
scope: $scope,
form: form,
current_item: $scope.organization,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
});
};
$scope.formCancel = function(){
$state.go('teams', null, {reload: true});
};
$scope.formSave = function(){
generator.clearApiErrors();
generator.checkAutoFill();
$scope.formSave = function() {
$rootScope.flashMessage = null;
if ($scope[form.name + '_form'].$valid){
if ($scope[form.name + '_form'].$valid) {
Rest.setUrl(defaultUrl + id + '/');
var data = processNewData(form.fields);
Rest.put(data).success(function(){
$state.go($state.current, null, {reload: true});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status });
});
Rest.put(data).success(function() {
$state.go($state.current, null, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status
});
});
}
};
@ -337,13 +240,8 @@ export function TeamsEdit($scope, $rootScope, $location,
return null;
}
};
/* Related Set implementation TDB */
}
TeamsEdit.$inject = ['$scope', '$rootScope', '$location',
'$stateParams', 'TeamForm', 'GenerateForm', 'Rest',
'ProcessErrors', 'RelatedSearchInit', 'RelatedPaginateInit',
'ClearScope', 'LookUpInit', 'GetBasePath',
'OrganizationList', 'Wait', '$state'
TeamsEdit.$inject = ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'Wait', '$state'
];

View File

@ -8,14 +8,14 @@
* @ngdoc function
* @name controllers.function:Users
* @description This controller's the Users page
*/
*/
import {N_} from "../i18n";
import { N_ } from "../i18n";
const user_type_options = [
{type: 'normal' , label: N_('Normal User') },
{type: 'system_auditor' , label: N_('System Auditor') },
{type: 'system_administrator', label: N_('System Administrator') },
{ type: 'normal', label: N_('Normal User') },
{ type: 'system_auditor', label: N_('System Auditor') },
{ type: 'system_administrator', label: N_('System Administrator') },
];
function user_type_sync($scope) {
@ -25,18 +25,17 @@ function user_type_sync($scope) {
switch (type_option.type) {
case 'system_administrator':
$scope.is_superuser = true;
break;
break;
case 'system_auditor':
$scope.is_system_auditor = true;
break;
break;
}
};
}
export function UsersList($scope, $rootScope, $location, $log, $stateParams,
Rest, Alert, UserList, GenerateList, Prompt, SearchInit, PaginateInit,
ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, SelectionInit,
Wait, $state, Refresh, $filter, rbacUiControlService, i18n) {
export function UsersList($scope, $rootScope, $stateParams,
Rest, Alert, UserList, Prompt, ClearScope, ProcessErrors, GetBasePath,
Wait, $state, $filter, rbacUiControlService, Dataset, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
@ -44,95 +43,57 @@ export function UsersList($scope, $rootScope, $location, $log, $stateParams,
ClearScope();
$scope.canAdd = false;
rbacUiControlService.canAdd('users')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
var list = UserList,
defaultUrl = GetBasePath('users'),
generator = GenerateList,
base = $location.path().replace(/^\//, '').split('/')[0],
mode = (base === 'users') ? 'edit' : 'select',
url = (base === 'organizations') ? GetBasePath('organizations') + $stateParams.organization_id + '/users/' :
GetBasePath('teams') + $stateParams.team_id + '/users/';
defaultUrl = GetBasePath('users');
var injectForm = function() {
generator.inject(UserList, { mode: mode, scope: $scope });
};
init();
injectForm();
function init() {
$scope.canAdd = false;
$scope.$on("RefreshUsersList", function() {
injectForm();
Refresh({
scope: $scope,
set: 'users',
iterator: 'user',
url: GetBasePath('users') + "?order_by=username&page_size=" + $scope.user_page_size
});
});
rbacUiControlService.canAdd('users')
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
$scope.selected = [];
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
if (mode === 'select') {
SelectionInit({ scope: $scope, list: list, url: url, returnToCaller: 1 });
$rootScope.flashMessage = null;
$scope.selected = [];
}
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
// Cleanup after a delete
Wait('stop');
$('#prompt-modal').modal('hide');
});
$rootScope.flashMessage = null;
SearchInit({
scope: $scope,
set: 'users',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
$scope.search(list.iterator);
$scope.addUser = function () {
$state.transitionTo('users.add');
$scope.addUser = function() {
$state.go('users.add');
};
$scope.editUser = function (id) {
$state.transitionTo('users.edit', {user_id: id});
$scope.editUser = function(id) {
$state.go('users.edit', { user_id: id });
};
$scope.deleteUser = function (id, name) {
$scope.deleteUser = function(id, name) {
var action = function () {
//$('#prompt-modal').on('hidden.bs.modal', function () {
// Wait('start');
//});
var action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
var url = defaultUrl + id + '/';
Rest.setUrl(url);
Rest.destroy()
.success(function () {
.success(function() {
if (parseInt($state.params.user_id) === id) {
$state.go("^", null, {reload: true});
$state.go('^', null, { reload: true });
} else {
$scope.search(list.iterator);
$state.go('.', null, { reload: true });
}
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status
});
});
};
@ -145,91 +106,54 @@ export function UsersList($scope, $rootScope, $location, $log, $stateParams,
};
}
UsersList.$inject = ['$scope', '$rootScope', '$location', '$log',
'$stateParams', 'Rest', 'Alert', 'UserList', 'generateList', 'Prompt',
'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'SelectionInit', 'Wait', '$state',
'Refresh', '$filter', 'rbacUiControlService', 'i18n'
UsersList.$inject = ['$scope', '$rootScope', '$stateParams',
'Rest', 'Alert', 'UserList', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath',
'Wait', '$state', '$filter', 'rbacUiControlService', 'Dataset', 'i18n'
];
export function UsersAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, UserForm, GenerateForm, Rest, Alert, ProcessErrors,
ReturnToCaller, ClearScope, GetBasePath, LookUpInit, OrganizationList,
ResetForm, Wait, CreateSelect2, $state, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
}
Rest.setUrl(GetBasePath('users'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a user.', 'alert-info');
}
});
export function UsersAdd($scope, $rootScope, $stateParams, UserForm,
GenerateForm, Rest, Alert, ProcessErrors, ReturnToCaller, ClearScope,
GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) {
ClearScope();
// Inject dynamic view
var defaultUrl = GetBasePath('organizations'),
form = UserForm,
generator = GenerateForm;
form = UserForm;
generator.inject(form, { mode: 'add', related: false, scope: $scope });
ResetForm();
init();
$scope.ldap_user = false;
$scope.not_ldap_user = !$scope.ldap_user;
$scope.ldap_dn = null;
$scope.socialAuthUser = false;
$scope.external_account = null;
function init() {
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
generator.reset();
$scope.ldap_user = false;
$scope.not_ldap_user = !$scope.ldap_user;
$scope.ldap_dn = null;
$scope.socialAuthUser = false;
$scope.external_account = null;
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
CreateSelect2({
element: '#user_user_type',
multiple: false
});
// Configure the lookup dialog. If we're adding a user through the Organizations tab,
// default the Organization value.
LookUpInit({
scope: $scope,
form: form,
current_item: ($stateParams.organization_id !== undefined) ? $stateParams.organization_id : null,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
if ($stateParams.organization_id) {
$scope.organization = $stateParams.organization_id;
Rest.setUrl(GetBasePath('organizations') + $stateParams.organization_id + '/');
Rest.get()
.success(function (data) {
$scope.organization_name = data.name;
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to lookup Organization: ' + data.id + '. GET returned status: ' + status });
Rest.setUrl(GetBasePath('users'));
Rest.options()
.success(function(data) {
if (!data.actions.POST) {
$state.go("^");
Alert('Permission Error', 'You do not have permission to add a user.', 'alert-info');
}
});
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
CreateSelect2({
element: '#user_user_type',
multiple: false
});
}
// Save
$scope.formSave = function () {
$scope.formSave = function() {
var fld, data = {};
generator.clearApiErrors();
generator.checkAutoFill();
if ($scope[form.name + '_form'].$valid) {
if ($scope.organization !== undefined && $scope.organization !== null && $scope.organization !== '') {
Rest.setUrl(defaultUrl + $scope.organization + '/users/');
@ -244,18 +168,17 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log,
data.is_system_auditor = $scope.is_system_auditor;
Wait('start');
Rest.post(data)
.success(function (data) {
.success(function(data) {
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'users') {
$rootScope.flashMessage = 'New user successfully created!';
$rootScope.$broadcast("EditIndicatorChange", "users", data.id);
$state.go('users.edit', {user_id: data.id}, {reload: true});
}
else {
$state.go('users.edit', { user_id: data.id }, { reload: true });
} else {
ReturnToCaller(1);
}
})
.error(function (data, status) {
.error(function(data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to add new user. POST returned status: ' + status });
});
} else {
@ -264,69 +187,102 @@ export function UsersAdd($scope, $rootScope, $compile, $location, $log,
}
};
$scope.formCancel = function () {
$state.transitionTo('users');
$scope.formCancel = function() {
$state.go('users');
};
// Password change
$scope.clearPWConfirm = function (fld) {
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
};
}
UsersAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log',
'$stateParams', 'UserForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath',
'LookUpInit', 'OrganizationList', 'ResetForm', 'Wait', 'CreateSelect2', '$state',
'i18n'
UsersAdd.$inject = ['$scope', '$rootScope', '$stateParams', 'UserForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n'
];
export function UsersEdit($scope, $rootScope, $location,
$stateParams, UserForm, GenerateForm, Rest, ProcessErrors,
RelatedSearchInit, RelatedPaginateInit, ClearScope,
GetBasePath, ResetForm, Wait, CreateSelect2 ,$state, i18n) {
$stateParams, UserForm, Rest, ProcessErrors,
ClearScope, GetBasePath, ResetForm, Wait, CreateSelect2, $state, i18n) {
for (var i = 0; i < user_type_options.length; i++) {
user_type_options[i].label = i18n._(user_type_options[i].label);
}
ClearScope();
var defaultUrl = GetBasePath('users'),
generator = GenerateForm,
form = UserForm,
var form = UserForm,
master = {},
id = $stateParams.user_id,
relatedSets = {},
set;
defaultUrl = GetBasePath('users') + id;
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
init();
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
function init() {
$scope.user_type_options = user_type_options;
$scope.user_type = user_type_options[0];
$scope.$watch('user_type', user_type_sync($scope));
Rest.setUrl(defaultUrl);
Wait('start');
Rest.get(defaultUrl).success(function(data) {
$scope.user_id = id;
$scope.ldap_user = (data.ldap_dn !== null && data.ldap_dn !== undefined && data.ldap_dn !== '') ? true : false;
$scope.not_ldap_user = !$scope.ldap_user;
master.ldap_user = $scope.ldap_user;
$scope.socialAuthUser = (data.auth.length > 0) ? true : false;
$scope.external_account = data.external_account;
$scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
$scope.user_type = $scope.user_type_options[0];
$scope.is_system_auditor = false;
$scope.is_superuser = false;
if (data.is_system_auditor) {
$scope.user_type = $scope.user_type_options[1];
$scope.is_system_auditor = true;
}
if (data.is_superuser) {
$scope.user_type = $scope.user_type_options[2];
$scope.is_superuser = true;
}
var setScopeFields = function(data){
$scope.user_obj = data;
CreateSelect2({
element: '#user_user_type',
multiple: false
});
$scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
setScopeFields(data);
Wait('stop');
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status
});
});
}
function setScopeFields(data) {
_(data)
.pick(function(value, key){
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key){
$scope[key] = value;
})
.value();
.pick(function(value, key) {
return form.fields.hasOwnProperty(key) === true;
})
.forEach(function(value, key) {
$scope[key] = value;
})
.value();
return;
};
}
$scope.convertApiUrl = function(str) {
if (str) {
@ -336,25 +292,12 @@ export function UsersEdit($scope, $rootScope, $location,
}
};
var setScopeRelated = function(data, related){
_(related)
.pick(function(value, key){
return data.related.hasOwnProperty(key) === true;
})
.forEach(function(value, key){
relatedSets[key] = {
url: data.related[key],
iterator: value.iterator
};
})
.value();
};
// prepares a data payload for a PUT request to the API
var processNewData = function(fields){
var processNewData = function(fields) {
var data = {};
_.forEach(fields, function(value, key){
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined){
data[key] = $scope[key];
_.forEach(fields, function(value, key) {
if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) {
data[key] = $scope[key];
}
});
data.is_superuser = $scope.is_superuser;
@ -362,98 +305,37 @@ export function UsersEdit($scope, $rootScope, $location,
return data;
};
var init = function(){
var url = defaultUrl + id;
Rest.setUrl(url);
Wait('start');
Rest.get(url).success(function(data){
$scope.user_id = id;
$scope.ldap_user = (data.ldap_dn !== null && data.ldap_dn !== undefined && data.ldap_dn !== '') ? true : false;
$scope.not_ldap_user = !$scope.ldap_user;
master.ldap_user = $scope.ldap_user;
$scope.socialAuthUser = (data.auth.length > 0) ? true : false;
$scope.external_account = data.external_account;
$scope.user_type = $scope.user_type_options[0];
$scope.is_system_auditor = false;
$scope.is_superuser = false;
if (data.is_system_auditor) {
$scope.user_type = $scope.user_type_options[1];
$scope.is_system_auditor = true;
}
if (data.is_superuser) {
$scope.user_type = $scope.user_type_options[2];
$scope.is_superuser = true;
}
$scope.user_obj = data;
CreateSelect2({
element: '#user_user_type',
multiple: false
});
setScopeFields(data);
setScopeRelated(data, form.related);
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
Wait('stop');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status });
});
$scope.formCancel = function() {
$state.go('users', null, { reload: true });
};
$scope.formCancel = function(){
$state.go('users', null, {reload: true});
};
$scope.formSave = function(){
generator.clearApiErrors();
generator.checkAutoFill();
$scope.formSave = function() {
$rootScope.flashMessage = null;
if ($scope[form.name + '_form'].$valid){
if ($scope[form.name + '_form'].$valid) {
Rest.setUrl(defaultUrl + id + '/');
var data = processNewData(form.fields);
Rest.put(data).success(function(){
$state.go($state.current, null, {reload: true});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status });
});
Rest.put(data).success(function() {
$state.go($state.current, null, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve user: ' +
$stateParams.id + '. GET status: ' + status
});
});
}
};
$scope.clearPWConfirm = function (fld) {
$scope.clearPWConfirm = function(fld) {
// If password value changes, make sure password_confirm must be re-entered
$scope[fld] = '';
$scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
$rootScope.flashMessage = null;
};
init();
/* Related Set implementation TDB */
}
UsersEdit.$inject = ['$scope', '$rootScope', '$location',
'$stateParams', 'UserForm', 'GenerateForm', 'Rest', 'ProcessErrors',
'RelatedSearchInit', 'RelatedPaginateInit', 'ClearScope', 'GetBasePath',
'$stateParams', 'UserForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath',
'ResetForm', 'Wait', 'CreateSelect2', '$state', 'i18n'
];

View File

@ -39,7 +39,7 @@ export default
label: i18n._("Hosts")
},
{
url: "/#/home/hosts?active-failures=true",
url: "/#/home/hosts?host_search=has_active_failures:true",
number: scope.data.hosts.failed,
label: i18n._("Failed Hosts"),
isFailureCount: true
@ -50,7 +50,7 @@ export default
label: i18n._("Inventories"),
},
{
url: "/#/inventories?status=sync-failed",
url: "/#/inventories?inventory_search=inventory_sources_with_failures__gt:0",
number: scope.data.inventories.inventory_failed,
label: i18n._("Inventory Sync Failures"),
isFailureCount: true
@ -61,7 +61,7 @@ export default
label: i18n._("Projects")
},
{
url: "/#/projects?status=failed,canceled",
url: "/#/projects?project_search=status__in:failed,canceled",
number: scope.data.projects.failed,
label: i18n._("Project Sync Failures"),
isFailureCount: true

View File

@ -1,4 +0,0 @@
<div class="tab-pane" id="organizations">
<div ui-view></div>
<div ng-cloak id="htmlTemplate" class="Panel"></div>
</div>

View File

@ -4,86 +4,52 @@
* All Rights Reserved
*************************************************/
export default
['$scope', '$state', '$stateParams', 'PageRangeSetup', 'GetBasePath', 'DashboardHostsList',
'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts', '$rootScope', 'SearchInit',
function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts, $rootScope, SearchInit){
var setJobStatus = function(){
_.forEach($scope.hosts, function(value){
SetStatus({
scope: $scope,
host: value
});
});
};
var generator = GenerateList,
list = DashboardHostsList,
defaultUrl = GetBasePath('hosts');
$scope.hostPageSize = 10;
$scope.editHost = function(id){
$state.go('dashboardHosts.edit', {id: id});
};
$scope.toggleHostEnabled = function(host){
DashboardHostService.setHostStatus(host, !host.enabled)
.then(function(res){
var index = _.findIndex($scope.hosts, function(o) {return o.id === res.data.id;});
$scope.hosts[index].enabled = res.data.enabled;
});
};
$scope.$on('PostRefresh', function(){
$scope.hosts = _.map($scope.hosts, function(value){
value.inventory_name = value.summary_fields.inventory.name;
value.inventory_id = value.summary_fields.inventory.id;
return value;
});
setJobStatus();
});
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
if (toState.name === "dashboardHosts.edit") {
$scope.rowBeingEdited = toParams.id;
$scope.listBeingEdited = "hosts";
}
else {
delete $scope.rowBeingEdited;
delete $scope.listBeingEdited;
}
});
// Remove the listener when the scope is destroyed to avoid a memory leak
$scope.$on('$destroy', function() {
cleanUpStateChangeListener();
});
var init = function(){
$scope.list = list;
$scope.host_active_search = false;
$scope.host_total_rows = hosts.results.length;
$scope.hosts = hosts.results;
setJobStatus();
generator.inject(list, {mode: 'edit', scope: $scope});
SearchInit({
scope: $scope,
set: 'hosts',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl,
pageSize: 10
});
PageRangeSetup({
scope: $scope,
count: hosts.count,
next: hosts.next,
previous: hosts.previous,
iterator: list.iterator
export default ['$scope', '$state', '$stateParams', 'GetBasePath', 'DashboardHostsList',
'generateList', 'SetStatus', 'DashboardHostService', '$rootScope', 'Dataset',
function($scope, $state, $stateParams, GetBasePath, DashboardHostsList,
GenerateList, SetStatus, DashboardHostService, $rootScope, Dataset) {
let list = DashboardHostsList;
init();
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$scope.$watchCollection(list.name, function() {
$scope[list.name] = _.map($scope.hosts, function(value) {
value.inventory_name = value.summary_fields.inventory.name;
value.inventory_id = value.summary_fields.inventory.id;
return value;
});
setJobStatus();
});
$scope.hostLoading = false;
if($state.current.name === "dashboardHosts.edit") {
$scope.rowBeingEdited = $state.params.id;
$scope.listBeingEdited = "hosts";
}
$scope.search(list.iterator);
};
init();
}];
}
function setJobStatus(){
_.forEach($scope.hosts, function(value) {
SetStatus({
scope: $scope,
host: value
});
});
}
$scope.editHost = function(id) {
$state.go('dashboardHosts.edit', { id: id });
};
$scope.toggleHostEnabled = function(host) {
DashboardHostService.setHostStatus(host, !host.enabled)
.then(function(res) {
var index = _.findIndex($scope.hosts, function(o) {
return o.id === res.data.id;
});
$scope.hosts[index].enabled = res.data.enabled;
});
};
}
];

View File

@ -1,4 +0,0 @@
<div class="tab-pane" id="HomeHosts">
<div ui-view></div>
<div ng-cloak id="htmlTemplate" class="Panel"></div>
</div>

View File

@ -18,7 +18,7 @@ export default function(){
class: 'Form-header-field',
ngClick: 'toggleHostEnabled()',
type: 'toggle',
editRequired: false,
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
dataTitle: 'Host Enabled'
@ -28,7 +28,7 @@ export default function(){
name: {
label: 'Host Name',
type: 'text',
editRequired: true,
value: '{{name}}',
awPopOver: "<p>Provide a host name, ip address, or ip address:port. Examples include:</p>" +
"<blockquote>myserver.domain.com<br/>" +
@ -43,12 +43,10 @@ export default function(){
description: {
label: 'Description',
type: 'text',
editRequired: false
},
variables: {
label: 'Variables',
type: 'textarea',
editRequired: false,
rows: 6,
class: 'modal-input-xlarge Form-textArea Form-formGroup--fullWidth',
dataTitle: 'Host Variables',

View File

@ -21,11 +21,7 @@ export default [ 'i18n', function(i18n){
basePath: 'unified_jobs',
label: '',
iconOnly: true,
searchable: false,
searchType: 'select',
nosort: true,
searchOptions: [],
searchLabel: 'Job Status',
icon: 'icon-job-{{ host.active_failures }}',
awToolTip: '{{ host.badgeToolTip }}',
awTipPlacement: 'right',
@ -54,24 +50,9 @@ export default [ 'i18n', function(i18n){
columnClass: 'List-staticColumn--toggle',
type: 'toggle',
ngClick: 'toggleHostEnabled(host)',
searchable: false,
nosort: true,
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
dataTitle: 'Host Enabled',
},
has_active_failures: {
label: 'Has failed jobs?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
},
has_inventory_sources: {
label: 'Has external source?',
searchSingleValue: true,
searchType: 'boolean',
searchValue: 'true',
searchOnly: true
}
},

View File

@ -1,61 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
import listController from './dashboard-hosts-list.controller';
import editController from './dashboard-hosts-edit.controller';
var dashboardHostsList = {
name: 'dashboardHosts',
url: '/home/hosts?:active-failures',
controller: listController,
templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-list'),
data: {
activityStream: true,
activityStreamTarget: 'host'
},
ncyBreadcrumb: {
parent: 'dashboard',
label: "HOSTS"
},
resolve: {
hosts: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams){
var defaultUrl = GetBasePath('hosts') + '?page_size=10' + ($stateParams['active-failures'] ? '&has_active_failures=true' : '' );
Rest.setUrl(defaultUrl);
return Rest.get().then(function(res){
var results = _.map(res.data.results, function(value){
value.inventory_name = value.summary_fields.inventory.name;
value.inventory_id = value.summary_fields.inventory.id;
return value;
});
res.data.results = results;
return res.data;
});
}]
}
};
var dashboardHostsEdit = {
name: 'dashboardHosts.edit',
url: '/:id',
controller: editController,
templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-edit'),
ncyBreadcrumb: {
parent: 'dashboardHosts',
label: "{{host.name}}"
},
resolve: {
host: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){
var defaultUrl = GetBasePath('hosts') + '?id=' + $stateParams.id;
Rest.setUrl(defaultUrl);
return Rest.get().then(function(res){
return res.data.results[0];
});
}]
}
};
export {dashboardHostsList, dashboardHostsEdit};

View File

@ -4,17 +4,43 @@
* All Rights Reserved
*************************************************/
import {dashboardHostsList, dashboardHostsEdit} from './dashboard-hosts.route';
import list from './dashboard-hosts.list';
import form from './dashboard-hosts.form';
import listController from './dashboard-hosts-list.controller';
import editController from './dashboard-hosts-edit.controller';
import service from './dashboard-hosts.service';
export default
angular.module('dashboardHosts', [])
angular.module('dashboardHosts', [])
.service('DashboardHostService', service)
.factory('DashboardHostsList', list)
.factory('DashboardHostsForm', form)
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(dashboardHostsList);
$stateExtender.addState(dashboardHostsEdit);
}]);
.config(['$stateProvider', 'stateDefinitionsProvider',
function($stateProvider, stateDefinitionsProvider) {
let stateDefinitions = stateDefinitionsProvider.$get();
$stateProvider.state({
name: 'dashboardHosts',
url: '/home/hosts',
lazyLoad: () => stateDefinitions.generateTree({
url: '/home/hosts',
parent: 'dashboardHosts',
modes: ['edit'],
list: 'DashboardHostsList',
form: 'DashboardHostsForm',
controllers: {
list: listController,
edit: editController
},
data: {
activityStream: true,
activityStreamTarget: 'host'
},
ncyBreadcrumb: {
parent: 'dashboard',
label: "HOSTS"
},
})
});
}
]);

View File

@ -18,6 +18,8 @@ export default
addTitle: i18n._('Create Credential'), //Legend in add mode
editTitle: '{{ name }}', //Legend in edit mode
name: 'credential',
// the top-most node of generated state tree
stateTree: 'credentials',
forceListeners: true,
subFormTitles: {
credentialSubForm: i18n._('Type Details'),
@ -31,24 +33,22 @@ export default
name: {
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
required: true,
autocomplete: false,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
description: {
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
organization: {
addRequired: false,
editRequired: false,
// interpolated with $rootScope
basePath: "{{$rootScope.current_user.is_superuser ? 'api/v1/organizations' : $rootScope.current_user.url + 'admin_of_organizations'}}",
ngShow: 'canShareCredential',
label: i18n._('Organization'),
type: 'lookup',
list: 'OrganizationList',
sourceModel: 'organization',
sourceField: 'name',
ngClick: 'lookUpOrganization()',
@ -56,7 +56,7 @@ export default
dataTitle: i18n._('Organization') + ' ',
dataPlacement: 'bottom',
dataContainer: "body",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
kind: {
label: i18n._('Type'),
@ -64,8 +64,7 @@ export default
type: 'select',
ngOptions: 'kind.label for kind in credential_kind_options track by kind.value', // select as label for value in array 'kind.label for kind in credential_kind_options',
ngChange: 'kindChange()',
addRequired: true,
editRequired: true,
required: true,
awPopOver: i18n._('<dl>\n' +
'<dt>Machine</dt>\n' +
'<dd>Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' +
@ -88,7 +87,7 @@ export default
dataPlacement: 'right',
dataContainer: "body",
hasSubForm: true,
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
access_key: {
label: i18n._('Access Key'),
@ -101,7 +100,7 @@ export default
autocomplete: false,
apiField: 'username',
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
secret_key: {
label: i18n._('Secret Key'),
@ -130,7 +129,7 @@ export default
dataPlacement: 'right',
dataContainer: "body",
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"host": {
labelBind: 'hostLabel',
@ -147,7 +146,7 @@ export default
init: false
},
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"subscription": {
label: i18n._("Subscription ID"),
@ -157,15 +156,15 @@ export default
reqExpression: 'subscription_required',
init: false
},
addRequired: false,
editRequired: false,
autocomplete: false,
awPopOver: i18n._('<p>Subscription ID is an Azure construct, which is mapped to a username.</p>'),
dataTitle: i18n._('Subscription ID'),
dataPlacement: 'right',
dataContainer: "body",
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"username": {
labelBind: 'usernameLabel',
@ -178,7 +177,7 @@ export default
},
autocomplete: false,
subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"email_address": {
labelBind: 'usernameLabel',
@ -194,7 +193,7 @@ export default
dataPlacement: 'right',
dataContainer: "body",
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"api_key": {
label: i18n._('API Key'),
@ -208,7 +207,7 @@ export default
hasShowInputButton: true,
clear: false,
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"password": {
labelBind: 'passwordLabel',
@ -222,15 +221,13 @@ export default
init: false
},
subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"ssh_password": {
label: i18n._('Password'),
type: 'sensitive',
ngShow: "kind.value == 'ssh'",
ngDisabled: "ssh_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false,
editRequired: false,
subCheckbox: {
variable: 'ssh_password_ask',
text: i18n._('Ask at runtime?'),
@ -251,8 +248,8 @@ export default
},
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
elementClass: 'Form-monospace',
addRequired: false,
editRequired: false,
awDropFile: true,
rows: 10,
awPopOver: i18n._("SSH key description"),
@ -261,14 +258,12 @@ export default
dataPlacement: 'right',
dataContainer: "body",
subForm: "credentialSubForm",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"ssh_key_unlock": {
label: i18n._('Private Key Passphrase'),
type: 'sensitive',
ngShow: "kind.value == 'ssh' || kind.value == 'scm'",
addRequired: false,
editRequired: false,
ngDisabled: "keyEntered === false || ssh_key_unlock_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
subCheckbox: {
variable: 'ssh_key_unlock_ask',
@ -293,25 +288,23 @@ export default
dataPlacement: 'right',
dataContainer: "body",
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"become_username": {
labelBind: 'becomeUsernameLabel',
type: 'text',
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
addRequired: false,
editRequired: false,
autocomplete: false,
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"become_password": {
labelBind: 'becomePasswordLabel',
type: 'sensitive',
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
ngDisabled: "become_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false,
editRequired: false,
subCheckbox: {
variable: 'become_password_ask',
text: i18n._('Ask at runtime?'),
@ -326,7 +319,7 @@ export default
label: i18n._('Client ID'),
subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
secret:{
type: 'sensitive',
@ -335,14 +328,14 @@ export default
label: i18n._('Client Secret'),
subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
tenant: {
type: 'text',
label: i18n._('Tenant ID'),
subForm: 'credentialSubForm',
ngShow: "kind.value === 'azure_rm'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
authorize: {
label: i18n._('Authorize'),
@ -350,7 +343,7 @@ export default
ngChange: "toggleCallback('host_config_key')",
subForm: 'credentialSubForm',
ngShow: "kind.value === 'net'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
authorize_password: {
label: i18n._('Authorize Password'),
@ -359,7 +352,7 @@ export default
autocomplete: false,
subForm: 'credentialSubForm',
ngShow: "authorize && authorize !== 'false'",
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"project": {
labelBind: 'projectLabel',
@ -370,14 +363,12 @@ export default
dataTitle: i18n._('Project Name'),
dataPlacement: 'right',
dataContainer: "body",
addRequired: false,
editRequired: false,
awRequiredWhen: {
reqExpression: 'project_required',
init: false
},
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
"domain": {
labelBind: 'domainLabel',
@ -391,18 +382,14 @@ export default
dataTitle: i18n._('Domain Name'),
dataPlacement: 'right',
dataContainer: "body",
addRequired: false,
editRequired: false,
subForm: 'credentialSubForm',
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)',
subForm: 'credentialSubForm'
},
"vault_password": {
label: i18n._("Vault Password"),
type: 'sensitive',
ngShow: "kind.value == 'ssh'",
ngDisabled: "vault_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)",
addRequired: false,
editRequired: false,
subCheckbox: {
variable: 'vault_password_ask',
text: i18n._('Ask at runtime?'),
@ -417,17 +404,17 @@ export default
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
label: 'Save',
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: true,
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' //Disable when $pristine or $invalid, optional
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' //Disable when $pristine or $invalid, optional
}
},
@ -437,24 +424,25 @@ export default
awToolTip: '{{permissionsTooltip}}',
dataTipWatch: 'permissionsTooltip',
dataPlacement: 'top',
basePath: 'credentials/:id/access_list/',
basePath: 'api/v1/credentials/{{$stateParams.credential_id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: i18n._('Permissions'),
iterator: 'permission',
index: false,
open: false,
searchType: 'select',
actions: {
add: {
ngClick: "addPermission",
ngClick: "$state.go('.add')",
label: 'Add',
awToolTip: i18n._('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: i18n._('&#43; ADD'),
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
fields: {
username: {
key: true,

View File

@ -18,31 +18,31 @@ export default
editTitle: '{{ name }}',
showTitle: true,
name: 'group',
basePath: 'groups',
// the parent node this generated state definition tree expects to attach to
stateTree: 'inventoryManage',
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
activeEditState: 'inventoryManage.editGroup',
well: false,
fields: {
name: {
label: 'Name',
type: 'text',
addRequired: true,
editRequired: true,
tab: 'properties',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
tab: 'properties'
},
description: {
label: 'Description',
type: 'text',
addRequired: false,
editRequired: false,
tab: 'properties',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)',
tab: 'properties'
},
variables: {
label: 'Variables',
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
addRequired: false,
editRequird: false,
rows: 12,
'default': '---',
dataTitle: 'Group Variables',
@ -65,23 +65,23 @@ export default
type: 'select',
ngOptions: 'source.label for source in source_type_options track by source.value',
ngChange: 'sourceChange(source)',
addRequired: false,
editRequired: false,
ngModel: 'source',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)',
ngModel: 'source'
},
credential: {
label: 'Cloud Credential',
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
ngShow: "source && source.value !== '' && source.value !== 'custom'",
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
ngClick: 'lookupCredential()',
awRequiredWhen: {
reqExpression: "cloudCredentialRequired",
init: "false"
},
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
source_regions: {
label: 'Regions',
@ -89,22 +89,20 @@ export default
ngOptions: 'source.label for source in source_region_choices track by source.value',
multiSelect: true,
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')",
addRequired: false,
editRequired: false,
dataTitle: 'Source Regions',
dataPlacement: 'right',
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
"</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
instance_filters: {
label: 'Instance Filters',
type: 'text',
ngShow: "source && source.value == 'ec2'",
addRequired: false,
editRequired: false,
dataTitle: 'Instance Filters',
dataPlacement: 'right',
awPopOver: "<p>Provide a comma-separated list of filter expressions. " +
@ -118,15 +116,13 @@ export default
"<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " +
"for a complete list of supported filters.</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
group_by: {
label: 'Only Group By',
type: 'select',
ngShow: "source && source.value == 'ec2'",
ngOptions: 'source.label for source in group_by_choices track by source.value',
addRequired: false,
editRequired: false,
multiSelect: true,
dataTitle: 'Only Group By',
dataPlacement: 'right',
@ -144,19 +140,19 @@ export default
"<li>Tag None: <strong>tags &raquo; tag_none</strong></li>" +
"</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>",
dataContainer: 'body',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
inventory_script: {
label : "Custom Inventory Script",
type: 'lookup',
basePath: 'inventory_scripts',
list: 'InventoryScriptList',
ngShow: "source && source.value === 'custom'",
sourceModel: 'inventory_script',
sourceField: 'name',
ngClick: 'lookUpInventory_script()' ,
addRequired: true,
editRequired: true,
ngRequired: "source && source.value === 'custom'",
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)',
},
custom_variables: {
id: 'custom_variables',
@ -164,8 +160,6 @@ export default
ngShow: "source && source.value=='custom' ",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
addRequired: false,
editRequired: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
@ -187,8 +181,6 @@ export default
ngShow: "source && source.value == 'ec2'",
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
addRequired: false,
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
@ -209,12 +201,9 @@ export default
vmware_variables: {
id: 'vmware_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'vmware'",
type: 'textarea',
addRequired: false,
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
@ -235,12 +224,9 @@ export default
openstack_variables: {
id: 'openstack_variables',
label: 'Source Variables', //"{{vars_label}}" ,
ngShow: "source && source.value == 'openstack'",
type: 'textarea',
addRequired: false,
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
editRequird: false,
rows: 6,
'default': '---',
parseTypeName: 'envParseType',
@ -263,14 +249,13 @@ export default
type: 'checkbox_group',
ngShow: "source && (source.value !== '' && source.value !== null)",
class: 'Form-checkbox--stacked',
fields: [{
name: 'overwrite',
label: 'Overwrite',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>If checked, all child groups and hosts not found on the external source will be deleted from ' +
'the local inventory.</p><p>When not checked, local child hosts and groups not found on the external source will ' +
'remain untouched by the inventory update process.</p>',
@ -278,14 +263,14 @@ export default
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
}, {
name: 'overwrite_vars',
label: 'Overwrite Variables',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>If checked, all variables for child groups and hosts will be removed and replaced by those ' +
'found on the external source.</p><p>When not checked, a merge will be performed, combining local variables with ' +
'those found on the external source.</p>',
@ -293,21 +278,19 @@ export default
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
}, {
name: 'update_on_launch',
label: 'Update on Launch',
type: 'checkbox',
ngShow: "source.value !== '' && source.value !== null",
addRequired: false,
editRequired: false,
awPopOver: '<p>Each time a job runs using this inventory, refresh the inventory from the selected source before ' +
'executing job tasks.</p>',
dataTitle: 'Update on Launch',
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options',
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
}]
},
update_cache_timeout: {
@ -319,8 +302,6 @@ export default
ngShow: "source && source.value !== '' && update_on_launch",
spinner: true,
"default": 0,
addRequired: false,
editRequired: false,
awPopOver: '<p>Time in seconds to consider an inventory sync to be current. During job runs and callbacks the task system will ' +
'evaluate the timestamp of the latest sync. If it is older than Cache Timeout, it is not considered current, ' +
'and a new inventory sync will be performed.</p>',
@ -333,16 +314,16 @@ export default
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(group_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},

View File

@ -26,8 +26,7 @@ export default
type: 'select',
multiple: true,
ngOptions: 'group.name for group in inventory_groups track by group.value',
addRequired: true,
editRequired: true,
required: true,
awPopOver: "<p>Provide a host name, ip address, or ip address:port. Examples include:</p>" +
"<blockquote>myserver.domain.com<br/>" +
"127.0.0.1<br />" +

View File

@ -17,6 +17,7 @@ export default
addTitle: 'Create Host',
editTitle: '{{ host.name }}',
name: 'host',
basePath: 'hosts',
well: false,
formLabelSize: 'col-lg-3',
formFieldSize: 'col-lg-9',
@ -26,7 +27,6 @@ export default
class: 'Form-header-field',
ngClick: 'toggleHostEnabled(host)',
type: 'toggle',
editRequired: false,
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
dataTitle: 'Host Enabled',
@ -36,8 +36,7 @@ export default
name: {
label: 'Host Name',
type: 'text',
addRequired: true,
editRequired: true,
required: true,
awPopOver: "<p>Provide a host name, ip address, or ip address:port. Examples include:</p>" +
"<blockquote>myserver.domain.com<br/>" +
"127.0.0.1<br />" +
@ -47,22 +46,18 @@ export default
dataTitle: 'Host Name',
dataPlacement: 'right',
dataContainer: 'body',
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(host.summary_fields.user_capabilities.edit || !canAdd)'
},
description: {
label: 'Description',
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(host.summary_fields.user_capabilities.edit || !canAdd)',
type: 'text'
},
variables: {
label: 'Variables',
type: 'textarea',
addRequired: false,
editRequird: false,
rows: 6,
"class": "modal-input-xlarge Form-textArea Form-formGroup--fullWidth",
class: 'Form-formGroup--fullWidth',
"default": "---",
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
"JSON:<br />\n" +
@ -85,19 +80,16 @@ export default
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(host.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(host.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(host.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {}
});

View File

@ -4,91 +4,136 @@
* All Rights Reserved
*************************************************/
/**
/**
* @ngdoc function
* @name forms.function:Inventories
* @description This form is for adding/editing an inventory
*/
*/
export default
angular.module('InventoryFormDefinition', ['ScanJobsListDefinition'])
.factory('InventoryFormObject', ['i18n', function(i18n) {
angular.module('InventoryFormDefinition', ['ScanJobsListDefinition'])
.factory('InventoryFormObject', ['i18n', function(i18n) {
return {
addTitle: i18n._('New Inventory'),
editTitle: '{{ inventory_name }}',
name: 'inventory',
tabs: true,
addTitle: 'New Inventory',
editTitle: '{{ inventory_name }}',
name: 'inventory',
basePath: 'inventory',
// the top-most node of this generated state tree
stateTree: 'inventories',
tabs: true,
fields: {
inventory_name: {
realName: 'name',
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
fields: {
inventory_name: {
realName: 'name',
label: i18n._('Name'),
type: 'text',
required: true,
capitalize: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
inventory_description: {
realName: 'description',
label: i18n._('Description'),
type: 'text',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
basePath: 'organizations',
list: 'OrganizationList',
sourceModel: 'organization',
sourceField: 'name',
awRequiredWhen: {
reqExpression: "organizationrequired",
init: "true"
},
inventory_description: {
realName: 'description',
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
variables: {
label: i18n._('Variables'),
type: 'textarea',
class: 'Form-formGroup--fullWidth',
rows: 6,
"default": "---",
awPopOver: "<p>Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
dataTitle: 'Inventory Variables',
dataPlacement: 'right',
dataContainer: 'body',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {
permissions: {
awToolTip: i18n._('Please save before assigning permissions'),
dataPlacement: 'top',
basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/',
type: 'collection',
title: 'Permissions',
iterator: 'permission',
index: false,
open: false,
search: {
order_by: 'username'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
sourceModel: 'organization',
sourceField: 'name',
ngClick: 'lookUpOrganization()',
awRequiredWhen: {
reqExpression: "organizationrequired",
init: "true"
actions: {
add: {
label: i18n._('Add'),
ngClick: "$state.go('.add')",
awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
fields: {
username: {
label: i18n._('User'),
linkBase: 'users',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
},
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
variables: {
label: i18n._('Variables'),
type: 'textarea',
class: 'Form-formGroup--fullWidth',
addRequired: false,
editRequird: false,
rows: 6,
"default": "---",
awPopOver: i18n._("<p>Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
"JSON:<br />\n" +
"<blockquote>{<br />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"password\": \"magic\"<br /> }</blockquote>\n" +
"YAML:<br />\n" +
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>'),
dataTitle: i18n._('Inventory Variables'),
dataPlacement: 'right',
dataContainer: 'body',
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
role: {
label: i18n._('Role'),
type: 'role',
noSort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4',
},
team_roles: {
label: i18n._('Team Roles'),
type: 'team_roles',
noSort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4',
}
}
},
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
close: {
ngClick: 'formCancel()',
ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)'
}
},
related: {
relatedSets: function(urls) {
return {
permissions: {
awToolTip: i18n._('Please save before assigning permissions'),
dataPlacement: 'top',
@ -102,7 +147,7 @@ export default
actions: {
add: {
ngClick: "addPermission",
label: 'Add',
label: i18n._('Add'),
awToolTip: i18n._('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: i18n._('&#43; ADD'),
@ -133,28 +178,22 @@ export default
}
}
}
},
relatedSets: function(urls) {
return {
permissions: {
iterator: 'permission',
url: urls.access_list
}
};
}
};
}
};}])
.factory('InventoryForm', ['InventoryFormObject', 'ScanJobsList',
.factory('InventoryForm', ['InventoryFormObject', 'ScanJobsList',
function(InventoryFormObject, ScanJobsList) {
return function() {
var itm;
for (itm in InventoryFormObject.related) {
if (InventoryFormObject.related[itm].include === "ScanJobsList") {
InventoryFormObject.related[itm] = ScanJobsList;
InventoryFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list
InventoryFormObject.related[itm] = ScanJobsList;
InventoryFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list
}
}
return InventoryFormObject;
};
}]);
}
]);

View File

@ -19,26 +19,27 @@ export default
addTitle: i18n._('New Job Template'),
editTitle: '{{ name }}',
name: 'job_templates',
base: 'job_templates',
name: 'job_template',
basePath: 'job_templates',
// the top-most node of generated state tree
stateTree: 'jobTemplates',
tabs: true,
// (optional) array of supporting templates to ng-include inside generated html
include: ['/static/partials/survey-maker-modal.html'],
fields: {
name: {
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
column: 1,
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
column: 1
},
description: {
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
column: 1,
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
job_type: {
label: i18n._('Job Type'),
@ -46,8 +47,7 @@ export default
ngOptions: 'type.label for type in job_type_options track by type.value',
ngChange: 'jobTypeChange()',
"default": 0,
addRequired: true,
editRequired: true,
required: true,
column: 1,
awPopOver: i18n._("<p>When this template is submitted as a job, setting the type to <em>run</em> will execute the playbook, running tasks " +
" on the selected hosts.</p> <p>Setting the type to <em>check</em> will not execute the playbook. Instead, <code>ansible</code> will check playbook " +
@ -61,14 +61,15 @@ export default
ngShow: "!job_type.value || job_type.value !== 'scan'",
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
inventory: {
label: i18n._('Inventory'),
type: 'lookup',
basePath: 'inventory',
list: 'InventoryList',
sourceModel: 'inventory',
sourceField: 'name',
ngClick: 'lookUpInventory()',
awRequiredWhen: {
reqExpression: '!ask_inventory_on_launch',
alwaysShowAsterisk: true
@ -84,7 +85,7 @@ export default
ngShow: "!job_type.value || job_type.value !== 'scan'",
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
project: {
label: i18n._('Project'),
@ -94,9 +95,10 @@ export default
'class': "{{!(job_type.value === 'scan' && project_name !== 'Default') ? 'hidden' : ''}}",
},
type: 'lookup',
list: 'ProjectList',
basePath: 'projects',
sourceModel: 'project',
sourceField: 'name',
ngClick: 'lookUpProject()',
awRequiredWhen: {
reqExpression: "projectrequired",
init: "true"
@ -106,7 +108,7 @@ export default
dataTitle: i18n._('Project'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
playbook: {
label: i18n._('Playbook'),
@ -128,9 +130,13 @@ export default
credential: {
label: i18n._('Machine Credential'),
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
search: {
kind: 'ssh'
},
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
awRequiredWhen: {
reqExpression: '!ask_credential_on_launch',
alwaysShowAsterisk: true
@ -146,38 +152,42 @@ export default
variable: 'ask_credential_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
cloud_credential: {
label: i18n._('Cloud Credential'),
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
search: {
cloud: 'true'
},
sourceModel: 'cloud_credential',
sourceField: 'name',
ngClick: 'lookUpCloudcredential()',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: i18n._("<p>Selecting an optional cloud credential in the job template will pass along the access credentials to the " +
"running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.</p>"),
dataTitle: i18n._('Cloud Credential'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
network_credential: {
label: i18n._('Network Credential'),
type: 'lookup',
list: 'CredentialList',
basePath: 'credentials',
search: {
kind: 'net'
},
sourceModel: 'network_credential',
sourceField: 'name',
ngClick: 'lookUpNetworkcredential()',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: i18n._("<p>Network credentials are used by Ansible networking modules to connect to and manage networking devices.</p>"),
dataTitle: i18n._('Network Credential'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
forks: {
label: i18n._('Forks'),
@ -187,8 +197,6 @@ export default
min: 0,
spinner: true,
"default": '0',
addRequired: false,
editRequired: false,
'class': "input-small",
column: 1,
awPopOver: i18n._('<p>The number of parallel or simultaneous processes to use while executing the playbook. 0 signifies ' +
@ -197,13 +205,11 @@ export default
dataTitle: i18n._('Forks'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working
},
limit: {
label: i18n._('Limit'),
type: 'text',
addRequired: false,
editRequired: false,
column: 1,
awPopOver: i18n._("<p>Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " +
"Multiple patterns can be separated by &#59; &#58; or &#44;</p><p>For more information and examples see " +
@ -215,28 +221,25 @@ export default
variable: 'ask_limit_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
verbosity: {
label: i18n._('Verbosity'),
type: 'select',
ngOptions: 'v.label for v in verbosity_options track by v.value',
"default": 1,
addRequired: true,
editRequired: true,
required: true,
column: 1,
awPopOver: i18n._("<p>Control the level of output ansible will produce as the playbook executes.</p>"),
dataTitle: i18n._('Verbosity'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
job_tags: {
label: i18n._('Job Tags'),
type: 'textarea',
rows: 5,
addRequired: false,
editRequired: false,
'elementClass': 'Form-textInput',
column: 2,
awPopOver: i18n._("<p>Provide a comma separated list of tags.</p>\n" +
@ -249,14 +252,12 @@ export default
variable: 'ask_tags_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
skip_tags: {
label: i18n._('Skip Tags'),
type: 'textarea',
rows: 5,
addRequired: false,
editRequired: false,
'elementClass': 'Form-textInput',
column: 2,
awPopOver: i18n._("<p>Provide a comma separated list of tags.</p>\n" +
@ -269,7 +270,7 @@ export default
variable: 'ask_skip_tags_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
checkbox_group: {
label: i18n._('Options'),
@ -278,21 +279,17 @@ export default
name: 'become_enabled',
label: i18n._('Enable Privilege Escalation'),
type: 'checkbox',
addRequired: false,
editRequird: false,
column: 2,
awPopOver: i18n._("<p>If enabled, run this playbook as an administrator. This is the equivalent of passing the <code>--become</code> option to the <code>ansible-playbook</code> command. </p>"),
dataPlacement: 'right',
dataTitle: i18n._('Become Privilege Escalation'),
dataContainer: "body",
labelClass: 'stack-inline',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
}, {
name: 'allow_callbacks',
label: i18n._('Allow Provisioning Callbacks'),
type: 'checkbox',
addRequired: false,
editRequird: false,
ngChange: "toggleCallback('host_config_key')",
column: 2,
awPopOver: i18n._("<p>Enables creation of a provisioning callback URL. Using the URL a host can contact Tower and request a configuration update " +
@ -301,14 +298,12 @@ export default
dataTitle: i18n._('Allow Provisioning Callbacks'),
dataContainer: "body",
labelClass: 'stack-inline',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
}]
},
callback_url: {
label: i18n._('Provisioning Callback URL'),
type: 'text',
addRequired: false,
editRequired: false,
readonly: true,
ngShow: "allow_callbacks && allow_callbacks !== 'false'",
column: 2,
@ -317,7 +312,7 @@ export default
dataPlacement: 'top',
dataTitle: i18n._('Provisioning Callback URL'),
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
host_config_key: {
label: i18n._('Host Config Key'),
@ -331,7 +326,7 @@ export default
dataPlacement: 'right',
dataTitle: i18n._("Host Config Key"),
dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
labels: {
label: i18n._('Labels'),
@ -339,21 +334,17 @@ export default
class: 'Form-formGroup--fullWidth',
ngOptions: 'label.label for label in labelOptions track by label.value',
multiSelect: true,
addRequired: false,
editRequired: false,
dataTitle: i18n._('Labels'),
dataPlacement: 'right',
awPopOver: i18n._("<p>Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.</p>"),
dataContainer: 'body',
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
variables: {
label: i18n._('Extra Variables'),
type: 'textarea',
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
rows: 6,
addRequired: false,
editRequired: false,
"default": "---",
column: 2,
awPopOver: i18n._("<p>Pass extra command line variables to the playbook. This is the <code>-e</code> or <code>--extra-vars</code> command line parameter " +
@ -369,14 +360,14 @@ export default
variable: 'ask_variables_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working
}
},
buttons: { //for now always generates <button> tags
add_survey: {
ngClick: 'addSurvey()',
ngShow: 'job_type.value !== "scan" && !survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAdd)',
ngShow: 'job_type.value !== "scan" && !survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || !canAdd)',
awFeature: 'surveys',
awToolTip: 'Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.',
dataPlacement: 'top'
@ -384,25 +375,25 @@ export default
edit_survey: {
ngClick: 'editSurvey()',
awFeature: 'surveys',
ngShow: 'job_type.value !== "scan" && survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: 'job_type.value !== "scan" && survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
view_survey: {
ngClick: 'editSurvey()',
awFeature: 'surveys',
ngShow: 'job_type.value !== "scan" && survey_exists && !(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: 'job_type.value !== "scan" && survey_exists && !(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
cancel: {
ngClick: 'formCancel()',
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "job_templates_form.$invalid",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
@ -413,21 +404,23 @@ export default
permissions: {
awToolTip: i18n._('Please save before assigning permissions'),
dataPlacement: 'top',
basePath: 'job_templates/:id/access_list/',
basePath: 'api/v1/job_templates/{{$stateParams.job_template_id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: i18n._('Permissions'),
iterator: 'permission',
index: false,
open: false,
searchType: 'select',
actions: {
add: {
ngClick: "addPermission",
ngClick: "$state.go('.add')",
label: 'Add',
awToolTip: 'Add a permission',
actionClass: 'btn List-buttonSubmit',
buttonContent: '&#43; ADD',
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
@ -443,14 +436,12 @@ export default
type: 'role',
noSort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4',
searchable: false
},
team_roles: {
label: 'Team Roles',
type: 'team_roles',
noSort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4',
searchable: false
}
}
},

View File

@ -3,7 +3,7 @@
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name forms.function:JobVarsPrompt
@ -27,8 +27,8 @@ export default
label: null,
type: 'textarea',
rows: 6,
addRequired: false,
editRequired: false,
"default": "---"
}
},

View File

@ -17,6 +17,7 @@ export default
addTitle: 'Create Job',
editTitle: '{{ id }} - {{ name }}',
name: 'jobs',
stateTree: 'jobs',
well: true,
base: 'jobs',
tabs: true,

View File

@ -18,46 +18,47 @@ export default
addTitle: i18n._('New Organization'), //Title in add mode
editTitle: '{{ name }}', //Title in edit mode
name: 'organization', //entity or model name in singular form
stateTree: 'organizations',
tabs: true,
fields: {
name: {
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: false,
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
capitalize: false
},
description: {
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
buttons: { //for now always generates <button> tags
cancel: {
ngClick: 'formCancel()',
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(organization_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: true,
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {
permissions: {
basePath: 'organizations/:id/access_list/',
awToolTip: i18n._('Please save before assigning permissions'),
basePath: 'api/v1/organizations/{{$stateParams.organization_id}}/access_list/',
search: {
order_by: 'username'
},
dataPlacement: 'top',
type: 'collection',
title: i18n._('Permissions'),
@ -68,11 +69,11 @@ export default
actions: {
add: {
ngClick: "addPermission",
label: 'Add',
label: i18n._('Add'),
awToolTip: i18n._('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: i18n._('&#43; ADD'),
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(organization_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},

View File

@ -18,43 +18,39 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
addTitle: i18n._('New Project'),
editTitle: '{{ name }}',
name: 'project',
basePath: 'projects',
// the top-most node of generated state tree
stateTree: 'projects',
forceListeners: true,
tabs: true,
subFormTitles: {
sourceSubForm: i18n._('Source Details'),
},
fields: {
name: {
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: false,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
capitalize: false
},
description: {
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
list: 'OrganizationList',
sourceModel: 'organization',
basePath: 'organizations',
sourceField: 'name',
ngClick: 'lookUpOrganization()',
awRequiredWhen: {
reqExpression: "organizationrequired",
init: "true"
},
dataTitle: i18n._('Organization'),
required: true,
dataContainer: 'body',
dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
scm_type: {
label: i18n._('SCM Type'),
@ -62,10 +58,9 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
class: 'Form-dropDown--scmType',
ngOptions: 'type.label for type in scm_type_options track by type.value',
ngChange: 'scmChange()',
addRequired: true,
editRequired: true,
required: true,
hasSubForm: true,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
missing_path_alert: {
type: 'alertblock',
@ -88,7 +83,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: i18n._('Project Base Path'),
dataContainer: 'body',
dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
local_path: {
label: i18n._('Playbook Directory'),
@ -106,7 +101,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: i18n._('Project Path'),
dataContainer: 'body',
dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
scm_url: {
label: 'SCM URL',
@ -123,28 +118,29 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
dataTitle: 'SCM URL',
dataContainer: 'body',
dataPlacement: 'right',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
scm_branch: {
labelBind: "scmBranchLabel",
type: 'text',
ngShow: "scm_type && scm_type.value !== 'manual'",
addRequired: false,
editRequired: false,
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)',
subForm: 'sourceSubForm',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
},
credential: {
label: i18n._('SCM Credential'),
type: 'lookup',
basePath: 'credentials',
list: 'CredentialList',
// apply a default search filter to show only scm credentials
search: {
kind: 'scm'
},
ngShow: "scm_type && scm_type.value !== 'manual'",
sourceModel: 'credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
addRequired: false,
editRequired: false,
subForm: 'sourceSubForm',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)',
subForm: 'sourceSubForm'
},
checkbox_group: {
label: i18n._('SCM Update Options'),
@ -155,39 +151,33 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
name: 'scm_clean',
label: i18n._('Clean'),
type: 'checkbox',
addRequired: false,
editRequired: false,
awPopOver: i18n._('<p>Remove any local modifications prior to performing an update.</p>'),
dataTitle: i18n._('SCM Clean'),
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}, {
name: 'scm_delete_on_update',
label: i18n._('Delete on Update'),
type: 'checkbox',
addRequired: false,
editRequired: false,
awPopOver: i18n._('<p>Delete the local repository in its entirety prior to performing an update.</p><p>Depending on the size of the ' +
'repository this may significantly increase the amount of time required to complete an update.</p>'),
dataTitle: i18n._('SCM Delete'),
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}, {
name: 'scm_update_on_launch',
label: i18n._('Update on Launch'),
type: 'checkbox',
addRequired: false,
editRequired: false,
awPopOver: i18n._('<p>Each time a job runs using this project, perform an update to the local repository prior to starting the job.</p>'),
dataTitle: i18n._('SCM Update'),
dataContainer: 'body',
dataPlacement: 'right',
labelClass: 'checkbox-options stack-inline',
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}]
},
scm_update_cache_timeout: {
@ -199,61 +189,61 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
ngShow: "scm_update_on_launch && projectSelected && scm_type.value !== 'manual'",
spinner: true,
"default": '0',
addRequired: false,
editRequired: false,
awPopOver: i18n._('<p>Time in seconds to consider a project to be current. During job runs and callbacks the task system will ' +
'evaluate the timestamp of the latest project update. If it is older than Cache Timeout, it is not considered current, ' +
'and a new project update will be performed.</p>'),
dataTitle: i18n._('Cache Timeout'),
dataPlacement: 'right',
dataContainer: "body",
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working
ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {
permissions: {
awToolTip: i18n._('Please save before assigning permissions'),
djangoModel: 'access_list',
dataPlacement: 'top',
basePath: 'projects/:id/access_list/',
basePath: 'api/v1/projects/{{$stateParams.project_id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: i18n._('Permissions'),
iterator: 'permission',
index: false,
open: false,
searchType: 'select',
actions: {
add: {
ngClick: "addPermission",
ngClick: "$state.go('.add')",
label: 'Add',
awToolTip: i18n._('Add a permission'),
actionClass: 'btn List-buttonSubmit',
buttonContent: i18n._('&#43; ADD'),
ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(project_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
fields: {
username: {
key: true,
label: 'User',
linkBase: 'users',
uiSref: 'users({user_id: field.id})',
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
},
role: {
@ -261,14 +251,12 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition'])
type: 'role',
noSort: true,
class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4',
noSearch: true
},
team_roles: {
label: 'Team Roles',
type: 'team_roles',
noSort: true,
class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4',
noSearch: true
}
}
},

View File

@ -18,75 +18,73 @@ export default
addTitle: i18n._('New Team'), //Legend in add mode
editTitle: '{{ name }}', //Legend in edit mode
name: 'team',
// the top-most node of generated state tree
stateTree: 'teams',
tabs: true,
fields: {
name: {
label: i18n._('Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: false,
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
capitalize: false
},
description: {
label: i18n._('Description'),
type: 'text',
addRequired: false,
editRequired: false,
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
list: 'OrganizationsList',
sourceModel: 'organization',
basePath: 'organizations',
sourceField: 'name',
addRequired: true,
editRequire: false,
ngClick: 'lookUpOrganization()',
awRequiredWhen: {
reqExpression: "orgrequired",
init: true
},
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
}
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(team_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(team_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(team_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {
access_list: {
permissions: {
dataPlacement: 'top',
awToolTip: i18n._('Please save before adding users'),
basePath: 'teams/:id/access_list/',
basePath: 'api/v1/teams/{{$stateParams.team_id}}/access_list/',
search: {
order_by: 'username'
},
type: 'collection',
title: i18n._('Users'),
iterator: 'permission',
index: false,
open: false,
searchType: 'select',
actions: {
add: {
ngClick: "addPermissionWithoutTeamTab",
// @issue https://github.com/ansible/ansible-tower/issues/3487
//ngClick: "addPermissionWithoutTeamTab",
label: 'Add',
awToolTip: i18n._('Add user to team'),
actionClass: 'btn List-buttonSubmit',
buttonContent: i18n._('&#43; ADD'),
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(team_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
@ -110,7 +108,12 @@ export default
hideSearchAndActions: true,
dataPlacement: 'top',
awToolTip: i18n._('Please save before assigning permissions'),
basePath: 'teams/:id/roles/',
basePath: 'api/v1/teams/{{$stateParams.team_id}}/roles/',
search: {
page_size: '10',
// @todo ask about name field / serializer on this endpoint
order_by: 'id'
},
type: 'collection',
title: i18n._('Granted Permissions'),
iterator: 'role',
@ -147,7 +150,7 @@ export default
ngShow: 'permission.summary_fields.user_capabilities.unattach'
}
},
hideOnSuperuser: true
//hideOnSuperuser: true // defunct with RBAC
}
},
};}]); //InventoryForm

View File

@ -18,6 +18,8 @@ export default
addTitle: i18n._('New User'),
editTitle: '{{ username }}',
name: 'user',
// the top-most node of generated state tree
stateTree: 'users',
forceListeners: true,
tabs: true,
@ -25,26 +27,23 @@ export default
first_name: {
label: i18n._('First Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
capitalize: true
},
last_name: {
label: i18n._('Last Name'),
type: 'text',
addRequired: true,
editRequired: true,
capitalize: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
capitalize: true
},
email: {
label: i18n._('Email'),
type: 'email',
addRequired: true,
editRequired: true,
autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)',
required: true,
autocomplete: false
},
username: {
label: i18n._('Username'),
@ -54,46 +53,42 @@ export default
init: true
},
autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
organization: {
label: i18n._('Organization'),
type: 'lookup',
list: 'OrganizationList',
basePath: 'organizations',
sourceModel: 'organization',
sourceField: 'name',
addRequired: true,
editRequired: false,
required: true,
excludeMode: 'edit',
ngClick: 'lookUpOrganization()',
awRequiredWhen: {
reqExpression: "orgrequired",
init: true
},
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
password: {
label: i18n._('Password'),
type: 'sensitive',
hasShowInputButton: true,
ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null',
addRequired: true,
editRequired: false,
ngRequired: "$state.match('add')",
labelNGClass: "{'prepend-asterisk' : $state.matches('add')}",
ngChange: "clearPWConfirm('password_confirm')",
autocomplete: false,
chkPass: true,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
password_confirm: {
label: i18n._('Confirm Password'),
type: 'sensitive',
hasShowInputButton: true,
ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null',
addRequired: true,
editRequired: false,
ngRequired: "$state.match('add')",
labelNGClass: "{'prepend-asterisk' : $state.matches('add')}",
awPassMatch: true,
associated: 'password',
autocomplete: false,
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
user_type: {
label: i18n._('User Type'),
@ -102,30 +97,33 @@ export default
disableChooseOption: true,
ngModel: 'user_type',
ngShow: 'current_user["is_superuser"]',
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
},
buttons: {
cancel: {
ngClick: 'formCancel()',
ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '!(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
},
save: {
ngClick: 'formSave()',
ngDisabled: true,
ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
ngShow: '(user_obj.summary_fields.user_capabilities.edit || !canAdd)'
}
},
related: {
organizations: {
basePath: 'users/:id/organizations',
awToolTip: i18n._('Please save before assigning to organizations'),
basePath: 'api/v1/users/{{$stateParams.user_id}}/organizations',
search: {
page_size: '10'
},
dataPlacement: 'top',
type: 'collection',
title: i18n._('Organizations'),
@ -144,11 +142,14 @@ export default
label: 'Description'
}
},
hideOnSuperuser: true
//hideOnSuperuser: true // RBAC defunct
},
teams: {
basePath: 'users/:id/teams',
awToolTip: i18n._('Please save before assigning to teams'),
basePath: 'api/v1/users/{{$stateParams.user_id}}/teams',
search: {
page_size: '10'
},
dataPlacement: 'top',
type: 'collection',
title: i18n._('Teams'),
@ -166,9 +167,15 @@ export default
label: 'Description'
}
},
hideOnSuperuser: true
//hideOnSuperuser: true // RBAC defunct
},
roles: {
permissions: {
basePath: 'api/v1/users/{{$stateParams.user_id}}/roles/',
search: {
page_size: '10',
// @todo ask about name field / serializer on this endpoint
order_by: 'id'
},
awToolTip: i18n._('Please save before assigning to organizations'),
dataPlacement: 'top',
hideSearchAndActions: true,
@ -196,6 +203,12 @@ export default
noSort: true
},
},
// @issue https://github.com/ansible/ansible-tower/issues/3487
// actions: {
// add: {
// }
// }
fieldActions: {
"delete": {
label: i18n._('Remove'),
@ -205,7 +218,7 @@ export default
ngShow: 'permission.summary_fields.user_capabilities.unattach'
}
},
hideOnSuperuser: true
//hideOnSuperuser: true // RBAC defunct
}
}

View File

@ -17,7 +17,6 @@ import JobSubmission from "./helpers/JobSubmission";
import JobTemplates from "./helpers/JobTemplates";
import Jobs from "./helpers/Jobs";
import LoadConfig from "./helpers/LoadConfig";
import PaginationHelpers from "./helpers/PaginationHelpers";
import Parse from "./helpers/Parse";
import ProjectPath from "./helpers/ProjectPath";
import Projects from "./helpers/Projects";
@ -28,10 +27,6 @@ import Variables from "./helpers/Variables";
import ApiDefaults from "./helpers/api-defaults";
import inventory from "./helpers/inventory";
import MD5 from "./helpers/md5";
import RefreshRelated from "./helpers/refresh-related";
import Refresh from "./helpers/refresh";
import RelatedSearch from "./helpers/related-search";
import Search from "./helpers/search";
import Teams from "./helpers/teams";
import AdhocHelper from "./helpers/Adhoc";
import ApiModelHelper from "./helpers/ApiModel";
@ -48,7 +43,6 @@ export
JobTemplates,
Jobs,
LoadConfig,
PaginationHelpers,
Parse,
ProjectPath,
Projects,
@ -59,10 +53,6 @@ export
ApiDefaults,
inventory,
MD5,
RefreshRelated,
Refresh,
RelatedSearch,
Search,
Teams,
AdhocHelper,
ApiModelHelper,

View File

@ -41,7 +41,7 @@
export default
angular.module('AdhocHelper', ['RestServices', 'Utilities',
'CredentialFormDefinition', 'CredentialsListDefinition', 'LookUpHelper',
'CredentialFormDefinition', 'CredentialsListDefinition',
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog',
'FormGenerator', 'JobVarsPromptFormDefinition'])

View File

@ -223,8 +223,8 @@ angular.module('CredentialsHelper', ['Utilities'])
}
])
.factory('FormSave', ['$rootScope', 'Refresh', '$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', '$state',
function ($rootScope, Refresh, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state) {
.factory('FormSave', ['$rootScope', '$location', 'Alert', 'Rest', 'ProcessErrors', 'Empty', 'GetBasePath', 'CredentialForm', 'ReturnToCaller', 'Wait', '$state',
function ($rootScope, $location, Alert, Rest, ProcessErrors, Empty, GetBasePath, CredentialForm, ReturnToCaller, Wait, $state) {
return function (params) {
var scope = params.scope,
mode = params.mode,
@ -279,12 +279,13 @@ angular.module('CredentialsHelper', ['Utilities'])
.success(function (data) {
scope.addedItem = data.id;
Refresh({
scope: scope,
set: 'credentials',
iterator: 'credential',
url: url
});
// @issue: OLD SEARCH
// Refresh({
// scope: scope,
// set: 'credentials',
// iterator: 'credential',
// url: url
// });
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];

View File

@ -15,9 +15,8 @@
import listGenerator from '../shared/list-generator/main';
export default
angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'GroupListDefinition', 'SearchHelper',
'PaginationHelpers', listGenerator.name, 'GroupsHelper', 'InventoryHelper', 'SelectionHelper',
'JobSubmissionHelper', 'RefreshHelper', 'PromptDialog', 'CredentialsListDefinition',
angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'GroupListDefinition', listGenerator.name, 'GroupsHelper', 'InventoryHelper', 'SelectionHelper',
'JobSubmissionHelper', 'PromptDialog', 'CredentialsListDefinition',
'InventoryStatusDefinition', 'VariablesHelper', 'SchedulesListDefinition', 'StandardOutHelper',
'SchedulesHelper'
])
@ -325,10 +324,10 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
*/
.factory('GroupsEdit', ['$filter', '$rootScope', '$location', '$log', '$stateParams', '$compile', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate',
'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find',
'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find',
'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SetSchedulesInnerDialogSize', 'CreateSelect2',
function ($filter, $rootScope, $location, $log, $stateParams, $compile, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait,
GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, Empty, Wait,
GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString, ToJSON, GroupsScheduleListInit,
SetSchedulesInnerDialogSize, CreateSelect2) {
return function (params) {
@ -842,9 +841,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
// Clean up
Wait('stop');
if (modal_scope.searchCleanUp) {
modal_scope.searchCleanup();
}
// @issue: OLD SEARCH
// if (modal_scope.searchCleanUp) {
// modal_scope.searchCleanup();
// }
try {
$('#group-modal-dialog').dialog('close');
}
@ -947,15 +948,19 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
catch(e) {
//ignore
}
if (modal_scope.searchCleanup) {
modal_scope.searchCleanup();
}
if (parent_scope.restoreSearch) {
parent_scope.restoreSearch();
}
else {
Wait('stop');
}
// @issue: OLD SEARCH
// if (modal_scope.searchCleanup) {
// modal_scope.searchCleanup();
// }
// if (parent_scope.restoreSearch) {
// parent_scope.restoreSearch();
// }
// else {
// Wait('stop');
// }
Wait('stop');
};
// Save

View File

@ -18,8 +18,8 @@ import listGenerator from '../shared/list-generator/main';
export default
angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name, 'HostListDefinition',
'SearchHelper', 'PaginationHelpers', listGenerator.name, 'HostsHelper',
'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper',
listGenerator.name, 'HostsHelper',
'InventoryHelper', 'InventoryFormDefinition', 'SelectionHelper',
'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'StandardOutHelper',
'GroupListDefinition'
])
@ -160,23 +160,26 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
};
}])
.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait',
.factory('HostsReload', [ '$stateParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'Wait',
'SetHostStatus', 'SetStatus', 'ApplyEllipsis',
function($stateParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus, SetStatus,
function($stateParams, Empty, InventoryHosts, GetBasePath, Wait, SetHostStatus, SetStatus,
ApplyEllipsis) {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
group_id = params.group_id,
inventory_id = params.inventory_id,
list = InventoryHosts,
pageSize = (params.pageSize) ? params.pageSize : 20,
parent_scope = params.parent_scope;
url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' :
GetBasePath('inventory') + inventory_id + '/hosts/';
// @issue: OLD SEARCH
// var list = InventoryHosts,
// group_id = params.group_id,
// inventory_id = params.inventory_id;
// pageSize = (params.pageSize) ? params.pageSize : 20,
//
// url = ( !Empty(group_id) ) ? GetBasePath('groups') + group_id + '/all_hosts/' :
// GetBasePath('inventory') + inventory_id + '/hosts/';
scope.search_place_holder='Search ' + scope.selected_group_name;
// @issue: OLD SEARCH
// scope.search_place_holder='Search ' + scope.selected_group_name;
if (scope.removeHostsReloadPostRefresh) {
scope.removeHostsReloadPostRefresh();
@ -196,31 +199,31 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
}
});
SearchInit({ scope: scope, set: 'hosts', list: list, url: url });
PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize });
if ($stateParams.host_name) {
scope[list.iterator + 'InputDisable'] = false;
scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
scope[list.iterator + 'SearchField'] = 'name';
scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
scope[list.iterator + 'SearchSelectValue'] = null;
}
if (scope.show_failures) {
scope[list.iterator + 'InputDisable'] = true;
scope[list.iterator + 'SearchValue'] = 'true';
scope[list.iterator + 'SearchField'] = 'has_active_failures';
scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label;
scope[list.iterator + 'SearchSelectValue'] = { value: 1 };
}
scope.search(list.iterator, null, true);
// @issue: OLD SEARCH
// SearchInit({ scope: scope, set: 'hosts', list: list, url: url });
// PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize });
//
// if ($stateParams.host_name) {
// scope[list.iterator + 'InputDisable'] = false;
// scope[list.iterator + 'SearchValue'] = $stateParams.host_name;
// scope[list.iterator + 'SearchField'] = 'name';
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
// scope[list.iterator + 'SearchSelectValue'] = null;
// }
//
// if (scope.show_failures) {
// scope[list.iterator + 'InputDisable'] = true;
// scope[list.iterator + 'SearchValue'] = 'true';
// scope[list.iterator + 'SearchField'] = 'has_active_failures';
// scope[list.iterator + 'SearchFieldLabel'] = list.fields.has_active_failures.label;
// scope[list.iterator + 'SearchSelectValue'] = { value: 1 };
// }
// scope.search(list.iterator, null, true);
};
}])
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList', 'SearchInit',
'PaginateInit',
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList, SearchInit, PaginateInit) {
.factory('HostsCopy', ['$compile', 'Rest', 'ProcessErrors', 'CreateDialog', 'GetBasePath', 'Wait', 'generateList', 'GroupList',
function($compile, Rest, ProcessErrors, CreateDialog, GetBasePath, Wait, GenerateList, GroupList) {
return function(params) {
var host_id = params.host_id,
@ -271,7 +274,9 @@ return function(params) {
scope.removeHostCopyDialogReady();
}
scope.removeCopyDialogReady = scope.$on('HostCopyDialogReady', function() {
var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/';
// @issue: OLD SEARCH
// var url = GetBasePath('inventory') + group_scope.inventory.id + '/groups/';
GenerateList.inject(GroupList, {
mode: 'lookup',
id: 'copy-host-select-container',
@ -279,19 +284,21 @@ return function(params) {
//,
//instructions: instructions
});
SearchInit({
scope: scope,
set: GroupList.name,
list: GroupList,
url: url
});
PaginateInit({
scope: scope,
list: GroupList,
url: url,
mode: 'lookup'
});
scope.search(GroupList.iterator, null, true, false);
// @issue: OLD SEARCH
// SearchInit({
// scope: scope,
// set: GroupList.name,
// list: GroupList,
// url: url
// });
// PaginateInit({
// scope: scope,
// list: GroupList,
// url: url,
// mode: 'lookup'
// });
// scope.search(GroupList.iterator, null, true, false);
});
if (scope.removeShowDialog) {
@ -341,8 +348,12 @@ return function(params) {
catch(e) {
// ignore
}
scope.searchCleanup();
group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists
// @issue: OLD SEARCH
// scope.searchCleanup();
// @issue: OLD SEARCH
// group_scope.restoreSearch(); // Restore all parent search stuff and refresh hosts and groups lists
scope.$destroy();
};

View File

@ -686,9 +686,11 @@ export default
scope.plays = [];
url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id';
url += (scope.search_play_name) ? '&play__icontains=' + encodeURIComponent(scope.search_play_name) : '';
url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
// @issue: OLD SEARCH - factory needs refactoring! will be completely rehauled for job details 3.1 update
// url = scope.job.url + 'job_plays/?page_size=' + scope.playsMaxRows + '&order=id';
// url += (scope.search_play_name) ? '&play__icontains=' + encodeURIComponent(scope.search_play_name) : '';
// url += (scope.search_play_status === 'failed') ? '&failed=true' : '';
scope.playsLoading = true;
Rest.setUrl(url);
Rest.get()
@ -786,9 +788,12 @@ export default
scope.tasks = [];
if (scope.selectedPlay) {
url = scope.job.url + 'job_tasks/?event_id=' + scope.selectedPlay;
url += (scope.search_task_name) ? '&task__icontains=' + encodeURIComponent(scope.search_task_name) : '';
url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id';
// @issue: OLD SEARCH
// url += (scope.search_task_name) ? '&task__icontains=' + encodeURIComponent(scope.search_task_name) : '';
// url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
// url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) {
if (p.id === scope.selectedPlay) {
play = scope.plays[idx];
@ -918,9 +923,12 @@ export default
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
if (scope.search_host_status === 'failed'){
params.failed = true;
}
// @issue: OLD SEARCH
// if (scope.search_host_status === 'failed'){
// params.failed = true;
// }
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results);
scope.hostResultsLoading = false;
@ -1052,27 +1060,30 @@ export default
filteredListX[key] = plays[key];
}
}
if (scope.search_play_name) {
for (key in plays) {
if (filteredListX[key].name.indexOf(scope.search_play_name) > 0) {
filteredListA[key] = filteredListX[key];
}
}
}
else {
filteredListA = filteredListX;
}
if (scope.search_play_status === 'failed') {
for (key in filteredListA) {
if (filteredListA[key].status === 'failed') {
filteredListB[key] = plays[key];
}
}
}
else {
filteredListB = filteredListA;
}
// @issue: OLD SEARCH
// if (scope.search_play_name) {
// for (key in plays) {
// if (filteredListX[key].name.indexOf(scope.search_play_name) > 0) {
// filteredListA[key] = filteredListX[key];
// }
// }
// }
// else {
// filteredListA = filteredListX;
// }
// @issue: OLD SEARCH
// if (scope.search_play_status === 'failed') {
// for (key in filteredListA) {
// if (filteredListA[key].status === 'failed') {
// filteredListB[key] = plays[key];
// }
// }
// }
// else {
// filteredListB = filteredListA;
// }
keys = Object.keys(filteredListB);
keys.sort(function(a,b) { return listSort(a,b); }).reverse();
@ -1130,27 +1141,29 @@ export default
}
}
if (scope.search_task_name) {
for (key in filteredListX) {
if (filteredListX[key].name.indexOf(scope.search_task_name) > 0) {
filteredListA[key] = filteredListX[key];
}
}
}
else {
filteredListA = filteredListX;
}
// @issue: OLD SEARCH
// if (scope.search_task_name) {
// for (key in filteredListX) {
// if (filteredListX[key].name.indexOf(scope.search_task_name) > 0) {
// filteredListA[key] = filteredListX[key];
// }
// }
// }
// else {
// filteredListA = filteredListX;
// }
if (scope.search_task_status === 'failed') {
for (key in filteredListA) {
if (filteredListA[key].status === 'failed') {
filteredListB[key] = tasks[key];
}
}
}
else {
filteredListB = filteredListA;
}
// @issue: OLD SEARCH
// if (scope.search_task_status === 'failed') {
// for (key in filteredListA) {
// if (filteredListA[key].status === 'failed') {
// filteredListB[key] = tasks[key];
// }
// }
// }
// else {
// filteredListB = filteredListA;
// }
keys = Object.keys(filteredListB);
keys.sort(function(a,b) { return listSort(a,b); }).reverse();
@ -1196,27 +1209,30 @@ export default
//hostResults = JSON.parse(JSON.stringify(scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults));
hostResults = scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults;
if (scope.search_host_name) {
for (key in hostResults) {
if (hostResults[key].name.indexOf(scope.search_host_name) > 0) {
filteredListA[key] = hostResults[key];
}
}
}
else {
filteredListA = hostResults;
}
// @issue: OLD SEARCH
// if (scope.search_host_name) {
// for (key in hostResults) {
// if (hostResults[key].name.indexOf(scope.search_host_name) > 0) {
// filteredListA[key] = hostResults[key];
// }
// }
// }
// else {
// filteredListA = hostResults;
// }
if (scope.search_host_status === 'failed' || scope.search_host_status === 'unreachable') {
for (key in filteredListA) {
if (filteredListA[key].status === 'failed') {
filteredListB[key] = filteredListA[key];
}
}
}
else {
filteredListB = filteredListA;
}
// @issue: OLD SEARCH
// if (scope.search_host_status === 'failed' || scope.search_host_status === 'unreachable') {
// for (key in filteredListA) {
// if (filteredListA[key].status === 'failed') {
// filteredListB[key] = filteredListA[key];
// }
// }
// }
// else {
// filteredListB = filteredListA;
// }
keys = Object.keys(filteredListB);
keys.sort(function compare(a, b) {
if (filteredListB[a].name === filteredListB[b].name) {

View File

@ -8,7 +8,7 @@
export default
angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition'])
'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition'])
.factory('CreateLaunchDialog', ['$compile', 'CreateDialog', 'Wait', 'ParseTypeChange',
function($compile, CreateDialog, Wait, ParseTypeChange) {

View File

@ -19,9 +19,9 @@ angular.module('JobTemplatesHelper', ['Utilities'])
*/
.factory('CallbackHelpInit', ['$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', '$stateParams', 'ProcessErrors', 'ParseTypeChange',
'ParseVariableString', 'Empty', 'LookUpInit', 'InventoryList', 'CredentialList','ProjectList', 'RelatedSearchInit', 'RelatedPaginateInit', 'Wait',
function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors,ParseTypeChange,
ParseVariableString, Empty, LookUpInit, InventoryList, CredentialList, ProjectList, RelatedSearchInit, RelatedPaginateInit, Wait) {
'ParseVariableString', 'Empty', 'InventoryList', 'CredentialList','ProjectList', 'Wait',
function($location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, ParseTypeChange,
ParseVariableString, Empty, InventoryList, CredentialList, ProjectList, Wait) {
return function(params) {
var scope = params.scope,
@ -31,7 +31,7 @@ angular.module('JobTemplatesHelper', ['Utilities'])
// loadingFinishedCount = 0,
// base = $location.path().replace(/^\//, '').split('/')[0],
master = {},
id = $stateParams.id,
id = $stateParams.job_template_id,
relatedSets = {};
// checkSCMStatus, getPlaybooks, callback,
// choicesCount = 0;
@ -158,74 +158,11 @@ angular.module('JobTemplatesHelper', ['Utilities'])
scope.can_edit = data.summary_fields.can_edit;
LookUpInit({
scope: scope,
form: form,
current_item: data.inventory,
list: InventoryList,
field: 'inventory',
input_type: "radio"
});
CredentialList.basePath = GetBasePath('credentials') + '?kind=ssh';
// remove "type" field from search options
CredentialList.fields.kind.noSearch = true;
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: scope,
form: form,
current_item: data.credential,
list: CredentialList,
field: 'credential',
hdr: 'Select Machine Credential',
input_type: "radio"
});
var NetworkCredentialList = {};
// Clone the CredentialList object for use with network_credential. Cloning
// and changing properties to avoid collision.
jQuery.extend(true, NetworkCredentialList, CredentialList);
NetworkCredentialList.name = 'networkcredentials';
NetworkCredentialList.iterator = 'networkcredential';
NetworkCredentialList.basePath = '/api/v1/credentials?kind=net';
LookUpInit({
url: GetBasePath('credentials') + '?kind=net',
scope: scope,
form: form,
current_item: data.network_credential,
list: NetworkCredentialList,
field: 'network_credential',
hdr: 'Select Network Credential',
input_type: "radio"
});
LookUpInit({
scope: scope,
form: form,
current_item: data.project,
list: ProjectList,
field: 'project',
input_type: "radio"
});
if (scope.project === "" && scope.playbook === "") {
scope.resetProjectToDefault();
}
RelatedSearchInit({
scope: scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: scope,
relatedSets: relatedSets
});
scope.$emit('jobTemplateLoaded', data.related.cloud_credential, master, relatedSets);
})
.error(function (data, status) {

View File

@ -14,92 +14,7 @@ import listGenerator from '../shared/list-generator/main';
export default
angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers',
'JobSubmissionHelper', 'StandardOutHelper', 'SearchHelper', 'PaginationHelpers', 'AdhocHelper', listGenerator.name])
/**
* JobsControllerInit({ scope: $scope });
*
* Initialize calling scope with all the bits required to support a jobs list
*
*/
.factory('JobsControllerInit', ['$state', 'Find', 'DeleteJob', 'RelaunchJob',
function($state, Find, DeleteJob, RelaunchJob) {
return function(params) {
var scope = params.scope,
iterator = (params.iterator) ? params.iterator : scope.iterator;
scope.deleteJob = function(id) {
DeleteJob({ scope: scope, id: id });
};
scope.relaunchJob = function(event, id) {
var list, job, typeId;
try {
$(event.target).tooltip('hide');
}
catch(e) {
//ignore
}
if (scope.completed_jobs) {
list = scope.completed_jobs;
}
else if (scope.running_jobs) {
list = scope.running_jobs;
}
else if (scope.queued_jobs) {
list = scope.queued_jobs;
}
else if (scope.jobs) {
list = scope.jobs;
}
else if(scope.all_jobs){
list = scope.all_jobs;
}
job = Find({ list: list, key: 'id', val: id });
if (job.type === 'inventory_update') {
typeId = job.inventory_source;
}
else if (job.type === 'project_update') {
typeId = job.project;
}
else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command') {
typeId = job.id;
}
RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name });
};
scope.refreshJobs = function() {
scope.search(iterator);
};
scope.viewJobDetails = function(job) {
var goToJobDetails = function(state) {
$state.go(state, {id: job.id}, {reload:true});
};
switch(job.type) {
case 'job':
goToJobDetails('jobDetail');
break;
case 'ad_hoc_command':
goToJobDetails('adHocJobStdout');
break;
case 'system_job':
goToJobDetails('managementJobStdout');
break;
case 'project_update':
goToJobDetails('scmUpdateStdout');
break;
case 'inventory_update':
goToJobDetails('inventorySyncStdout');
break;
}
};
};
}
])
'JobSubmissionHelper', 'StandardOutHelper', 'AdhocHelper', listGenerator.name])
.factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', 'RelaunchAdhoc',
function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) {
@ -210,83 +125,6 @@ export default
};
}])
/**
*
* Called from JobsList controller to load each section or list on the page
*
*/
.factory('LoadJobsScope', ['$stateParams', '$location', '$compile',
'SearchInit', 'PaginateInit', 'generateList', 'JobsControllerInit',
'JobsListUpdate',
function($stateParams, $location, $compile, SearchInit, PaginateInit,
GenerateList, JobsControllerInit, JobsListUpdate) {
return function(params) {
var parent_scope = params.parent_scope,
scope = params.scope,
list = params.list,
id = params.id,
url = params.url,
pageSize = params.pageSize || 10,
base = $location.path().replace(/^\//, '').split('/')[0],
search_params = params.searchParams,
spinner = (params.spinner === undefined) ? true : params.spinner, key;
var buildTooltips = function(data){
data.forEach((val) => {
val.status_tip = 'Job ' + val.status + ". Click for details.";
});
};
GenerateList.inject(list, {
mode: 'edit',
id: id,
scope: scope,
title: false
});
SearchInit({
scope: scope,
set: list.name,
list: list,
url: url
});
PaginateInit({
scope: scope,
list: list,
url: url,
pageSize: pageSize
});
scope.iterator = list.iterator;
if (scope.removePostRefresh) {
scope.removePostRefresh();
}
scope.$on('PostRefresh', function(){
JobsControllerInit({ scope: scope, parent_scope: parent_scope });
JobsListUpdate({ scope: scope, parent_scope: parent_scope, list: list });
buildTooltips(scope.jobs);
parent_scope.$emit('listLoaded');
});
if (base === 'jobs' && list.name === 'all_jobs') {
if ($stateParams.id__int) {
scope[list.iterator + 'SearchField'] = 'id';
scope[list.iterator + 'SearchValue'] = $stateParams.id__int;
scope[list.iterator + 'SearchFieldLabel'] = 'Job ID';
}
}
if (search_params) {
for (key in search_params) {
scope[key] = search_params[key];
}
}
scope.search(list.iterator, null, null, null, null, spinner);
};
}])
.factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait',
'ProcessErrors', 'Prompt', 'Alert', '$filter',
function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert,
@ -338,7 +176,8 @@ export default
scope.$emit(callback, action_label);
}
else {
scope.search(scope.iterator);
// @issue: OLD SEARCH
// scope.search(scope.iterator);
}
})
.error(function(obj, status) {
@ -359,7 +198,8 @@ export default
scope.$emit(callback, action_label);
}
else {
scope.search(scope.iterator);
// @issue: OLD SEARCH
// scope.search(scope.iterator);
}
})
.error(function (obj, status) {

View File

@ -1,182 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:PaginationHelpers
* @description pagination
*/
export default
angular.module('PaginationHelpers', ['Utilities', 'RefreshHelper', 'RefreshRelatedHelper'])
.factory('PageRangeSetup', ['Empty',
function (Empty) {
return function (params) {
var scope = params.scope,
count = params.count,
next = params.next,
previous = params.previous,
iterator = params.iterator,
i, first, last;
scope[iterator + '_page'] = 1;
scope[iterator + '_num_pages'] = Math.ceil((count / scope[iterator + '_page_size']));
scope[iterator + '_num_pages'] = (scope[iterator + '_num_pages'] <= 0) ? 1 : scope[iterator + '_num_pages'];
scope[iterator + '_total_rows'] = count;
$('#'+iterator+'-pagination #pagination-links li:eq(1)').removeAttr('class');
// Which page are we on?
if (Empty(next) && previous) {
// no next page, but there is a previous page
scope[iterator + '_page'] = /page=\d+/.test(previous) ? parseInt(previous.match(/page=(\d+)/)[1]) + 1 : 2;
} else if (next && Empty(previous)) {
// next page available, but no previous page
scope[iterator + '_page'] = 1;
$('#'+iterator+'-pagination #pagination-links li:eq(1)').attr('class', 'disabled');
} else if (next && previous) {
// we're in between next and previous
scope[iterator + '_page'] = /page=\d+/.test(previous) ? parseInt(previous.match(/page=(\d+)/)[1]) + 1 : 2;
}
// Calc the range of up to 10 pages to show
scope[iterator + '_page_range'] = [];
first = (scope[iterator + '_page'] > 5) ? scope[iterator + '_page'] - 5 : 1;
if (scope[iterator + '_page'] < 6) {
last = (10 <= scope[iterator + '_num_pages']) ? 10 : scope[iterator + '_num_pages'];
} else {
last = (scope[iterator + '_page'] + 4 < scope[iterator + '_num_pages']) ?
scope[iterator + '_page'] + 4 : scope[iterator + '_num_pages'];
}
for (i = first; i <= last; i++) {
scope[iterator + '_page_range'].push(i);
}
};
}
])
.factory('RelatedPaginateInit', ['RefreshRelated', '$cookieStore', 'Wait',
function (RefreshRelated, $cookieStore, Wait) {
return function (params) {
var scope = params.scope,
relatedSets = params.relatedSets,
pageSize = (params.pageSize) ? params.pageSize : 10,
key;
for (key in relatedSets) {
scope[relatedSets[key].iterator + '_url'] = relatedSets[key].url;
scope[relatedSets[key].iterator + '_page'] = 0;
scope[relatedSets[key].iterator + '_page_size'] = pageSize;
}
scope.getPage = function (page, set, iterator) {
var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''),
connect = (/\/$/.test(new_url)) ? '?' : '&';
if(scope[iterator + '_page'] === 1 && page === 0){
return;
}
new_url += connect + 'page=' + page;
if (scope[iterator + 'SearchFilters']){
new_url += _.reduce(scope[iterator+'SearchFilters'], (result, filter) => result + '&' + filter.url, '');
}
if (scope[iterator + 'SearchParams']){
new_url += '&' + scope[iterator + 'SearchParams'];
}
new_url += '&page_size=' + scope[iterator + '_page_size'];
Wait('start');
RefreshRelated({ scope: scope, set: set, iterator: iterator, url: new_url });
};
scope.pageIsActive = function (page, iterator) {
return (page === scope[iterator + '_page']) ? 'active' : '';
};
scope.changePageSize = function (set, iterator) {
// Called when a new page size is selected
scope[iterator + '_page'] = 1;
var url = scope[iterator + '_url'];
// Using the session cookie, keep track of user rows per page selection
$cookieStore.put(iterator + '_page_size', scope[iterator + '_page_size']);
url = url.replace(/\/\?.*$/, '/');
url += (scope[iterator + 'SearchParams']) ? '?' + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] :
'?page_size=' + scope[iterator + '_page_size'];
RefreshRelated({
scope: scope,
set: set,
iterator: iterator,
url: url
});
};
};
}
])
.factory('PaginateInit', ['Refresh', '$cookieStore', 'Wait',
function (Refresh, $cookieStore, Wait) {
return function (params) {
var scope = params.scope,
list = params.list,
iterator = (params.iterator) ? params.iterator : list.iterator,
pageSize = params.pageSize,
mode = (params.mode) ? params.mode : null;
scope[iterator + '_page'] = (params.page) ? params.page : 1;
scope[iterator + '_url'] = params.url;
scope[iterator + '_mode'] = mode;
if (pageSize) {
scope[iterator + '_page_size'] = params.pageSize;
} else if (mode === 'lookup') {
scope[iterator + '_page_size'] = 5;
} else {
scope[iterator + '_page_size'] = 20;
}
scope.getPage = function (page, set, iterator) {
var new_url = scope[iterator + '_url'].replace(/.page\=\d+/, ''),
connect = (/\/$/.test(new_url)) ? '?' : '&';
if(scope[iterator + '_page'] === 1 && page === 0){
return;
}
new_url += connect + 'page=' + page;
if (scope[iterator + 'SearchFilters']){
new_url += _.reduce(scope[iterator+'SearchFilters'], (result, filter) => result + '&' + filter.url, '');
}
if (scope[iterator + 'SearchParams']){
new_url += '&' + scope[iterator + 'SearchParams'];
}
new_url += '&page_size=' + scope[iterator + '_page_size'];
Wait('start');
scope.getNewPage = true;
Refresh({ scope: scope, set: set, iterator: iterator, url: new_url });
};
scope.pageIsActive = function (page, iterator) {
return (page === scope[iterator + '_page']) ? 'active' : '';
};
scope.changePageSize = function (set, iterator, spinner) {
// Called whenever a new page size is selected
scope[iterator + '_page'] = 1;
var new_url = scope[iterator + '_url'].replace(/\?page_size\=\d+/, ''),
connect = (/\/$/.test(new_url)) ? '?' : '&';
new_url += (scope[iterator + 'SearchParams']) ? connect + scope[iterator + 'SearchParams'] + '&page_size=' + scope[iterator + '_page_size'] :
connect + 'page_size=' + scope[iterator + '_page_size'];
if (spinner === undefined || spinner === true) {
Wait('start');
}
Refresh({ scope: scope, set: set, iterator: iterator, url: new_url });
};
};
}
]);

View File

@ -17,7 +17,7 @@
import listGenerator from '../shared/list-generator/main';
export default
angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', 'SearchHelper', 'PaginationHelpers', listGenerator.name, 'ModalDialog',
angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', listGenerator.name, 'ModalDialog',
'GeneratorHelpers'])
.factory('EditSchedule', ['SchedulerInit', '$rootScope', 'Wait', 'Rest',
@ -272,7 +272,7 @@ export default
if (callback) {
scope.$emit(callback, data);
}
$state.go("^");
$state.go("^", null, {reload: true});
});
scope.saveSchedule = function() {
SchedulePost({
@ -376,7 +376,8 @@ export default
* });
*
*/
.factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', function(Wait, GetBasePath, ProcessErrors, Rest) {
.factory('ToggleSchedule', ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', '$state',
function(Wait, GetBasePath, ProcessErrors, Rest, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
@ -391,12 +392,8 @@ export default
data.enabled = (data.enabled) ? false : true;
Rest.put(data)
.success( function() {
if (callback) {
scope.$emit(callback, id);
}
else {
Wait('stop');
}
Wait('stop');
$state.go('.', null, {reload: true});
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
@ -429,9 +426,9 @@ export default
* })
*
*/
.factory('DeleteSchedule', ['GetBasePath','Rest', 'Wait',
.factory('DeleteSchedule', ['GetBasePath','Rest', 'Wait', '$state',
'ProcessErrors', 'Prompt', 'Find', '$location', '$filter',
function(GetBasePath, Rest, Wait, ProcessErrors, Prompt, Find,
function(GetBasePath, Rest, Wait, $state, ProcessErrors, Prompt, Find,
$location, $filter) {
return function(params) {
@ -461,6 +458,9 @@ export default
if (new RegExp('/' + id + '$').test($location.$$url)) {
$location.url($location.url().replace(/[/][0-9]+$/, "")); // go to list view
}
else{
$state.go('.', null, {reload: true});
}
})
.error(function (data, status) {
try {
@ -497,214 +497,4 @@ export default
});
return response;
};
}])
.factory('SchedulesControllerInit', ['$state', '$location',
'ToggleSchedule', 'DeleteSchedule', 'ParamPass',
function($state, $location, ToggleSchedule, DeleteSchedule,
ParamPass) {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
iterator = (params.iterator) ? params.iterator : scope.iterator,
base = params.base || $location.path().replace(/^\//, '').split('/')[0];
scope.toggleSchedule = function(event, id) {
try {
$(event.target).tooltip('hide');
}
catch(e) {
// ignore
}
ToggleSchedule({
scope: scope,
id: id,
callback: 'SchedulesRefresh'
});
};
scope.deleteSchedule = function(id) {
DeleteSchedule({
scope: scope,
id: id,
callback: 'SchedulesRefresh'
});
};
scope.editSchedule = function(id) {
if ($state.includes('inventoryManage')){
$state.go('inventoryManage.schedules.edit', {schedule_id: id});
}
else if ($state.current.name === 'jobs'){
// id === schedule object in this case
var stateDictionary = {
// type: stateName
job: 'jobTemplateSchedules.edit',
system_job: 'managementJobSchedules.edit',
project_update: 'projectSchedules.edit',
};
$state.go(stateDictionary[id.type], {schedule_id: id.id, id: id.summary_fields.unified_job_template.id});
}
else{
var base = $state.current.name.split(".")[0];
$state.go(base + ".edit", {schedule_id: id});
}
};
scope.addSchedule = function() {
if ($state.includes('inventoryManage')){
scope.schedule_url = parent_scope.current_url.split('?')[0];
ParamPass.set(scope.schedule_url);
$state.go('inventoryManage.schedules.add');
}
else{
var base = $state.current.name.split(".")[0];
ParamPass.set(scope.schedule_url);
$state.go(base + ".add");
}
};
scope.refreshSchedules = function() {
if (base === 'jobs') {
parent_scope.refreshJobs();
}
else {
scope.search(iterator);
}
};
if (scope.removeSchedulesRefresh) {
scope.removeSchedulesRefresh();
}
scope.removeSchedulesRefresh = scope.$on('SchedulesRefresh', function() {
scope.search(iterator);
});
};
}])
.factory('SchedulesListInit', [ function() {
return function(params) {
var scope = params.scope,
list = params.list,
choices = params.choices;
scope[list.name].forEach(function(item, item_idx) {
var fld,
field,
itm = scope[list.name][item_idx],
job = item.summary_fields.unified_job_template;
itm.enabled = (itm.enabled) ? true : false;
if (itm.enabled) {
itm.play_tip = 'Schedule is active. Click to stop.';
itm.status = 'active';
itm.status_tip = 'Schedule is active. Click to stop.';
}
else {
itm.play_tip = 'Schedule is stopped. Click to activate.';
itm.status = 'stopped';
itm.status_tip = 'Schedule is stopped. Click to activate.';
}
itm.nameTip = item.name;
// include the word schedule if the schedule name does not include the word schedule
if (item.name.indexOf("schedule") === -1 && item.name.indexOf("Schedule") === -1) {
itm.nameTip += " schedule";
}
itm.nameTip += " for ";
if (job.name.indexOf("job") === -1 && job.name.indexOf("Job") === -1) {
itm.nameTip += "job ";
}
itm.nameTip += job.name;
itm.nameTip += ". Click to edit schedule.";
// Copy summary_field values
for (field in list.fields) {
fld = list.fields[field];
if (fld.sourceModel) {
if (itm.summary_fields[fld.sourceModel]) {
itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField];
}
}
}
// Set the item type label
if (list.fields.type) {
choices.every(function(choice) {
if (choice.value === item.type) {
itm.type_label = choice.label;
return false;
}
return true;
});
}
});
};
}])
/**
*
* Called from a controller to setup the scope for a schedules list
*
*/
.factory('LoadSchedulesScope', ['$compile', '$location', '$stateParams','SearchInit', 'PaginateInit', 'generateList', 'SchedulesControllerInit',
'SchedulesListInit',
function($compile, $location, $stateParams, SearchInit, PaginateInit, GenerateList, SchedulesControllerInit, SchedulesListInit) {
return function(params) {
var parent_scope = params.parent_scope,
scope = params.scope,
list = params.list,
id = params.id,
url = params.url,
searchSize = params.searchSize,
pageSize = params.pageSize || 10,
spinner = (params.spinner === undefined) ? true : params.spinner;
GenerateList.inject(list, {
mode: 'edit',
id: id,
scope: scope,
searchSize: (searchSize) ? searchSize : 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
showSearch: true,
title: true,
});
SearchInit({
scope: scope,
set: list.name,
list: list,
url: url
});
PaginateInit({
scope: scope,
list: list,
url: url,
pageSize: pageSize
});
scope.iterator = list.iterator;
if (scope.removePostRefresh) {
scope.removePostRefresh();
}
scope.$on('PostRefresh', function(){
SchedulesControllerInit({
scope: scope,
parent_scope: parent_scope,
list: list
});
SchedulesListInit({
scope: scope,
list: list,
choices: parent_scope.type_choices
});
parent_scope.$emit('listLoaded');
});
if ($stateParams.id__int) {
scope[list.iterator + 'SearchField'] = 'id';
scope[list.iterator + 'SearchValue'] = $stateParams.id__int;
scope[list.iterator + 'SearchFieldLabel'] = 'ID';
}
scope.search(list.iterator, null, null, null, null, spinner);
};
}]);

View File

@ -17,12 +17,12 @@ import listGenerator from '../shared/list-generator/main';
export default
angular.module('InventoryHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name,
'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'SearchHelper', 'VariablesHelper',
'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'VariablesHelper',
])
.factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList',
.factory('SaveInventory', ['InventoryForm', 'Rest', 'Alert', 'ProcessErrors', 'OrganizationList',
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON',
function (InventoryForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, Wait,
function (InventoryForm, Rest, Alert, ProcessErrors, OrganizationList, GetBasePath, ParseTypeChange, Wait,
ToJSON) {
return function (params) {

View File

@ -1,59 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:refresh-related
* @description
* discussion
* RefreshRelatedHelper
*
* Used to refresh a related set whenever pagination or filter options change.
*
* RefreshRelated({
* scope: <current scope>,
* set: <model>,
* iterator: <model name in singular form (i.e. organization),
* url: <the api url to call>
* });
*
*/
export default
angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities', 'PaginationHelpers'])
.factory('RefreshRelated', ['ProcessErrors', 'Rest', 'Wait', 'PageRangeSetup',
function (ProcessErrors, Rest, Wait, PageRangeSetup) {
return function (params) {
var scope = params.scope,
set = params.set,
iterator = params.iterator,
url = params.url;
Rest.setUrl(url);
Rest.get()
.success(function (data) {
PageRangeSetup({
scope: scope,
count: data.count,
next: data.next,
previous: data.previous,
iterator: iterator
});
scope[set] = data.results;
scope[iterator + 'Loading'] = false;
scope[iterator + 'HoldInput'] = false;
Wait('stop');
scope.$emit('related' + set);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status });
});
};
}
]);

View File

@ -1,104 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:refresh
* @description
* RefreshHelper
*
* Used to refresh a related set whenever pagination or filter options change.
*
* RefreshRelated({
* scope: <current scope>,
* set: <model>,
* iterator: <model name in singular form (i.e. organization),
* url: <the api url to call>
* });
*
*/
export default
angular.module('RefreshHelper', ['RestServices', 'Utilities', 'PaginationHelpers'])
.factory('Refresh', ['$rootScope', '$location', 'ProcessErrors', 'Rest', 'Wait', 'Empty', 'PageRangeSetup', 'pagination', function ($rootScope, $location, ProcessErrors, Rest, Wait, Empty, PageRangeSetup, pagination) {
return function (params) {
var scope = params.scope,
set = params.set,
iterator = params.iterator,
deferWaitStop = params.deferWaitStop;
var getPage = function(url) {
scope.current_url = url;
Rest.setUrl(url);
Rest.get()
.success(function (data) {
var i, modifier;
PageRangeSetup({
scope: scope,
count: data.count,
next: data.next,
previous: data.previous,
iterator: iterator
});
for (i = 1; i <= 3; i++) {
modifier = (i === 1) ? '' : i;
scope[iterator + 'HoldInput' + modifier] = false;
}
scope[set] = data.results;
scope[iterator + 'Loading'] = false;
scope[iterator + 'HidePaginator'] = false;
if (!deferWaitStop) {
Wait('stop');
}
scope.$emit('PostRefresh', set);
})
.error(function (data, status) {
scope[iterator + 'HoldInput'] = false;
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to retrieve ' + set + '. GET returned status: ' + status
});
});
};
var id, restUrl, pageSize;
// if you're editing an object, make sure you're on the right
// page to display the element you are editing
if (scope.addedItem) {
id = scope.addedItem + "";
delete scope.addedItem;
$rootScope.rowBeingEdited = id;
$rootScope.listBeingEdited = set;
$rootScope.addedAnItem = true;
restUrl = params.url.split("?")[0];
pageSize = scope[iterator + '_page_size'];
pagination.getInitialPageForList(id, restUrl, pageSize)
.then(function (currentPage) {
scope.getPage(currentPage, set, iterator);
});
} else if ($location.$$url.split("/")[1] === params.set && $location.$$url.split("/")[2] && $location.$$url.split("/")[2] !== "add" && !scope.getNewPage) {
id = $location.$$url.split("/")[2];
restUrl = params.url.split("?")[0];
pageSize = scope[iterator + '_page_size'];
pagination.getInitialPageForList(id, restUrl, pageSize)
.then(function (currentPage) {
scope[iterator + '_page'] = currentPage;
params.url = params.url + "&page=" + currentPage;
getPage(params.url);
});
} else {
getPage(params.url);
}
};
}
]);

View File

@ -1,295 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:related-search
* @description
* RelatedSearchHelper
*
* All the parts for controlling the search widget on
* related collections.
*
* SearchInit({
* scope: <scope>,
* relatedSets: <array of related collections {model_name, url, iterator}>,
* form: <form object used by FormGenerator>
* });
*
*
*/
export default
angular.module('RelatedSearchHelper', ['RestServices', 'Utilities', 'RefreshRelatedHelper'])
.factory('RelatedSearchInit', ['$timeout', 'Alert', 'Rest', 'RefreshRelated', 'Wait', 'Empty',
function ($timeout, Alert, Rest, RefreshRelated, Wait, Empty) {
return function (params) {
var scope = params.scope,
relatedSets = params.relatedSets,
form = params.form, f;
// add 'selected' class to the selected li element
function setSelectedItem(iterator, label) {
$('#' + iterator + 'SearchDropdown' + ' li').each(function() {
$(this).removeClass('selected');
var link = $(this).find('a');
if (label === link.text()) {
$(this).addClass('selected');
}
});
}
// Set default values
function setDefaults(inIterator) {
var iterator, f, fld, set;
for (set in form.related) {
if (form.related[set].type !== 'tree' && (inIterator === undefined || inIterator === form.related[set].iterator)) {
iterator = form.related[set].iterator;
for (fld in form.related[set].fields) {
if (form.related[set].fields[fld].key) {
if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) {
scope[iterator + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label;
}
break;
}
}
if (Empty(scope[iterator + 'SearchField'])) {
// A field marked as key may not be 'searchable'. Find the first searchable field.
for (fld in form.related[set].fields) {
if (form.related[set].fields[fld].searchable === undefined || form.related[set].fields[fld].searchable === true) {
scope[iterator + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = form.related[set].fields[fld].label;
break;
}
}
}
scope[iterator + 'SortOrder'] = null;
scope[iterator + 'SearchType'] = 'icontains';
scope[iterator + 'SearchTypeLabel'] = 'Contains';
scope[iterator + 'SearchValue'] = null;
scope[iterator + 'SelectShow'] = false;
//scope[iterator + 'HideSearchType'] = false;
scope[iterator + 'ShowStartBtn'] = true;
scope[iterator + 'HideAllStartBtn'] = false;
f = scope[iterator + 'SearchField'];
if (form.related[set].fields[f].searchType &&
(form.related[set].fields[f].searchType === 'boolean' || form.related[set].fields[f].searchType === 'select')) {
scope[iterator + 'SelectShow'] = true;
scope[iterator + 'SearchSelectOpts'] = form.related[set].fields[f].searchOptions;
}
if (form.related[set].fields[f].searchType && form.related[set].fields[f].searchType === 'gtzero') {
scope[iterator + "InputHide"] = true;
}
setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel']);
}
}
}
setDefaults();
scope.resetSearch = function (iterator) {
setDefaults(iterator);
scope.search(iterator);
};
// Functions to handle search widget changes
scope.setSearchField = function (iterator, fld, label) {
var f, related;
for (related in form.related) {
if (form.related[related].iterator === iterator) {
f = form.related[related].fields[fld];
}
}
scope[iterator + 'SearchFieldLabel'] = label;
scope[iterator + 'SearchField'] = fld;
scope[iterator + 'SearchValue'] = '';
scope[iterator + 'SelectShow'] = false;
//scope[iterator + 'HideSearchType'] = false;
scope[iterator + 'InputHide'] = false;
scope[iterator + 'ShowStartBtn'] = true;
if (f.searchType !== undefined && f.searchType === 'gtzero') {
scope[iterator + "InputHide"] = true;
scope[iterator + 'ShowStartBtn'] = false;
}
if (f.searchType !== undefined && (f.searchType === 'boolean' || f.searchType === 'select')) {
scope[iterator + 'SelectShow'] = true;
scope[iterator + 'SearchSelectOpts'] = f.searchOptions;
}
if (f.searchType !== undefined && f.searchType === 'int') {
//scope[iterator + 'HideSearchType'] = true;
scope[iterator + 'SearchType'] = 'int';
}
setSelectedItem(iterator, label);
scope.search(iterator);
};
scope.setSearchType = function (model, type, label) {
scope[model + 'SearchTypeLabel'] = label;
scope[model + 'SearchType'] = type;
scope.search(model);
};
scope.startSearch = function (e, iterator) {
// If use clicks enter while on input field, start the search
if (e.keyCode === 13) {
scope.search(iterator);
}
};
scope.search = function (iterator) {
//scope[iterator + 'SearchSpin'] = true;
Wait('start');
scope[iterator + 'Loading'] = false;
scope[iterator + 'HoldInput'] = true;
if (scope[iterator + 'SearchValue']) {
// User typed a value in input field
scope[iterator + 'ShowStartBtn'] = false;
}
if(scope[iterator + 'SearchValue'] && scope[iterator + 'SearchValue'] !== '') {
scope[iterator + '_active_search'] = true;
}
else {
scope[iterator + '_active_search'] = false;
}
if (iterator === 'host') {
if (scope.hostSearchField === 'has_active_failures') {
if (scope.hostSearchSelectValue && scope.hostSearchSelectValue.value === 1) {
scope.hostFailureFilter = true;
} else {
scope.hostFailureFilter = false;
}
}
}
var fld, key, set, url, sort_order;
for (key in relatedSets) {
if (relatedSets[key].iterator === iterator) {
set = key;
url = relatedSets[key].url;
for (fld in form.related[key].fields) {
if (form.related[key].fields[fld].key) {
if (form.related[key].fields[fld].desc) {
sort_order = '-' + fld;
} else {
sort_order = fld;
}
}
}
break;
}
}
sort_order = (scope[iterator + 'SortOrder'] === null) ? sort_order : scope[iterator + 'SortOrder'];
f = form.related[set].fields[scope[iterator + 'SearchField']];
if ((scope[iterator + 'SelectShow'] === false && !Empty(scope[iterator + 'SearchValue'])) ||
(scope[iterator + 'SelectShow'] && scope[iterator + 'SearchSelectValue']) ||
(f.searchType && f.searchType === 'gtzero')) {
if (f.sourceModel) {
// handle fields whose source is a related model e.g. inventories.organization
scope[iterator + 'SearchParams'] = f.sourceModel + '__' + f.sourceField + '__';
} else if (f.searchField) {
scope[iterator + 'SearchParams'] = f.searchField + '__';
} else {
scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__';
}
if (f.searchType && (f.searchType === 'int' || f.searchType === 'boolean')) {
scope[iterator + 'SearchParams'] += 'int=';
} else if (f.searchType && f.searchType === 'gtzero') {
scope[iterator + 'SearchParams'] += 'gt=0';
} else {
scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType'] + '=';
}
if (f.searchType && (f.searchType === 'boolean' || f.searchType === 'select')) {
scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue'].value;
} else if (f.searchType === undefined || f.searchType === 'gtzero') {
scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue']);
}
scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + encodeURI(sort_order) : '';
} else {
scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + encodeURI(sort_order) : '';
}
scope[iterator + '_page'] = 1;
url += (url.match(/\/$/)) ? '?' : '&';
url += scope[iterator + 'SearchParams'];
url += (scope[iterator + '_page_size']) ? '&page_size=' + scope[iterator + '_page_size'] : "";
if (scope[iterator + 'SearchFilters']){
url += _.reduce(scope[iterator+'SearchFilters'], (result, filter) => result + '&' + filter.url, '');
}
RefreshRelated({ scope: scope, set: set, iterator: iterator, url: url });
};
scope.$on("refreshList", function(e, iterator) {
scope.search(iterator);
});
scope.sort = function (iterator, fld) {
var sort_order, icon, direction, set;
// reset sort icons back to 'icon-sort' on all columns
// except the one clicked
$('.' + iterator + ' .list-header').each(function () {
if ($(this).attr('id') !== iterator + '-' + fld + '-header') {
var icon = $(this).find('i');
icon.attr('class', 'fa fa-sort');
}
});
// Toggle the icon for the clicked column
// and set the sort direction
icon = $('#' + iterator + '-' + fld + '-header i');
direction = '';
if (icon.hasClass('fa-sort')) {
icon.removeClass('fa-sort');
icon.addClass('fa-sort-up');
} else if (icon.hasClass('fa-sort-up')) {
icon.removeClass('fa-sort-up');
icon.addClass('fa-sort-down');
direction = '-';
} else if (icon.hasClass('fa-sort-down')) {
icon.removeClass('fa-sort-down');
icon.addClass('fa-sort-up');
}
// Set the sorder order value and call the API to refresh the list with the new order
for (set in form.related) {
if (form.related[set].iterator === iterator) {
if (form.related[set].fields[fld].sourceModel) {
sort_order = direction + form.related[set].fields[fld].sourceModel + '__' +
form.related[set].fields[fld].sourceField;
} else {
sort_order = direction + fld;
}
}
}
scope[iterator + 'SortOrder'] = sort_order;
scope.search(iterator);
};
};
}
]);

View File

@ -1,536 +0,0 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:search
* @description
* SearchHelper
*
* All the parts for controlling the search widget on
* related collections.
*
* SearchInit({
* scope: <scope>,
* set: <model name (i.e. organizations) used in ng-repeat>
* url: <default api url used to load data>
* list: <list object used by ListGenerator>
* });
*
*/
export default
angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
.factory('SearchInit', ['Alert', 'Rest', 'Refresh', '$location', 'GetBasePath', 'Empty', '$timeout', 'Wait', 'Store',
function (Alert, Rest, Refresh, $location, GetBasePath, Empty, $timeout, Wait, Store) {
return function (params) {
var scope = params.scope,
set = params.set,
defaultUrl = params.url,
list = params.list,
iterator = (params.iterator) ? params.iterator : list.iterator,
setWidgets = (params.setWidgets === false) ? false : true,
sort_order = params.sort_order || '',
widgets, i, modifier;
// add 'selected' class to the selected li element
function setSelectedItem(iterator, label, modifier) {
// add 'selected' class to the selected li element
$('#' + iterator + 'SearchDropdown' + modifier + ' li').each(function() {
$(this).removeClass('selected');
var link = $(this).find('a');
if (label === link.text()) {
$(this).addClass('selected');
}
});
}
function setDefaults(widget) {
// Set default values
var f, fld, fka, modifier;
modifier = (widget === undefined || widget === 1) ? '' : widget;
scope[iterator + 'SearchField' + modifier] = '';
scope[iterator + 'SearchFieldLabel' + modifier] = '';
for (fld in list.fields) {
if (list.fields[fld].searchWidget === undefined && widget === 1 ||
list.fields[fld].searchWidget === widget) {
if (list.fields[fld].key) {
if (list.fields[fld].sourceModel) {
fka = list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField;
sort_order = (list.fields[fld].desc) ? '-' + fka : fka;
} else {
sort_order = (list.fields[fld].desc) ? '-' + fld : fld;
}
if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) {
scope[iterator + 'SearchField' + modifier] = fld;
scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label;
}
break;
}
}
}
// Default the search field to 'defaultSearchField', if one exists
for (fld in list.fields) {
if (list.fields[fld].searchWidget === undefined && widget === 1 ||
list.fields[fld].searchWidget === widget) {
if (list.fields[fld].defaultSearchField) {
scope[iterator + 'SearchField' + modifier] = fld;
scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label;
}
}
}
// A field marked as key may not be 'searchable', and there might not be a 'defaultSearchField',
// so find the first searchable field.
if (Empty(scope[iterator + 'SearchField' + modifier])) {
for (fld in list.fields) {
if (list.fields[fld].searchWidget === undefined && widget === 1 ||
list.fields[fld].searchWidget === widget) {
if (list.fields[fld].searchable === undefined || list.fields[fld].searchable === true) {
scope[iterator + 'SearchField' + modifier] = fld;
scope[iterator + 'SearchFieldLabel' + modifier] = list.fields[fld].label;
break;
}
}
}
}
scope[iterator + 'SearchType' + modifier] = 'icontains';
scope[iterator + 'SearchTypeLabel' + modifier] = 'Contains';
scope[iterator + 'SearchParams' + modifier] = '';
scope[iterator + 'SearchValue' + modifier] = '';
scope[iterator + 'SelectShow' + modifier] = false; // show/hide the Select
scope[iterator + 'HideSearchType' + modifier] = false;
scope[iterator + 'InputDisable' + modifier] = false;
scope[iterator + 'ExtraParms' + modifier] = '';
scope[iterator + 'ShowStartBtn' + modifier] = true;
scope[iterator + 'HideAllStartBtn' + modifier] = false;
if (list.fields[scope[iterator + 'SearchField' + modifier]] &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) {
if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) {
// if set to a scope variable
scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder];
} else {
// Set to a string value in the list definition
scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder;
}
} else {
// Default value
scope[iterator + 'SearchPlaceholder' + modifier] = 'Search';
}
scope[iterator + 'InputDisable' + modifier] =
(list.fields[scope[iterator + 'SearchField' + modifier]] &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchObject === 'all') ? true : false;
f = scope[iterator + 'SearchField' + modifier];
if (list.fields[f]) {
if (list.fields[f].searchType && (list.fields[f].searchType === 'boolean' ||
list.fields[f].searchType === 'select')) {
scope[iterator + 'SelectShow' + modifier] = true;
scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[f].searchOptions;
}
if (list.fields[f].searchType && list.fields[f].searchType === 'int') {
scope[iterator + 'HideSearchType' + modifier] = true;
}
if (list.fields[f].searchType && list.fields[f].searchType === 'gtzero') {
scope[iterator + 'InputHide' + modifier] = true;
}
}
setSelectedItem(iterator, scope[iterator + 'SearchFieldLabel' + modifier], modifier);
}
if (setWidgets) {
// Set default values for each search widget on the page
widgets = (list.searchWidgets) ? list.searchWidgets : 1;
for (i = 1; i <= widgets; i++) {
modifier = (i === 1) ? '' : i;
if ($('#search-widget-container' + modifier)) {
setDefaults(i);
}
}
}
scope[iterator + '_current_search_params'] = {
set: set,
defaultUrl: defaultUrl,
list: list,
iterator: iterator,
sort_order: sort_order
};
Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']);
Store('CurrentSearchParams', scope[iterator + '_current_search_params']); // Keeping this around for activity stream
// Functions to handle search widget changes
scope.setSearchField = function (iterator, fld, label, widget) {
var modifier = (widget === undefined || widget === 1) ? '' : widget;
scope[iterator + 'SearchFieldLabel' + modifier] = label;
scope[iterator + 'SearchField' + modifier] = fld;
scope[iterator + 'SearchValue' + modifier] = '';
scope[iterator + 'SelectShow' + modifier] = false;
scope[iterator + 'HideSearchType' + modifier] = false;
scope[iterator + 'InputHide' + modifier] = false;
scope[iterator + 'SearchType' + modifier] = 'icontains';
scope[iterator + 'InputDisable' + modifier] = (list.fields[fld].searchObject === 'all') ? true : false;
scope[iterator + 'ShowStartBtn' + modifier] = true;
if (list.fields[scope[iterator + 'SearchField' + modifier]] &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder) {
if (scope[list.fields[scope[iterator + 'SearchField' + modifier]].searchPlaceholder]) {
// if set to a scope variable
scope[iterator + 'SearchPlaceholder' + modifier] = scope[list.fields[scope[iterator + 'SearchField' +
modifier]].searchPlaceholder];
} else {
// Set to a string value in the list definition
scope[iterator + 'SearchPlaceholder' + modifier] = list.fields[scope[iterator + 'SearchField' +
modifier]].searchPlaceholder;
}
} else {
// Default value
scope[iterator + 'SearchPlaceholder' + modifier] = 'Search';
}
if (list.fields[fld].searchType && list.fields[fld].searchType === 'gtzero') {
scope[iterator + "InputDisable" + modifier] = true;
scope[iterator + 'ShowStartBtn' + modifier] = false;
scope.search(iterator);
} else if (list.fields[fld].searchSingleValue) {
// Query a specific attribute for one specific value
// searchSingleValue: true
// searchType: 'boolean|int|etc.'
// searchValue: < value to match for boolean use 'true'|'false' >
scope[iterator + 'InputDisable' + modifier] = true;
scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue;
// For boolean type, SearchValue must be an object
if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'true') {
scope[iterator + "SearchSelectValue" + modifier] = {
value: 1
};
} else if (list.fields[fld].searchType === 'boolean' && list.fields[fld].searchValue === 'false') {
scope[iterator + "SearchSelectValue" + modifier] = {
value: 0
};
} else {
scope[iterator + "SearchSelectValue" + modifier] = {
value: list.fields[fld].searchValue
};
}
scope[iterator + 'ShowStartBtn' + modifier] = false;
} else if (list.fields[fld].searchType === 'in') {
scope[iterator + "SearchType" + modifier] = 'in';
scope[iterator + "SearchValue" + modifier] = list.fields[fld].searchValue;
scope[iterator + "InputDisable" + modifier] = true;
scope[iterator + 'ShowStartBtn' + modifier] = false;
} else if (list.fields[fld].searchType && (list.fields[fld].searchType === 'boolean' ||
list.fields[fld].searchType === 'select' || list.fields[fld].searchType === 'select_or')) {
scope[iterator + 'SelectShow' + modifier] = true;
scope[iterator + 'SearchSelectOpts' + modifier] = list.fields[fld].searchOptions;
scope[iterator + 'SearchType' + modifier] = '';
} else if (list.fields[fld].searchType && list.fields[fld].searchType === 'int') {
//scope[iterator + 'HideSearchType' + modifier] = true;
scope[iterator + 'SearchType' + modifier] = 'int';
} else if (list.fields[fld].searchType && list.fields[fld].searchType === 'isnull') {
scope[iterator + 'SearchType' + modifier] = 'isnull';
scope[iterator + 'InputDisable' + modifier] = true;
scope[iterator + 'SearchValue' + modifier] = 'true';
scope[iterator + 'ShowStartBtn' + modifier] = false;
}
setSelectedItem(iterator, label, modifier);
scope.search(iterator);
};
scope.resetSearch = function (iterator) {
// Respdond to click of reset button
var i,
widgets = (list.searchWidgets) ? list.searchWidgets : 1;
for (i = 1; i <= widgets; i++) {
// Clear each search widget
setDefaults(i);
}
// Force removal of search keys from the URL
window.location = '/#' + $location.path();
scope.search(iterator);
};
if (scope.removeDoSearch) {
scope.removeDoSearch();
}
scope.removeDoSearch = scope.$on('doSearch', function (e, iterator, page, load, calcOnly, deferWaitStop) {
//
// Execute the search
//
var url = (calcOnly) ? '' : defaultUrl,
connect;
if (!calcOnly) {
scope[iterator + 'Loading'] = (load === undefined || load === true) ? true : false;
scope[iterator + 'Page'] = (page) ? parseInt(page) - 1 : 0;
}
//finalize and execute the query
if (scope[iterator + 'SearchParams']) {
if (/\/$/.test(url)) {
url += '?' + scope[iterator + 'SearchParams'];
} else {
url += '&' + scope[iterator + 'SearchParams'];
}
}
connect = (/\/$/.test(url)) ? '?' : '&';
url += (scope[iterator + '_page_size']) ? connect + 'page_size=' + scope[iterator + '_page_size'] : "";
if (page) {
connect = (/\/$/.test(url)) ? '?' : '&';
url += connect + 'page=' + page;
}
if (scope[iterator + 'ExtraParms']) {
connect = (/\/$/.test(url)) ? '?' : '&';
url += connect + scope[iterator + 'ExtraParms'];
}
url = url.replace(/\&\&/g, '&').replace(/\?\&/,'?');
if (scope[iterator + 'SearchFilters']){
url += _.reduce(scope[iterator+'SearchFilters'], (result, filter) => result + '&' + filter.url, '');
}
if (calcOnly) {
scope.$emit('searchParamsReady', url);
}
else if (defaultUrl && !/undefined/.test(url)) {
Refresh({
scope: scope,
set: set,
iterator: iterator,
url: url,
deferWaitStop: deferWaitStop
});
}
e.stopPropagation();
});
if (scope.removePrepareSearch) {
scope.removePrepareSearch();
}
scope.removePrepareSearch = scope.$on('prepareSearch', function (e, iterator, page, load, calcOnly, deferWaitStop, spinner) {
// Start the search spinner
if (spinner) {
Wait('start');
}
e.stopPropagation();
scope.$emit('prepareSearch2', iterator, page, load, calcOnly, deferWaitStop);
});
if (scope.removePrepareSearch2) {
scope.removePrepareSearch2();
}
scope.removePrepareSearch2 = scope.$on('prepareSearch2', function (e, iterator, page, load, calcOnly, deferWaitStop) {
// Continue building the search by examining the remaining search widgets. If we're looking at activity_stream,
// there's more than one.
var i, modifier,
widgets = (list.searchWidgets) ? list.searchWidgets : 1;
// Initialize SearchParams as an empty string if it's not defined. If we don't do this and SearchParams === undefined
// then 'undefined' will sneak into the string as we are concatenating and the request will never get sent since we
// regex search for 'undefined' in the doSearch section of this process.
scope[iterator + 'SearchParams'] = (!scope[iterator + 'SearchParams'] || scope[iterator + 'SearchParams'] === undefined) ? '' : scope[iterator + 'SearchParams'];
for (i = 1; i <= widgets; i++) {
modifier = (i === 1) ? '' : i;
scope[iterator + 'HoldInput' + modifier] = true;
if ($('#search-widget-container' + modifier) &&
list.fields[scope[iterator + 'SearchField' + modifier]] && !list.fields[scope[iterator + 'SearchField' + modifier]].searchObject) {
if (scope[iterator + 'SearchValue' + modifier]) {
// if user typed a value in the input box, show the reset link
scope[iterator + 'ShowStartBtn' + modifier] = false;
} else {
scope[iterator + 'ShowStartBtn' + modifier] = true;
}
if ((!scope[iterator + 'SelectShow' + modifier] && !Empty(scope[iterator + 'SearchValue' + modifier])) ||
(scope[iterator + 'SelectShow' + modifier] && scope[iterator + 'SearchSelectValue' + modifier]) ||
(list.fields[scope[iterator + 'SearchField' + modifier]] &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero')) {
scope[iterator + '_active_search'] = true;
if (list.fields[scope[iterator + 'SearchField' + modifier]].searchField) {
scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].searchField + '__';
} else if (list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel) {
// handle fields whose source is a related model e.g. inventories.organization
scope[iterator + 'SearchParams'] += '&' + list.fields[scope[iterator + 'SearchField' + modifier]].sourceModel + '__' +
list.fields[scope[iterator + 'SearchField' + modifier]].sourceField + '__';
} else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' &&
Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) {
scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__';
} else if ( list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select' &&
!Empty(scope[iterator + 'SearchSelectValue' + modifier].value) ) {
scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier];
} else {
scope[iterator + 'SearchParams'] += '&' + scope[iterator + 'SearchField' + modifier] + '__';
}
if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType &&
(list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'int' ||
list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean')) {
scope[iterator + 'SearchParams'] += 'int=';
} else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'gtzero') {
scope[iterator + 'SearchParams'] += 'gt=0';
} else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'isnull') {
scope[iterator + 'SearchParams'] += 'isnull=';
} else if ( (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select') &&
Empty(scope[iterator + 'SearchSelectValue' + modifier].value) && !/\_\_$/.test(scope[iterator + 'SearchParams']) ) {
scope[iterator + 'SearchParams'] += '=iexact=';
} else if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'in') {
if (!/\_\_$/.test(scope[iterator + 'SearchParams'])) {
scope[iterator + 'SearchParams'] += '__';
}
scope[iterator + 'SearchParams'] += 'in=';
} else if (/\_\_$/.test(scope[iterator + 'SearchParams'])) {
scope[iterator + 'SearchParams'] += 'icontains=';
} else {
scope[iterator + 'SearchParams'] += scope[iterator + 'SearchType' + modifier] + '=';
}
if (list.fields[scope[iterator + 'SearchField' + modifier]].searchType &&
(list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'boolean' ||
list.fields[scope[iterator + 'SearchField' + modifier]].searchType === 'select')) {
scope[iterator + 'SearchParams'] += scope[iterator + 'SearchSelectValue' + modifier].value;
} else {
if ((!list.fields[scope[iterator + 'SearchField' + modifier]].searchType) ||
(list.fields[scope[iterator + 'SearchField' + modifier]].searchType &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'or' &&
list.fields[scope[iterator + 'SearchField' + modifier]].searchType !== 'gtzero')) {
scope[iterator + 'SearchParams'] += encodeURI(scope[iterator + 'SearchValue' + modifier]);
}
}
}
}
}
if ((iterator === 'inventory' && scope.inventoryFailureFilter) ||
(iterator === 'host' && scope.hostFailureFilter)) {
//Things that bypass the search widget. Should go back and add a second widget possibly on
//inventory pages and eliminate this
scope[iterator + 'SearchParams'] += '&has_active_failures=true';
}
if (sort_order) {
scope[iterator + 'SearchParams'] = 'order_by=' + encodeURI(sort_order);
}
e.stopPropagation();
scope.$emit('doSearch', iterator, page, load, calcOnly, deferWaitStop);
});
scope.startSearch = function (e, iterator) {
// If use clicks enter while on input field, start the search
if (e.keyCode === 13) {
scope.search(iterator);
}
};
/**
* Initiate a searh.
*
* @iterator: required, list.iterator value
* @Page: optional. Added to accomodate back function on Job Events detail.
* @Load: optional, set to false if 'Loading' message not desired
* @calcOnly: optional, set to true when you want to calc or figure out search params without executing the search
* @deferWaitStop: optional, when true refresh.js will NOT issue Wait('stop'), thus leaving the spinner. Caller is then
* responsible for stopping the spinner post refresh.
* @spinner: optional, if false, don't show the spinner.
*/
scope.search = function (iterator, page, load, calcOnly, deferWaitStop, spinner) {
page = page || null;
load = (load || !scope[set] || scope[set].length === 0) ? true : false;
calcOnly = (calcOnly) ? true : false;
deferWaitStop = (deferWaitStop) ? true : false;
spinner = (spinner === undefined) ? true : spinner;
if (load) {
scope[set] = []; //clear the list array to make sure 'Loading' is the only thing visible on the list
}
if(scope[iterator + 'SearchFilters'] && scope[iterator + 'SearchFilters'].length > 0) {
scope[iterator + '_active_search'] = true;
}
else {
scope[iterator + '_active_search'] = false;
}
scope.$emit('prepareSearch', iterator, page, load, calcOnly, deferWaitStop, spinner);
};
scope.sort = function (iterator, fld) {
// resets any existing order_by parameters in $scope.current_url;
var resetOrderBy = function(){
var url = _.filter(scope.current_url.split('&'), (o) => !o.includes('order_by'));
scope.current_url = url.join('&');
};
resetOrderBy();
// Reset sort icons back to 'icon-sort' on all columns
// except the one clicked.
$('.list-header').each(function () {
if ($(this).attr('id') !== iterator + '-' + fld + '-header') {
var icon = $(this).find('i');
icon.attr('class', 'fa fa-sort');
}
});
// Toggle the icon for the clicked column
// and set the sort direction
var icon = $('#' + iterator + '-' + fld + '-header i'),
direction = '';
if (icon.hasClass('fa-sort')) {
icon.removeClass('fa-sort');
icon.addClass('fa-sort-up');
} else if (icon.hasClass('fa-sort-up')) {
icon.removeClass('fa-sort-up');
icon.addClass('fa-sort-down');
direction = '-';
} else if (icon.hasClass('fa-sort-down')) {
icon.removeClass('fa-sort-down');
icon.addClass('fa-sort-up');
}
// Set the sorder order value and call the API to refresh the list with the new order
if (list.fields[fld].searchField) {
sort_order = direction + list.fields[fld].searchField;
} else if (list.fields[fld].sortField) {
sort_order = direction + list.fields[fld].sortField;
} else {
if (list.fields[fld].sourceModel) {
sort_order = direction + list.fields[fld].sourceModel + '__' + list.fields[fld].sourceField;
} else {
sort_order = direction + fld;
}
}
scope[list.iterator + '_current_search_params'].sort_order = sort_order;
Store(iterator + '_current_search_params', scope[iterator + '_current_search_params']);
scope.search(list.iterator);
};
// Call after modal dialogs to remove any lingering callbacks
scope.searchCleanup = function () {
scope.removeDoSearch();
scope.removePrepareSearch();
scope.removePrepareSearch2();
};
};
}
]);

View File

@ -3,7 +3,7 @@
*
* All Rights Reserved
*************************************************/
/**
* @ngdoc function
* @name helpers.function:teams
@ -15,8 +15,7 @@
import listGenerator from '../shared/list-generator/main';
export default
angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', 'SearchHelper',
'PaginationHelpers', listGenerator.name
angular.module('TeamHelper', ['RestServices', 'Utilities', 'OrganizationListDefinition', listGenerator.name
])
.factory('SetTeamListeners', ['Alert', 'Rest',
function (Alert, Rest) {
@ -40,7 +39,10 @@ export default
}
}
}
scope[iterator + 'SearchSpin'] = false;
// @issue: OLD SEARCH
// scope[iterator + 'SearchSpin'] = false;
scope[set] = results;
}
});
@ -74,8 +76,8 @@ export default
}
])
.factory('TeamLookUpOrganizationInit', ['Alert', 'Rest', 'OrganizationList', 'generateList', 'SearchInit', 'PaginateInit',
function (Alert, Rest, OrganizationList, GenerateList, SearchInit, PaginateInit) {
.factory('TeamLookUpOrganizationInit', ['Alert', 'Rest', 'OrganizationList', 'generateList',
function (Alert, Rest, OrganizationList, GenerateList) {
return function (params) {
var scope = params.scope;
@ -115,19 +117,21 @@ export default
}
};
SearchInit({
scope: listScope,
set: list.name,
list: list,
url: defaultUrl
});
PaginateInit({
scope: listScope,
list: list,
url: defaultUrl,
mode: 'lookup'
});
scope.search(list.iterator);
// @issue: OLD SEARCH
// SearchInit({
// scope: listScope,
// set: list.name,
// list: list,
// url: defaultUrl
// });
// PaginateInit({
// scope: listScope,
// list: list,
// url: defaultUrl,
// mode: 'lookup'
// });
// scope.search(list.iterator);
listScope.toggle_organization(scope.organization);
};
};

View File

@ -11,9 +11,8 @@
*/
function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
$stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
$stateParams, GenerateForm, InventoryForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
$state) {
Rest.setUrl(GetBasePath('inventory'));
@ -29,36 +28,28 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = InventoryForm(),
generator = GenerateForm;
form = InventoryForm();
form.formLabelSize = null;
form.formFieldSize = null;
init();
generator.inject(form, { mode: 'add', related: false, scope: $scope });
function init() {
form.formLabelSize = null;
form.formFieldSize = null;
generator.reset();
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
$scope.parseType = 'yaml';
ParseTypeChange({
scope: $scope,
variable: 'variables',
parse_variable: 'parseType',
field_id: 'inventory_variables'
});
LookUpInit({
scope: $scope,
form: form,
current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
$scope.parseType = 'yaml';
ParseTypeChange({
scope: $scope,
variable: 'variables',
parse_variable: 'parseType',
field_id: 'inventory_variables'
});
}
// Save
$scope.formSave = function () {
generator.clearApiErrors();
$scope.formSave = function() {
Wait('start');
try {
var fld, json_data, data;
@ -68,22 +59,24 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
data = {};
for (fld in form.fields) {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = $scope[fld];
data[form.fields[fld].realName] = $scope[fld];
} else {
data[fld] = $scope[fld];
data[fld] = $scope[fld];
}
}
Rest.setUrl(defaultUrl);
Rest.post(data)
.success(function (data) {
.success(function(data) {
var inventory_id = data.id;
Wait('stop');
$location.path('/inventories/' + inventory_id + '/manage');
})
.error(function (data, status) {
ProcessErrors( $scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to add new inventory. Post returned status: ' + status });
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to add new inventory. Post returned status: ' + status
});
});
} catch (err) {
Wait('stop');
@ -92,13 +85,13 @@ function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
};
$scope.formCancel = function () {
$state.transitionTo('inventories');
$scope.formCancel = function() {
$state.go('inventories');
};
}
export default['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd];
export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'GenerateForm', 'InventoryForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange',
'Wait', 'ToJSON', '$state', InventoriesAdd
];

View File

@ -1,19 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
import InventoriesAdd from './inventory-add.controller';
export default {
name: 'inventories.add',
route: '/add',
templateUrl: templateUrl('inventories/inventories'),
controller: InventoriesAdd,
ncyBreadcrumb: {
parent: "inventories",
label: "CREATE INVENTORY"
}
};

View File

@ -4,10 +4,8 @@
* All Rights Reserved
*************************************************/
import route from './inventory-add.route';
import controller from './inventory-add.controller';
export default
angular.module('inventoryAdd', [])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}]);
angular.module('inventoryAdd', [])
.controller('InventoryAddController', controller);

View File

@ -11,88 +11,62 @@
*/
function InventoriesEdit($scope, $rootScope, $compile, $location,
$log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
Prompt, InitiatePlaybookRun, CreateDialog, deleteJobTemplate, $state,
$filter) {
ClearScope();
$log, $stateParams, InventoryForm, Rest, Alert, ProcessErrors,
ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON,
ParseVariableString, Prompt, InitiatePlaybookRun,
deleteJobTemplate, $state, $filter) {
// Inject dynamic view
var defaultUrl = GetBasePath('inventory'),
form = InventoryForm(),
generator = GenerateForm,
inventory_id = $stateParams.inventory_id,
master = {},
fld, json_data, data,
relatedSets = {};
fld, json_data, data;
form.formLabelSize = null;
form.formFieldSize = null;
$scope.inventory_id = inventory_id;
ClearScope();
init();
$scope.$watch('invnentory_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
function init() {
ClearScope();
form.formLabelSize = null;
form.formFieldSize = null;
$scope.inventory_id = inventory_id;
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
generator.reset();
// After the project is loaded, retrieve each related set
if ($scope.inventoryLoadedRemove) {
$scope.inventoryLoadedRemove();
$scope.$watch('invnentory_obj.summary_fields.user_capabilities.edit', function(val) {
if (val === false) {
$scope.canAdd = false;
}
});
}
$scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () {
var set;
for (set in relatedSets) {
$scope.search(relatedSets[set].iterator);
}
});
Wait('start');
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
Rest.get()
.success(function (data) {
.success(function(data) {
var fld;
for (fld in form.fields) {
if (fld === 'variables') {
$scope.variables = ParseVariableString(data.variables);
master.variables = $scope.variables;
} else if (fld === 'inventory_name') {
$scope[fld] = data.name;
$scope[fld] = data.name;
master[fld] = $scope[fld];
} else if (fld === 'inventory_description') {
$scope[fld] = data.description;
$scope[fld] = data.description;
master[fld] = $scope[fld];
} else if (data[fld]) {
$scope[fld] = data[fld];
$scope[fld] = data[fld];
master[fld] = $scope[fld];
}
if (form.fields[fld].sourceModel && data.summary_fields &&
data.summary_fields[form.fields[fld].sourceModel]) {
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
}
}
relatedSets = form.relatedSets(data.related);
// Initialize related search functions. Doing it here to make sure relatedSets object is populated.
RelatedSearchInit({
scope: $scope,
form: form,
relatedSets: relatedSets
});
RelatedPaginateInit({
scope: $scope,
relatedSets: relatedSets
});
Wait('stop');
$scope.parseType = 'yaml';
@ -102,224 +76,94 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
parse_variable: 'parseType',
field_id: 'inventory_variables'
});
LookUpInit({
scope: $scope,
form: form,
current_item: $scope.organization,
list: OrganizationList,
field: 'organization',
input_type: 'radio'
});
$scope.inventory_obj = data;
$scope.$emit('inventoryLoaded');
})
.error(function (data, status) {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status });
.error(function(data, status) {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status
});
});
// Save
$scope.formSave = function () {
Wait('start');
$scope.formSave = function() {
Wait('start');
// Make sure we have valid variable data
json_data = ToJSON($scope.parseType, $scope.variables);
// Make sure we have valid variable data
json_data = ToJSON($scope.parseType, $scope.variables);
data = {};
for (fld in form.fields) {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = $scope[fld];
} else {
data[fld] = $scope[fld];
}
}
data = {};
for (fld in form.fields) {
if (form.fields[fld].realName) {
data[form.fields[fld].realName] = $scope[fld];
} else {
data[fld] = $scope[fld];
}
}
Rest.setUrl(defaultUrl + inventory_id + '/');
Rest.put(data)
.success(function () {
Wait('stop');
$state.go($state.current, {}, {reload: true});
})
.error(function (data, status) {
ProcessErrors($scope, data, status, form, { hdr: 'Error!',
msg: 'Failed to update inventory. PUT returned status: ' + status });
});
Rest.setUrl(defaultUrl + inventory_id + '/');
Rest.put(data)
.success(function() {
Wait('stop');
$state.go($state.current, {}, { reload: true });
})
.error(function(data, status) {
ProcessErrors($scope, data, status, form, {
hdr: 'Error!',
msg: 'Failed to update inventory. PUT returned status: ' + status
});
});
};
$scope.manageInventory = function(){
$location.path($location.path() + '/manage');
$scope.manageInventory = function() {
$location.path($location.path() + '/manage');
};
$scope.formCancel = function () {
$state.transitionTo('inventories');
$scope.formCancel = function() {
$state.go('inventories');
};
$scope.addScanJob = function(){
$location.path($location.path()+'/job_templates/add');
$scope.addScanJob = function() {
$location.path($location.path() + '/job_templates/add');
};
$scope.launchScanJob = function(){
$scope.launchScanJob = function() {
InitiatePlaybookRun({ scope: $scope, id: this.scan_job_template.id });
};
$scope.scheduleScanJob = function(){
$location.path('/job_templates/'+this.scan_job_template.id+'/schedules');
$scope.scheduleScanJob = function() {
$location.path('/job_templates/' + this.scan_job_template.id + '/schedules');
};
$scope.editScanJob = function(){
$location.path($location.path()+'/job_templates/'+this.scan_job_template.id);
$scope.editScanJob = function() {
$location.path($location.path() + '/job_templates/' + this.scan_job_template.id);
};
$scope.copyScanJobTemplate = function(){
var id = this.scan_job_template.id,
name = this.scan_job_template.name,
element,
buttons = [{
"label": "Cancel",
"onClick": function() {
$(this).dialog('close');
},
"icon": "fa-times",
"class": "btn btn-default",
"id": "copy-close-button"
},{
"label": "Copy",
"onClick": function() {
copyAction();
},
"icon": "fa-copy",
"class": "btn btn-primary",
"id": "job-copy-button"
}],
copyAction = function () {
// retrieve the copy of the job template object from the api, then overwrite the name and throw away the id
Wait('start');
var url = GetBasePath('job_templates')+id;
Rest.setUrl(url);
Rest.get()
.success(function (data) {
data.name = $scope.new_copy_name;
delete data.id;
$scope.$emit('GoToCopy', data);
})
.error(function (data) {
Wait('stop');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
});
};
CreateDialog({
id: 'copy-job-modal' ,
title: "Copy",
scope: $scope,
buttons: buttons,
width: 500,
height: 300,
minWidth: 200,
callback: 'CopyDialogReady'
});
$('#job_name').text(name);
$('#copy-job-modal').show();
if ($scope.removeCopyDialogReady) {
$scope.removeCopyDialogReady();
}
$scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() {
//clear any old remaining text
$scope.new_copy_name = "" ;
$scope.copy_form.$setPristine();
$('#copy-job-modal').dialog('open');
$('#job-copy-button').attr('ng-disabled', "!copy_form.$valid");
element = angular.element(document.getElementById('job-copy-button'));
$compile(element)($scope);
});
if ($scope.removeGoToCopy) {
$scope.removeGoToCopy();
}
$scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) {
var url = GetBasePath('job_templates'),
old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ;
Rest.setUrl(url);
Rest.post(data)
.success(function (data) {
if(data.survey_enabled===true){
$scope.$emit("CopySurvey", data, old_survey_url);
}
else {
$('#copy-job-modal').dialog('close');
Wait('stop');
$location.path($location.path() + '/job_templates/' + data.id);
}
})
.error(function (data) {
Wait('stop');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
});
});
if ($scope.removeCopySurvey) {
$scope.removeCopySurvey();
}
$scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) {
// var url = data.related.survey_spec;
Rest.setUrl(old_url);
Rest.get()
.success(function (survey_data) {
Rest.setUrl(new_data.related.survey_spec);
Rest.post(survey_data)
.success(function () {
$('#copy-job-modal').dialog('close');
Wait('stop');
$location.path($location.path() + '/job_templates/' + new_data.id);
})
.error(function (data) {
Wait('stop');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status });
});
})
.error(function (data) {
Wait('stop');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status });
});
});
};
$scope.deleteScanJob = function () {
var id = this.scan_job_template.id ,
action = function () {
$('#prompt-modal').modal('hide');
Wait('start');
deleteJobTemplate(id)
.success(function () {
$('#prompt-modal').modal('hide');
$scope.search(form.related.scan_job_templates.iterator);
})
.error(function (data) {
Wait('stop');
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'DELETE returned status: ' + status });
});
};
$scope.deleteScanJob = function() {
var id = this.scan_job_template.id,
action = function() {
$('#prompt-modal').modal('hide');
Wait('start');
deleteJobTemplate(id)
.success(function() {
$('#prompt-modal').modal('hide');
// @issue: OLD SEARCH
// $scope.search(form.related.scan_job_templates.iterator);
})
.error(function(data) {
Wait('stop');
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: 'DELETE returned status: ' + status
});
});
};
Prompt({
hdr: 'Delete',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the job template below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(this.scan_job_template.name) + '</div>',
body: '<div class="Prompt-bodyQuery">Are you sure you want to delete the job template below?</div><div class="Prompt-bodyTarget">' + $filter('sanitize')(this.scan_job_template.name) + '</div>',
action: action,
actionText: 'DELETE'
});
@ -329,11 +173,8 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
}
export default ['$scope', '$rootScope', '$compile', '$location',
'$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString',
'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt',
'InitiatePlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state',
'$filter', InventoriesEdit,
'$log', '$stateParams', 'InventoryForm', 'Rest', 'Alert',
'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait',
'ToJSON', 'ParseVariableString', 'Prompt', 'InitiatePlaybookRun',
'deleteJobTemplate', '$state', '$filter', InventoriesEdit,
];

View File

@ -1,22 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
import InventoriesEdit from './inventory-edit.controller';
export default {
name: 'inventories.edit',
route: '/:inventory_id',
templateUrl: templateUrl('inventories/inventories'),
controller: InventoriesEdit,
data: {
activityStreamId: 'inventory_id'
},
ncyBreadcrumb: {
parent: 'inventories',
label: "{{inventory_obj.name}}"
}
};

View File

@ -4,10 +4,8 @@
* All Rights Reserved
*************************************************/
import route from './inventory-edit.route';
import controller from './inventory-edit.controller';
export default
angular.module('inventoryEdit', [])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}]);
.controller('InventoryEditController', controller);

View File

@ -1,5 +0,0 @@
<div class="tab-pane" id="inventories">
<div ui-view=""></div>
<div ng-cloak id="htmlTemplate" class="Panel"></div>
<div ng-include="'/static/partials/logviewer.html'"></div>
</div>

View File

@ -11,11 +11,15 @@
*/
function InventoriesList($scope, $rootScope, $location, $log,
$stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath, Wait,
Find, Empty, $state, rbacUiControlService) {
$stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList, Prompt,
ClearScope, ProcessErrors, GetBasePath, Wait, Find, Empty, $state, rbacUiControlService, Dataset) {
let list = InventoryList,
defaultUrl = GetBasePath('inventory');
init();
function init(){
$scope.canAdd = false;
rbacUiControlService.canAdd('inventory')
@ -23,11 +27,49 @@ function InventoriesList($scope, $rootScope, $location, $log,
$scope.canAdd = canAdd;
});
var list = InventoryList,
defaultUrl = GetBasePath('inventory') + ($stateParams.status === 'sync-failed' ? '?not__inventory_sources_with_failures=0' : ''),
view = generateList,
paths = $location.path().replace(/^\//, '').split('/'),
mode = (paths[0] === 'inventories') ? 'edit' : 'select';
$scope.$watchCollection(list.name, function(){
_.forEach($scope[list.name], buildStatusIndicators);
});
// Search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
$rootScope.flashMessage = null;
}
function buildStatusIndicators(inventory){
inventory.launch_class = "";
if (inventory.has_inventory_sources) {
if (inventory.inventory_sources_with_failures > 0) {
inventory.syncStatus = 'error';
inventory.syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details';
}
else {
inventory.syncStatus = 'successful';
inventory.syncTip = 'No inventory sync failures. Click for details.';
}
}
else {
inventory.syncStatus = 'na';
inventory.syncTip = 'Not configured for inventory sync.';
inventory.launch_class = "btn-disabled";
}
if (inventory.has_active_failures) {
inventory.hostsStatus = 'error';
inventory.hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.';
}
else if (inventory.total_hosts) {
inventory.hostsStatus = 'successful';
inventory.hostsTip = 'No hosts with failures. Click for details.';
}
else {
inventory.hostsStatus = 'none';
inventory.hostsTip = 'Inventory contains 0 hosts.';
}
}
function ellipsis(a) {
if (a.length > 20) {
@ -62,117 +104,6 @@ function InventoriesList($scope, $rootScope, $location, $log,
$scope.triggerPopover(event);
}
view.inject(InventoryList, { mode: mode, scope: $scope });
$rootScope.flashMessage = null;
SearchInit({
scope: $scope,
set: 'inventories',
list: list,
url: defaultUrl
});
PaginateInit({
scope: $scope,
list: list,
url: defaultUrl
});
if ($stateParams.name) {
$scope[InventoryList.iterator + 'InputDisable'] = false;
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name;
$scope[InventoryList.iterator + 'SearchField'] = 'name';
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label;
$scope[InventoryList.iterator + 'SearchSelectValue'] = null;
}
if ($stateParams.has_active_failures) {
$scope[InventoryList.iterator + 'InputDisable'] = true;
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures;
$scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures';
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label;
$scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? {
value: 1
} : {
value: 0
};
}
if ($stateParams.has_inventory_sources) {
$scope[InventoryList.iterator + 'InputDisable'] = true;
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources;
$scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources';
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label;
$scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? {
value: 1
} : {
value: 0
};
}
if ($stateParams.inventory_sources_with_failures) {
// pass a value of true, however this field actually contains an integer value
$scope[InventoryList.iterator + 'InputDisable'] = true;
$scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures;
$scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures';
$scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label;
$scope[InventoryList.iterator + 'SearchType'] = 'gtzero';
}
$scope.search(list.iterator);
if ($scope.removePostRefresh) {
$scope.removePostRefresh();
}
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
//If we got here by deleting an inventory, stop the spinner and cleanup events
Wait('stop');
try {
$('#prompt-modal').modal('hide');
}
catch(e) {
// ignore
}
$scope.inventories.forEach(function(inventory, idx) {
$scope.inventories[idx].launch_class = "";
if (inventory.has_inventory_sources) {
if (inventory.inventory_sources_with_failures > 0) {
$scope.inventories[idx].syncStatus = 'error';
$scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details';
}
else {
$scope.inventories[idx].syncStatus = 'successful';
$scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.';
}
}
else {
$scope.inventories[idx].syncStatus = 'na';
$scope.inventories[idx].syncTip = 'Not configured for inventory sync.';
$scope.inventories[idx].launch_class = "btn-disabled";
}
if (inventory.has_active_failures) {
$scope.inventories[idx].hostsStatus = 'error';
$scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.';
}
else if (inventory.total_hosts) {
$scope.inventories[idx].hostsStatus = 'successful';
$scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.';
}
else {
$scope.inventories[idx].hostsStatus = 'none';
$scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.';
}
});
});
if ($scope.removeRefreshInventories) {
$scope.removeRefreshInventories();
}
$scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () {
// Reflect changes after inventory properties edit completes
$scope.search(list.iterator);
});
if ($scope.removeHostSummaryReady) {
$scope.removeHostSummaryReady();
}
@ -343,7 +274,8 @@ function InventoriesList($scope, $rootScope, $location, $log,
if (parseInt($state.params.inventory_id) === id) {
$state.go("^", null, {reload: true});
} else {
$scope.search(list.iterator);
// @issue: OLD SEARCH
// $scope.search(list.iterator);
}
})
.error(function (data, status) {
@ -361,15 +293,6 @@ function InventoriesList($scope, $rootScope, $location, $log,
});
};
$scope.lookupOrganization = function (organization_id) {
Rest.setUrl(GetBasePath('organizations') + organization_id + '/');
Rest.get()
.success(function (data) {
return data.name;
});
};
// Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status
$scope.viewJobs = function (id) {
$location.url('/jobs/?inventory__int=' + id);
@ -382,5 +305,5 @@ function InventoriesList($scope, $rootScope, $location, $log,
export default ['$scope', '$rootScope', '$location', '$log',
'$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList',
'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', InventoriesList];
'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'rbacUiControlService', 'Dataset', InventoriesList
];

View File

@ -1,22 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
import InventoriesList from './inventory-list.controller';
export default {
name: 'inventories',
route: '/inventories?{status}',
templateUrl: templateUrl('inventories/inventories'),
controller: InventoriesList,
data: {
activityStream: true,
activityStreamTarget: 'inventory'
},
ncyBreadcrumb: {
label: "INVENTORIES"
}
};

View File

@ -4,10 +4,8 @@
* All Rights Reserved
*************************************************/
import route from './inventory-list.route';
import controller from './inventory-list.controller';
export default
angular.module('inventoryList', [])
.run(['$stateExtender', function($stateExtender) {
$stateExtender.addState(route);
}]);
angular.module('inventoryList', [])
.controller('InventoryListController', controller);

View File

@ -8,11 +8,218 @@ import inventoryAdd from './add/main';
import inventoryEdit from './edit/main';
import inventoryList from './list/main';
import inventoryManage from './manage/main';
import inventoryManageListRoute from './manage/inventory-manage.route';
import { copyMoveGroupRoute, copyMoveHostRoute } from './manage/copy-move/copy-move.route';
import adHocRoute from './manage/adhoc/adhoc.route';
import { templateUrl } from '../shared/template-url/template-url.factory';
export default
angular.module('inventory', [
inventoryAdd.name,
inventoryEdit.name,
inventoryList.name,
inventoryManage.name,
]);
inventoryAdd.name,
inventoryEdit.name,
inventoryList.name,
inventoryManage.name,
])
.config(['$stateProvider', '$stateExtenderProvider', 'stateDefinitionsProvider',
function($stateProvider, $stateExtenderProvider, stateDefinitionsProvider) {
// When stateDefinition.lazyLoad() resolves, states matching name.** or /url** will be de-registered and replaced with resolved states
// This means inventoryManage states will not be registered correctly on page refresh, unless they're registered at the same time as the inventories state tree
let stateTree, inventories,
addGroup, editGroup, addHost, editHost,
listSchedules, addSchedule, editSchedule,
stateDefinitions = stateDefinitionsProvider.$get(),
stateExtender = $stateExtenderProvider.$get();
function generateStateTree() {
// inventories state node
inventories = stateDefinitions.generateTree({
parent: 'inventories', // top-most node in the generated tree (will replace this state definition)
modes: ['add', 'edit'],
list: 'InventoryList',
form: 'InventoryForm',
controllers: {
list: 'InventoryListController',
add: 'InventoryAddController',
edit: 'InventoryEditController'
},
data: {
activityStream: true,
activityStreamTarget: 'inventory'
}
});
// scheduler state nodes
listSchedules = {
name: 'inventoryManage.editGroup.schedules',
url: '/schedules',
searchPrefix: 'schedule',
ncyBreadcrumb: {
parent: 'inventoryManage.editGroup({group_id: parentObject.id})',
label: 'SCHEDULES'
},
resolve: {
Dataset: ['SchedulesList', 'QuerySet', '$stateParams', 'GetBasePath', 'groupData',
function(list, qs, $stateParams, GetBasePath, groupData) {
let path = `${groupData.related.inventory_source}schedules`;
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
ParentObject: ['groupData', function(groupData) {
return groupData;
}]
},
views: {
// clear form template when views render in this substate
'form@': {
templateProvider: () => ''
},
'list@': {
templateProvider: function(SchedulesList, generateList, ParentObject) {
// include name of parent resource in listTitle
SchedulesList.listTitle = `${ParentObject.name}<div class='List-titleLockup'></div>Schedules`;
let html = generateList.build({
list: SchedulesList,
mode: 'edit'
});
html = generateList.wrapPanel(html);
return html;
},
controller: 'schedulerListController'
}
}
};
addSchedule = {
name: 'inventoryManage.editGroup.schedules.add',
url: '/add',
ncyBreadcrumb: {
label: "CREATE SCHEDULE"
},
views: {
'form@': {
controller: 'schedulerAddController',
templateUrl: templateUrl("scheduler/schedulerForm")
}
}
};
editSchedule = {
name: 'inventoryManage.editGroup.schedules.edit',
url: '/:schedule_id',
ncyBreadcrumb: {
label: "{{schedule_obj.name}}"
},
views: {
'form@': {
templateUrl: templateUrl("scheduler/schedulerForm"),
controller: 'schedulerEditController',
}
}
};
// group state nodes
addGroup = stateDefinitions.generateTree({
url: '/add-group',
name: 'inventoryManage.addGroup',
modes: ['add'],
form: 'GroupForm',
controllers: {
add: 'GroupAddController'
}
});
editGroup = stateDefinitions.generateTree({
//parent: 'inventoryManage', // top-most node in the generated tree (tree will replace this node)
url: '/edit-group/:group_id',
name: 'inventoryManage.editGroup',
modes: ['edit'],
form: 'GroupForm',
controllers: {
edit: 'GroupEditController'
},
resolve: {
edit: {
groupData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService) {
return GroupManageService.get({ id: $stateParams.group_id }).then(res => res.data.results[0]);
}],
inventorySourceData: ['$stateParams', 'GroupManageService', function($stateParams, GroupManageService) {
return GroupManageService.getInventorySource({ group: $stateParams.group_id }).then(res => res.data.results[0]);
}]
}
},
// concat boilerplate schedule state definitions with generated editGroup state definitions
}).then((generated) => {
let schedulerDefinitions = _.map([
stateExtender.buildDefinition(listSchedules),
stateExtender.buildDefinition(addSchedule),
stateExtender.buildDefinition(editSchedule)
],
(state) => stateExtender.buildDefinition(state));
return {
states: _(generated.states)
.concat(schedulerDefinitions)
.value()
};
});
// host state nodes
addHost = stateDefinitions.generateTree({
url: '/add-host',
name: 'inventoryManage.addHost',
modes: ['add'],
form: 'HostForm',
controllers: {
add: 'HostsAddController'
}
});
editHost = stateDefinitions.generateTree({
url: '/edit-host/:host_id',
name: 'inventoryManage.editHost',
modes: ['edit'],
form: 'HostForm',
controllers: {
edit: 'HostEditController'
},
resolve: {
host: ['$stateParams', 'HostManageService', function($stateParams, HostManageService) {
return HostManageService.get({ id: $stateParams.host_id }).then(function(res) {
return res.data.results[0];
});
}]
},
ncyBreadcrumb: {
label: "{{host.name}}",
},
});
return Promise.all([
inventories,
addGroup,
editGroup,
addHost,
editHost,
]).then((generated) => {
return {
states: _.reduce(generated, (result, definition) => {
return result.concat(definition.states);
}, [
stateExtender.buildDefinition(inventoryManageListRoute),
stateExtender.buildDefinition(copyMoveGroupRoute),
stateExtender.buildDefinition(copyMoveHostRoute),
stateExtender.buildDefinition(adHocRoute),
])
};
});
}
stateTree = {
name: 'inventories',
url: '/inventories',
lazyLoad: () => generateStateTree()
};
$stateProvider.state(stateTree);
}
]);

Some files were not shown because too many files have changed in this diff Show More