mirror of
https://github.com/ansible/awx.git
synced 2026-05-12 03:47:36 -02:30
Merge pull request #5448 from ryanpetrello/remove-computed-group-and-host-fields
remove computed inventory fields from Host and Group Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -98,21 +98,14 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'total_hosts',
|
'total_hosts',
|
||||||
'hosts_with_active_failures',
|
'hosts_with_active_failures',
|
||||||
'total_groups',
|
'total_groups',
|
||||||
'groups_with_active_failures',
|
|
||||||
'has_inventory_sources',
|
'has_inventory_sources',
|
||||||
'total_inventory_sources',
|
'total_inventory_sources',
|
||||||
'inventory_sources_with_failures',
|
'inventory_sources_with_failures',
|
||||||
'organization_id',
|
'organization_id',
|
||||||
'kind',
|
'kind',
|
||||||
'insights_credential_id',),
|
'insights_credential_id',),
|
||||||
'host': DEFAULT_SUMMARY_FIELDS + ('has_active_failures',
|
'host': DEFAULT_SUMMARY_FIELDS,
|
||||||
'has_inventory_sources'),
|
'group': DEFAULT_SUMMARY_FIELDS,
|
||||||
'group': DEFAULT_SUMMARY_FIELDS + ('has_active_failures',
|
|
||||||
'total_hosts',
|
|
||||||
'hosts_with_active_failures',
|
|
||||||
'total_groups',
|
|
||||||
'groups_with_active_failures',
|
|
||||||
'has_inventory_sources'),
|
|
||||||
'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'),
|
'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'),
|
||||||
'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'),
|
'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'),
|
||||||
'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
|
'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
|
||||||
@@ -1549,20 +1542,15 @@ class InventorySerializer(BaseSerializerWithVariables):
|
|||||||
'admin', 'adhoc',
|
'admin', 'adhoc',
|
||||||
{'copy': 'organization.inventory_admin'}
|
{'copy': 'organization.inventory_admin'}
|
||||||
]
|
]
|
||||||
groups_with_active_failures = serializers.IntegerField(
|
|
||||||
read_only=True,
|
|
||||||
min_value=0,
|
|
||||||
help_text=_('This field has been deprecated and will be removed in a future release')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Inventory
|
model = Inventory
|
||||||
fields = ('*', 'organization', 'kind', 'host_filter', 'variables', 'has_active_failures',
|
fields = ('*', 'organization', 'kind', 'host_filter', 'variables', 'has_active_failures',
|
||||||
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
||||||
'groups_with_active_failures', 'has_inventory_sources',
|
'has_inventory_sources', 'total_inventory_sources',
|
||||||
'total_inventory_sources', 'inventory_sources_with_failures',
|
'inventory_sources_with_failures', 'insights_credential',
|
||||||
'insights_credential', 'pending_deletion',)
|
'pending_deletion',)
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(InventorySerializer, self).get_related(obj)
|
res = super(InventorySerializer, self).get_related(obj)
|
||||||
@@ -1644,6 +1632,9 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
capabilities_prefetch = ['inventory.admin']
|
capabilities_prefetch = ['inventory.admin']
|
||||||
|
|
||||||
|
has_active_failures = serializers.SerializerMethodField()
|
||||||
|
has_inventory_sources = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Host
|
model = Host
|
||||||
fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables',
|
fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables',
|
||||||
@@ -1757,6 +1748,14 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
ret['last_job_host_summary'] = None
|
ret['last_job_host_summary'] = None
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def get_has_active_failures(self, obj):
|
||||||
|
return bool(
|
||||||
|
obj.last_job_host_summary and obj.last_job_host_summary.failed
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_has_inventory_sources(self, obj):
|
||||||
|
return obj.inventory_sources.exists()
|
||||||
|
|
||||||
|
|
||||||
class AnsibleFactsSerializer(BaseSerializer):
|
class AnsibleFactsSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1769,17 +1768,10 @@ class AnsibleFactsSerializer(BaseSerializer):
|
|||||||
class GroupSerializer(BaseSerializerWithVariables):
|
class GroupSerializer(BaseSerializerWithVariables):
|
||||||
show_capabilities = ['copy', 'edit', 'delete']
|
show_capabilities = ['copy', 'edit', 'delete']
|
||||||
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc']
|
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc']
|
||||||
groups_with_active_failures = serializers.IntegerField(
|
|
||||||
read_only=True,
|
|
||||||
min_value=0,
|
|
||||||
help_text=_('This field has been deprecated and will be removed in a future release')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ('*', 'inventory', 'variables', 'has_active_failures',
|
fields = ('*', 'inventory', 'variables')
|
||||||
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
|
||||||
'groups_with_active_failures', 'has_inventory_sources')
|
|
||||||
|
|
||||||
def build_relational_field(self, field_name, relation_info):
|
def build_relational_field(self, field_name, relation_info):
|
||||||
field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info)
|
field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info)
|
||||||
|
|||||||
@@ -204,20 +204,15 @@ class DashboardView(APIView):
|
|||||||
'failed': ec2_inventory_failed.count()}
|
'failed': ec2_inventory_failed.count()}
|
||||||
|
|
||||||
user_groups = get_user_queryset(request.user, models.Group)
|
user_groups = get_user_queryset(request.user, models.Group)
|
||||||
groups_job_failed = (
|
|
||||||
models.Group.objects.filter(hosts_with_active_failures__gt=0) | models.Group.objects.filter(groups_with_active_failures__gt=0)
|
|
||||||
).count()
|
|
||||||
groups_inventory_failed = models.Group.objects.filter(inventory_sources__last_job_failed=True).count()
|
groups_inventory_failed = models.Group.objects.filter(inventory_sources__last_job_failed=True).count()
|
||||||
data['groups'] = {'url': reverse('api:group_list', request=request),
|
data['groups'] = {'url': reverse('api:group_list', request=request),
|
||||||
'failures_url': reverse('api:group_list', request=request) + "?has_active_failures=True",
|
|
||||||
'total': user_groups.count(),
|
'total': user_groups.count(),
|
||||||
'job_failed': groups_job_failed,
|
|
||||||
'inventory_failed': groups_inventory_failed}
|
'inventory_failed': groups_inventory_failed}
|
||||||
|
|
||||||
user_hosts = get_user_queryset(request.user, models.Host)
|
user_hosts = get_user_queryset(request.user, models.Host)
|
||||||
user_hosts_failed = user_hosts.filter(has_active_failures=True)
|
user_hosts_failed = user_hosts.filter(last_job_host_summary__failed=True)
|
||||||
data['hosts'] = {'url': reverse('api:host_list', request=request),
|
data['hosts'] = {'url': reverse('api:host_list', request=request),
|
||||||
'failures_url': reverse('api:host_list', request=request) + "?has_active_failures=True",
|
'failures_url': reverse('api:host_list', request=request) + "?last_job_host_summary__failed=True",
|
||||||
'total': user_hosts.count(),
|
'total': user_hosts.count(),
|
||||||
'failed': user_hosts_failed.count()}
|
'failed': user_hosts_failed.count()}
|
||||||
|
|
||||||
|
|||||||
@@ -907,7 +907,7 @@ class HostAccess(BaseAccess):
|
|||||||
model = Host
|
model = Host
|
||||||
select_related = ('created_by', 'modified_by', 'inventory',
|
select_related = ('created_by', 'modified_by', 'inventory',
|
||||||
'last_job__job_template', 'last_job_host_summary__job',)
|
'last_job__job_template', 'last_job_host_summary__job',)
|
||||||
prefetch_related = ('groups',)
|
prefetch_related = ('groups', 'inventory_sources')
|
||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
|
return self.model.objects.filter(inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
|
||||||
|
|||||||
52
awx/main/migrations/0103_v370_remove_computed_fields.py
Normal file
52
awx/main/migrations/0103_v370_remove_computed_fields.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.16 on 2019-02-21 17:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0102_v370_unifiedjob_canceled'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='groups_with_active_failures',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='has_active_failures',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='has_inventory_sources',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='hosts_with_active_failures',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='total_groups',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='group',
|
||||||
|
name='total_hosts',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='host',
|
||||||
|
name='has_active_failures',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='host',
|
||||||
|
name='has_inventory_sources',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobhostsummary',
|
||||||
|
name='failed',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, editable=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
# Python
|
# Python
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import copy
|
import copy
|
||||||
@@ -339,139 +338,17 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def update_host_computed_fields(self):
|
def update_computed_fields(self):
|
||||||
'''
|
|
||||||
Update computed fields for all hosts in this inventory.
|
|
||||||
'''
|
|
||||||
hosts_to_update = {}
|
|
||||||
hosts_qs = self.hosts
|
|
||||||
# Define queryset of all hosts with active failures.
|
|
||||||
hosts_with_active_failures = hosts_qs.filter(last_job_host_summary__isnull=False, last_job_host_summary__failed=True).values_list('pk', flat=True)
|
|
||||||
# Find all hosts that need the has_active_failures flag set.
|
|
||||||
hosts_to_set = hosts_qs.filter(has_active_failures=False, pk__in=hosts_with_active_failures)
|
|
||||||
for host_pk in hosts_to_set.values_list('pk', flat=True):
|
|
||||||
host_updates = hosts_to_update.setdefault(host_pk, {})
|
|
||||||
host_updates['has_active_failures'] = True
|
|
||||||
# Find all hosts that need the has_active_failures flag cleared.
|
|
||||||
hosts_to_clear = hosts_qs.filter(has_active_failures=True).exclude(pk__in=hosts_with_active_failures)
|
|
||||||
for host_pk in hosts_to_clear.values_list('pk', flat=True):
|
|
||||||
host_updates = hosts_to_update.setdefault(host_pk, {})
|
|
||||||
host_updates['has_active_failures'] = False
|
|
||||||
# Define queryset of all hosts with cloud inventory sources.
|
|
||||||
hosts_with_cloud_inventory = hosts_qs.filter(inventory_sources__source__in=CLOUD_INVENTORY_SOURCES).values_list('pk', flat=True)
|
|
||||||
# Find all hosts that need the has_inventory_sources flag set.
|
|
||||||
hosts_to_set = hosts_qs.filter(has_inventory_sources=False, pk__in=hosts_with_cloud_inventory)
|
|
||||||
for host_pk in hosts_to_set.values_list('pk', flat=True):
|
|
||||||
host_updates = hosts_to_update.setdefault(host_pk, {})
|
|
||||||
host_updates['has_inventory_sources'] = True
|
|
||||||
# Find all hosts that need the has_inventory_sources flag cleared.
|
|
||||||
hosts_to_clear = hosts_qs.filter(has_inventory_sources=True).exclude(pk__in=hosts_with_cloud_inventory)
|
|
||||||
for host_pk in hosts_to_clear.values_list('pk', flat=True):
|
|
||||||
host_updates = hosts_to_update.setdefault(host_pk, {})
|
|
||||||
host_updates['has_inventory_sources'] = False
|
|
||||||
# Now apply updates to hosts where needed (in batches).
|
|
||||||
all_update_pks = list(hosts_to_update.keys())
|
|
||||||
|
|
||||||
def _chunk(items, chunk_size):
|
|
||||||
for i, group in itertools.groupby(enumerate(items), lambda x: x[0] // chunk_size):
|
|
||||||
yield (g[1] for g in group)
|
|
||||||
|
|
||||||
for update_pks in _chunk(all_update_pks, 500):
|
|
||||||
for host in hosts_qs.filter(pk__in=update_pks):
|
|
||||||
host_updates = hosts_to_update[host.pk]
|
|
||||||
for field, value in host_updates.items():
|
|
||||||
setattr(host, field, value)
|
|
||||||
host.save(update_fields=host_updates.keys())
|
|
||||||
|
|
||||||
def update_group_computed_fields(self):
|
|
||||||
'''
|
|
||||||
Update computed fields for all active groups in this inventory.
|
|
||||||
'''
|
|
||||||
group_children_map = self.get_group_children_map()
|
|
||||||
group_hosts_map = self.get_group_hosts_map()
|
|
||||||
active_host_pks = set(self.hosts.values_list('pk', flat=True))
|
|
||||||
failed_host_pks = set(self.hosts.filter(last_job_host_summary__failed=True).values_list('pk', flat=True))
|
|
||||||
# active_group_pks = set(self.groups.values_list('pk', flat=True))
|
|
||||||
failed_group_pks = set() # Update below as we check each group.
|
|
||||||
groups_with_cloud_pks = set(self.groups.filter(inventory_sources__source__in=CLOUD_INVENTORY_SOURCES).values_list('pk', flat=True))
|
|
||||||
groups_to_update = {}
|
|
||||||
|
|
||||||
# Build list of group pks to check, starting with the groups at the
|
|
||||||
# deepest level within the tree.
|
|
||||||
root_group_pks = set(self.root_groups.values_list('pk', flat=True))
|
|
||||||
group_depths = {} # pk: max_depth
|
|
||||||
|
|
||||||
def update_group_depths(group_pk, current_depth=0):
|
|
||||||
max_depth = group_depths.get(group_pk, -1)
|
|
||||||
# Arbitrarily limit depth to avoid hitting Python recursion limit (which defaults to 1000).
|
|
||||||
if current_depth > 100:
|
|
||||||
return
|
|
||||||
if current_depth > max_depth:
|
|
||||||
group_depths[group_pk] = current_depth
|
|
||||||
for child_pk in group_children_map.get(group_pk, set()):
|
|
||||||
update_group_depths(child_pk, current_depth + 1)
|
|
||||||
for group_pk in root_group_pks:
|
|
||||||
update_group_depths(group_pk)
|
|
||||||
group_pks_to_check = [x[1] for x in sorted([(v,k) for k,v in group_depths.items()], reverse=True)]
|
|
||||||
|
|
||||||
for group_pk in group_pks_to_check:
|
|
||||||
# Get all children and host pks for this group.
|
|
||||||
parent_pks_to_check = set([group_pk])
|
|
||||||
parent_pks_checked = set()
|
|
||||||
child_pks = set()
|
|
||||||
host_pks = set()
|
|
||||||
while parent_pks_to_check:
|
|
||||||
for parent_pk in list(parent_pks_to_check):
|
|
||||||
c_ids = group_children_map.get(parent_pk, set())
|
|
||||||
child_pks.update(c_ids)
|
|
||||||
parent_pks_to_check.remove(parent_pk)
|
|
||||||
parent_pks_checked.add(parent_pk)
|
|
||||||
parent_pks_to_check.update(c_ids - parent_pks_checked)
|
|
||||||
h_ids = group_hosts_map.get(parent_pk, set())
|
|
||||||
host_pks.update(h_ids)
|
|
||||||
# Define updates needed for this group.
|
|
||||||
group_updates = groups_to_update.setdefault(group_pk, {})
|
|
||||||
group_updates.update({
|
|
||||||
'total_hosts': len(active_host_pks & host_pks),
|
|
||||||
'has_active_failures': bool(failed_host_pks & host_pks),
|
|
||||||
'hosts_with_active_failures': len(failed_host_pks & host_pks),
|
|
||||||
'total_groups': len(child_pks),
|
|
||||||
'groups_with_active_failures': len(failed_group_pks & child_pks),
|
|
||||||
'has_inventory_sources': bool(group_pk in groups_with_cloud_pks),
|
|
||||||
})
|
|
||||||
if group_updates['has_active_failures']:
|
|
||||||
failed_group_pks.add(group_pk)
|
|
||||||
|
|
||||||
# Now apply updates to each group as needed (in batches).
|
|
||||||
all_update_pks = list(groups_to_update.keys())
|
|
||||||
for offset in range(0, len(all_update_pks), 500):
|
|
||||||
update_pks = all_update_pks[offset:(offset + 500)]
|
|
||||||
for group in self.groups.filter(pk__in=update_pks):
|
|
||||||
group_updates = groups_to_update[group.pk]
|
|
||||||
for field, value in list(group_updates.items()):
|
|
||||||
if getattr(group, field) != value:
|
|
||||||
setattr(group, field, value)
|
|
||||||
else:
|
|
||||||
group_updates.pop(field)
|
|
||||||
if group_updates:
|
|
||||||
group.save(update_fields=group_updates.keys())
|
|
||||||
|
|
||||||
def update_computed_fields(self, update_groups=True, update_hosts=True):
|
|
||||||
'''
|
'''
|
||||||
Update model fields that are computed from database relationships.
|
Update model fields that are computed from database relationships.
|
||||||
'''
|
'''
|
||||||
logger.debug("Going to update inventory computed fields, pk={0}".format(self.pk))
|
logger.debug("Going to update inventory computed fields, pk={0}".format(self.pk))
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
if update_hosts:
|
|
||||||
self.update_host_computed_fields()
|
|
||||||
if update_groups:
|
|
||||||
self.update_group_computed_fields()
|
|
||||||
active_hosts = self.hosts
|
active_hosts = self.hosts
|
||||||
failed_hosts = active_hosts.filter(has_active_failures=True)
|
failed_hosts = active_hosts.filter(last_job_host_summary__failed=True)
|
||||||
active_groups = self.groups
|
active_groups = self.groups
|
||||||
if self.kind == 'smart':
|
if self.kind == 'smart':
|
||||||
active_groups = active_groups.none()
|
active_groups = active_groups.none()
|
||||||
failed_groups = active_groups.filter(has_active_failures=True)
|
|
||||||
if self.kind == 'smart':
|
if self.kind == 'smart':
|
||||||
active_inventory_sources = self.inventory_sources.none()
|
active_inventory_sources = self.inventory_sources.none()
|
||||||
else:
|
else:
|
||||||
@@ -482,7 +359,6 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
|
|||||||
'total_hosts': active_hosts.count(),
|
'total_hosts': active_hosts.count(),
|
||||||
'hosts_with_active_failures': failed_hosts.count(),
|
'hosts_with_active_failures': failed_hosts.count(),
|
||||||
'total_groups': active_groups.count(),
|
'total_groups': active_groups.count(),
|
||||||
'groups_with_active_failures': failed_groups.count(),
|
|
||||||
'has_inventory_sources': bool(active_inventory_sources.count()),
|
'has_inventory_sources': bool(active_inventory_sources.count()),
|
||||||
'total_inventory_sources': active_inventory_sources.count(),
|
'total_inventory_sources': active_inventory_sources.count(),
|
||||||
'inventory_sources_with_failures': failed_inventory_sources.count(),
|
'inventory_sources_with_failures': failed_inventory_sources.count(),
|
||||||
@@ -545,7 +421,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
|
|||||||
if (self.kind == 'smart' and 'host_filter' in kwargs.get('update_fields', ['host_filter']) and
|
if (self.kind == 'smart' and 'host_filter' in kwargs.get('update_fields', ['host_filter']) and
|
||||||
connection.vendor != 'sqlite'):
|
connection.vendor != 'sqlite'):
|
||||||
# Minimal update of host_count for smart inventory host filter changes
|
# Minimal update of host_count for smart inventory host filter changes
|
||||||
self.update_computed_fields(update_groups=False, update_hosts=False)
|
self.update_computed_fields()
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
self._update_host_smart_inventory_memeberships()
|
self._update_host_smart_inventory_memeberships()
|
||||||
@@ -631,18 +507,6 @@ class Host(CommonModelNameNotUnique, RelatedJobsMixin):
|
|||||||
editable=False,
|
editable=False,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
has_active_failures = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Flag indicating whether the last job failed for this host.'),
|
|
||||||
)
|
|
||||||
has_inventory_sources = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Flag indicating whether this host was created/updated from any external inventory sources.'),
|
|
||||||
)
|
|
||||||
inventory_sources = models.ManyToManyField(
|
inventory_sources = models.ManyToManyField(
|
||||||
'InventorySource',
|
'InventorySource',
|
||||||
related_name='hosts',
|
related_name='hosts',
|
||||||
@@ -673,34 +537,6 @@ class Host(CommonModelNameNotUnique, RelatedJobsMixin):
|
|||||||
def get_absolute_url(self, request=None):
|
def get_absolute_url(self, request=None):
|
||||||
return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request)
|
return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
def update_computed_fields(self, update_inventory=True, update_groups=True):
|
|
||||||
'''
|
|
||||||
Update model fields that are computed from database relationships.
|
|
||||||
'''
|
|
||||||
has_active_failures = bool(self.last_job_host_summary and
|
|
||||||
self.last_job_host_summary.failed)
|
|
||||||
active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES)
|
|
||||||
computed_fields = {
|
|
||||||
'has_active_failures': has_active_failures,
|
|
||||||
'has_inventory_sources': bool(active_inventory_sources.count()),
|
|
||||||
}
|
|
||||||
for field, value in computed_fields.items():
|
|
||||||
if getattr(self, field) != value:
|
|
||||||
setattr(self, field, value)
|
|
||||||
else:
|
|
||||||
computed_fields.pop(field)
|
|
||||||
if computed_fields:
|
|
||||||
self.save(update_fields=computed_fields.keys())
|
|
||||||
# Groups and inventory may also need to be updated when host fields
|
|
||||||
# change.
|
|
||||||
# NOTE: I think this is no longer needed
|
|
||||||
# if update_groups:
|
|
||||||
# for group in self.all_groups:
|
|
||||||
# group.update_computed_fields()
|
|
||||||
# if update_inventory:
|
|
||||||
# self.inventory.update_computed_fields(update_groups=False,
|
|
||||||
# update_hosts=False)
|
|
||||||
# Rebuild summary fields cache
|
|
||||||
variables_dict = VarsDictProperty('variables')
|
variables_dict = VarsDictProperty('variables')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -815,42 +651,6 @@ class Group(CommonModelNameNotUnique, RelatedJobsMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text=_('Hosts associated directly with this group.'),
|
help_text=_('Hosts associated directly with this group.'),
|
||||||
)
|
)
|
||||||
total_hosts = models.PositiveIntegerField(
|
|
||||||
default=0,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Total number of hosts directly or indirectly in this group.'),
|
|
||||||
)
|
|
||||||
has_active_failures = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Flag indicating whether this group has any hosts with active failures.'),
|
|
||||||
)
|
|
||||||
hosts_with_active_failures = models.PositiveIntegerField(
|
|
||||||
default=0,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Number of hosts in this group with active failures.'),
|
|
||||||
)
|
|
||||||
total_groups = models.PositiveIntegerField(
|
|
||||||
default=0,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Total number of child groups contained within this group.'),
|
|
||||||
)
|
|
||||||
groups_with_active_failures = models.PositiveIntegerField(
|
|
||||||
default=0,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Number of child groups within this group that have active failures.'),
|
|
||||||
)
|
|
||||||
has_inventory_sources = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
editable=False,
|
|
||||||
help_text=_('This field is deprecated and will be removed in a future release. '
|
|
||||||
'Flag indicating whether this group was created/updated from any external inventory sources.'),
|
|
||||||
)
|
|
||||||
inventory_sources = models.ManyToManyField(
|
inventory_sources = models.ManyToManyField(
|
||||||
'InventorySource',
|
'InventorySource',
|
||||||
related_name='groups',
|
related_name='groups',
|
||||||
@@ -925,32 +725,6 @@ class Group(CommonModelNameNotUnique, RelatedJobsMixin):
|
|||||||
mark_actual()
|
mark_actual()
|
||||||
activity_stream_delete(None, self)
|
activity_stream_delete(None, self)
|
||||||
|
|
||||||
def update_computed_fields(self):
|
|
||||||
'''
|
|
||||||
Update model fields that are computed from database relationships.
|
|
||||||
'''
|
|
||||||
active_hosts = self.all_hosts
|
|
||||||
failed_hosts = active_hosts.filter(last_job_host_summary__failed=True)
|
|
||||||
active_groups = self.all_children
|
|
||||||
# FIXME: May not be accurate unless we always update groups depth-first.
|
|
||||||
failed_groups = active_groups.filter(has_active_failures=True)
|
|
||||||
active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES)
|
|
||||||
computed_fields = {
|
|
||||||
'total_hosts': active_hosts.count(),
|
|
||||||
'has_active_failures': bool(failed_hosts.count()),
|
|
||||||
'hosts_with_active_failures': failed_hosts.count(),
|
|
||||||
'total_groups': active_groups.count(),
|
|
||||||
'groups_with_active_failures': failed_groups.count(),
|
|
||||||
'has_inventory_sources': bool(active_inventory_sources.count()),
|
|
||||||
}
|
|
||||||
for field, value in computed_fields.items():
|
|
||||||
if getattr(self, field) != value:
|
|
||||||
setattr(self, field, value)
|
|
||||||
else:
|
|
||||||
computed_fields.pop(field)
|
|
||||||
if computed_fields:
|
|
||||||
self.save(update_fields=computed_fields.keys())
|
|
||||||
|
|
||||||
variables_dict = VarsDictProperty('variables')
|
variables_dict = VarsDictProperty('variables')
|
||||||
|
|
||||||
def get_all_parents(self, except_pks=None):
|
def get_all_parents(self, except_pks=None):
|
||||||
@@ -1556,7 +1330,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions, CustomVirtualE
|
|||||||
self.update()
|
self.update()
|
||||||
if not getattr(_inventory_updates, 'is_updating', False):
|
if not getattr(_inventory_updates, 'is_updating', False):
|
||||||
if self.inventory is not None:
|
if self.inventory is not None:
|
||||||
self.inventory.update_computed_fields(update_groups=False, update_hosts=False)
|
self.inventory.update_computed_fields()
|
||||||
|
|
||||||
def _get_current_status(self):
|
def _get_current_status(self):
|
||||||
if self.source:
|
if self.source:
|
||||||
|
|||||||
@@ -1066,7 +1066,7 @@ class JobHostSummary(CreatedModifiedModel):
|
|||||||
processed = models.PositiveIntegerField(default=0, editable=False)
|
processed = models.PositiveIntegerField(default=0, editable=False)
|
||||||
rescued = models.PositiveIntegerField(default=0, editable=False)
|
rescued = models.PositiveIntegerField(default=0, editable=False)
|
||||||
skipped = models.PositiveIntegerField(default=0, editable=False)
|
skipped = models.PositiveIntegerField(default=0, editable=False)
|
||||||
failed = models.BooleanField(default=False, editable=False)
|
failed = models.BooleanField(default=False, editable=False, db_index=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
host = getattr_dne(self, 'host')
|
host = getattr_dne(self, 'host')
|
||||||
@@ -1101,7 +1101,6 @@ class JobHostSummary(CreatedModifiedModel):
|
|||||||
update_fields.append('last_job_host_summary_id')
|
update_fields.append('last_job_host_summary_id')
|
||||||
if update_fields:
|
if update_fields:
|
||||||
self.host.save(update_fields=update_fields)
|
self.host.save(update_fields=update_fields)
|
||||||
#self.host.update_computed_fields()
|
|
||||||
|
|
||||||
|
|
||||||
class SystemJobOptions(BaseModel):
|
class SystemJobOptions(BaseModel):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import pkg_resources
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
|
from django.db import connection
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models.signals import (
|
from django.db.models.signals import (
|
||||||
pre_save,
|
pre_save,
|
||||||
@@ -103,7 +104,7 @@ def emit_update_inventory_computed_fields(sender, **kwargs):
|
|||||||
except Inventory.DoesNotExist:
|
except Inventory.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
update_inventory_computed_fields.delay(inventory.id, True)
|
update_inventory_computed_fields.delay(inventory.id)
|
||||||
|
|
||||||
|
|
||||||
def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
||||||
@@ -124,7 +125,9 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if inventory is not None:
|
if inventory is not None:
|
||||||
update_inventory_computed_fields.delay(inventory.id, True)
|
connection.on_commit(
|
||||||
|
lambda: update_inventory_computed_fields.delay(inventory.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs):
|
def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs):
|
||||||
|
|||||||
@@ -588,7 +588,7 @@ def handle_work_error(task_id, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
def update_inventory_computed_fields(inventory_id):
|
||||||
'''
|
'''
|
||||||
Signal handler and wrapper around inventory.update_computed_fields to
|
Signal handler and wrapper around inventory.update_computed_fields to
|
||||||
prevent unnecessary recursive calls.
|
prevent unnecessary recursive calls.
|
||||||
@@ -599,7 +599,7 @@ def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
|||||||
return
|
return
|
||||||
i = i[0]
|
i = i[0]
|
||||||
try:
|
try:
|
||||||
i.update_computed_fields(update_hosts=should_update_hosts)
|
i.update_computed_fields()
|
||||||
except DatabaseError as e:
|
except DatabaseError as e:
|
||||||
if 'did not affect any rows' in str(e):
|
if 'did not affect any rows' in str(e):
|
||||||
logger.debug('Exiting duplicate update_inventory_computed_fields task.')
|
logger.debug('Exiting duplicate update_inventory_computed_fields task.')
|
||||||
@@ -642,7 +642,7 @@ def update_host_smart_inventory_memberships():
|
|||||||
logger.exception('Failed to update smart inventory memberships for {}'.format(smart_inventory.pk))
|
logger.exception('Failed to update smart inventory memberships for {}'.format(smart_inventory.pk))
|
||||||
# Update computed fields for changed inventories outside atomic action
|
# Update computed fields for changed inventories outside atomic action
|
||||||
for smart_inventory in changed_inventories:
|
for smart_inventory in changed_inventories:
|
||||||
smart_inventory.update_computed_fields(update_groups=False, update_hosts=False)
|
smart_inventory.update_computed_fields()
|
||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
@@ -1872,7 +1872,7 @@ class RunJob(BaseTask):
|
|||||||
except Inventory.DoesNotExist:
|
except Inventory.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
update_inventory_computed_fields.delay(inventory.id, True)
|
update_inventory_computed_fields.delay(inventory.id)
|
||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
@@ -2855,4 +2855,4 @@ def deep_copy_model_obj(
|
|||||||
), permission_check_func[2])
|
), permission_check_func[2])
|
||||||
permission_check_func(creater, copy_mapping.values())
|
permission_check_func(creater, copy_mapping.values())
|
||||||
if isinstance(new_obj, Inventory):
|
if isinstance(new_obj, Inventory):
|
||||||
update_inventory_computed_fields.delay(new_obj.id, True)
|
update_inventory_computed_fields.delay(new_obj.id)
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ from django.db import connection
|
|||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
|
||||||
def app_post_migration(sender, app_config, **kwargs):
|
def app_post_migration(sender, app_config, **kwargs):
|
||||||
@@ -23,3 +26,13 @@ if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def immediate_on_commit():
|
||||||
|
"""
|
||||||
|
Context manager executing transaction.on_commit() hooks immediately as
|
||||||
|
if the connection was in auto-commit mode.
|
||||||
|
"""
|
||||||
|
def on_commit(func):
|
||||||
|
func()
|
||||||
|
with mock.patch('django.db.connection.on_commit', side_effect=on_commit) as patch:
|
||||||
|
yield patch
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import base64
|
import base64
|
||||||
import contextlib
|
|
||||||
import json
|
import json
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
@@ -12,22 +10,11 @@ from awx.main.utils.encryption import decrypt_value, get_encryption_key
|
|||||||
from awx.api.versioning import reverse, drf_reverse
|
from awx.api.versioning import reverse, drf_reverse
|
||||||
from awx.main.models.oauth import (OAuth2Application as Application,
|
from awx.main.models.oauth import (OAuth2Application as Application,
|
||||||
OAuth2AccessToken as AccessToken)
|
OAuth2AccessToken as AccessToken)
|
||||||
|
from awx.main.tests.functional import immediate_on_commit
|
||||||
from awx.sso.models import UserEnterpriseAuth
|
from awx.sso.models import UserEnterpriseAuth
|
||||||
from oauth2_provider.models import RefreshToken
|
from oauth2_provider.models import RefreshToken
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def immediate_on_commit():
|
|
||||||
"""
|
|
||||||
Context manager executing transaction.on_commit() hooks immediately as
|
|
||||||
if the connection was in auto-commit mode.
|
|
||||||
"""
|
|
||||||
def on_commit(func):
|
|
||||||
func()
|
|
||||||
with mock.patch('django.db.connection.on_commit', side_effect=on_commit) as patch:
|
|
||||||
yield patch
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_personal_access_token_creation(oauth_application, post, alice):
|
def test_personal_access_token_creation(oauth_application, post, alice):
|
||||||
url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
|
url = drf_reverse('api:oauth_authorization_root_view') + 'token/'
|
||||||
|
|||||||
@@ -125,9 +125,9 @@ def project_playbooks():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def run_computed_fields_right_away(request):
|
def run_computed_fields_right_away(request):
|
||||||
|
|
||||||
def run_me(inventory_id, should_update_hosts=True):
|
def run_me(inventory_id):
|
||||||
i = Inventory.objects.get(id=inventory_id)
|
i = Inventory.objects.get(id=inventory_id)
|
||||||
i.update_computed_fields(update_hosts=should_update_hosts)
|
i.update_computed_fields()
|
||||||
|
|
||||||
mocked = mock.patch(
|
mocked = mock.patch(
|
||||||
'awx.main.signals.update_inventory_computed_fields.delay',
|
'awx.main.signals.update_inventory_computed_fields.delay',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from awx.main.signals import (
|
|||||||
# AWX models
|
# AWX models
|
||||||
from awx.main.models.organization import Organization
|
from awx.main.models.organization import Organization
|
||||||
from awx.main.models import ActivityStream, Job
|
from awx.main.models import ActivityStream, Job
|
||||||
|
from awx.main.tests.functional import immediate_on_commit
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -34,9 +35,10 @@ class TestComputedFields:
|
|||||||
|
|
||||||
def test_computed_fields_normal_use(self, mocker, inventory):
|
def test_computed_fields_normal_use(self, mocker, inventory):
|
||||||
job = Job.objects.create(name='fake-job', inventory=inventory)
|
job = Job.objects.create(name='fake-job', inventory=inventory)
|
||||||
with mocker.patch.object(update_inventory_computed_fields, 'delay'):
|
with immediate_on_commit():
|
||||||
job.delete()
|
with mocker.patch.object(update_inventory_computed_fields, 'delay'):
|
||||||
update_inventory_computed_fields.delay.assert_called_once_with(inventory.id, True)
|
job.delete()
|
||||||
|
update_inventory_computed_fields.delay.assert_called_once_with(inventory.id)
|
||||||
|
|
||||||
def test_disable_computed_fields(self, mocker, inventory):
|
def test_disable_computed_fields(self, mocker, inventory):
|
||||||
job = Job.objects.create(name='fake-job', inventory=inventory)
|
job = Job.objects.create(name='fake-job', inventory=inventory)
|
||||||
|
|||||||
@@ -283,13 +283,13 @@ class TestTaskImpact:
|
|||||||
|
|
||||||
def test_limit_task_impact(self, job_host_limit, run_computed_fields_right_away):
|
def test_limit_task_impact(self, job_host_limit, run_computed_fields_right_away):
|
||||||
job = job_host_limit(5, 2)
|
job = job_host_limit(5, 2)
|
||||||
job.inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory
|
job.inventory.update_computed_fields()
|
||||||
assert job.inventory.total_hosts == 5
|
assert job.inventory.total_hosts == 5
|
||||||
assert job.task_impact == 2 + 1 # forks becomes constraint
|
assert job.task_impact == 2 + 1 # forks becomes constraint
|
||||||
|
|
||||||
def test_host_task_impact(self, job_host_limit, run_computed_fields_right_away):
|
def test_host_task_impact(self, job_host_limit, run_computed_fields_right_away):
|
||||||
job = job_host_limit(3, 5)
|
job = job_host_limit(3, 5)
|
||||||
job.inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory
|
job.inventory.update_computed_fields()
|
||||||
assert job.task_impact == 3 + 1 # hosts becomes constraint
|
assert job.task_impact == 3 + 1 # hosts becomes constraint
|
||||||
|
|
||||||
def test_shard_task_impact(self, slice_job_factory, run_computed_fields_right_away):
|
def test_shard_task_impact(self, slice_job_factory, run_computed_fields_right_away):
|
||||||
@@ -304,6 +304,7 @@ class TestTaskImpact:
|
|||||||
len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts'])
|
len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts'])
|
||||||
for i in range(3)
|
for i in range(3)
|
||||||
] == [1, 1, 1]
|
] == [1, 1, 1]
|
||||||
|
jobs[0].inventory.update_computed_fields()
|
||||||
assert [job.task_impact for job in jobs] == [2, 2, 2] # plus one base task impact
|
assert [job.task_impact for job in jobs] == [2, 2, 2] # plus one base task impact
|
||||||
# Uneven distribution - first job takes the extra host
|
# Uneven distribution - first job takes the extra host
|
||||||
jobs[0].inventory.hosts.create(name='remainder_foo')
|
jobs[0].inventory.hosts.create(name='remainder_foo')
|
||||||
@@ -311,5 +312,5 @@ class TestTaskImpact:
|
|||||||
len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts'])
|
len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts'])
|
||||||
for i in range(3)
|
for i in range(3)
|
||||||
] == [2, 1, 1]
|
] == [2, 1, 1]
|
||||||
jobs[0].inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory
|
jobs[0].inventory.update_computed_fields()
|
||||||
assert [job.task_impact for job in jobs] == [3, 2, 2]
|
assert [job.task_impact for job in jobs] == [3, 2, 2]
|
||||||
|
|||||||
@@ -598,6 +598,11 @@ table, tbody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.List-staticColumnLayout--groups {
|
.List-staticColumnLayout--groups {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: @at-space @at-space-5x auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.List-staticColumnLayout--hostNestedGroups {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: @at-space @at-space-5x @at-space-5x auto;
|
grid-template-columns: @at-space @at-space-5x @at-space-5x auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default
|
|||||||
label: i18n._("Hosts")
|
label: i18n._("Hosts")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "/#/hosts?host_search=has_active_failures:true",
|
url: "/#/hosts?host_search=last_job_host_summary__failed:true",
|
||||||
number: scope.data.hosts.failed,
|
number: scope.data.hosts.failed,
|
||||||
label: i18n._("Failed Hosts"),
|
label: i18n._("Failed Hosts"),
|
||||||
isFailureCount: true
|
isFailureCount: true
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
*************************************************/
|
*************************************************/
|
||||||
export default
|
export default
|
||||||
['$scope', '$rootScope', '$state', '$stateParams', 'HostsRelatedGroupsList', 'InventoryUpdate',
|
['$scope', '$rootScope', '$state', '$stateParams', 'HostsRelatedGroupsList', 'InventoryUpdate',
|
||||||
'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'host', 'GroupsService',
|
||||||
'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'host', 'GroupsService',
|
|
||||||
function($scope, $rootScope, $state, $stateParams, HostsRelatedGroupsList, InventoryUpdate,
|
function($scope, $rootScope, $state, $stateParams, HostsRelatedGroupsList, InventoryUpdate,
|
||||||
CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
CancelSourceUpdate, rbacUiControlService, GetBasePath, Dataset, Find, qs, inventoryData, host, GroupsService){
|
||||||
GetHostsStatusMsg, Dataset, Find, qs, inventoryData, host, GroupsService){
|
|
||||||
|
|
||||||
let list = HostsRelatedGroupsList;
|
let list = HostsRelatedGroupsList;
|
||||||
|
|
||||||
@@ -29,27 +27,6 @@
|
|||||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||||
|
|
||||||
$scope.$watchCollection(list.name, function(){
|
|
||||||
_.forEach($scope[list.name], buildStatusIndicators);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildStatusIndicators(group){
|
|
||||||
if (group === undefined || group === null) {
|
|
||||||
group = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let hosts_status;
|
|
||||||
|
|
||||||
hosts_status = GetHostsStatusMsg({
|
|
||||||
active_failures: group.hosts_with_active_failures,
|
|
||||||
total_hosts: group.total_hosts,
|
|
||||||
inventory_id: $scope.inventory_id,
|
|
||||||
group_id: group.id
|
|
||||||
});
|
|
||||||
_.assign(group,
|
|
||||||
{hosts_status_tip: hosts_status.tooltip},
|
|
||||||
{hosts_status_class: hosts_status.class});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.editGroup = function(id){
|
$scope.editGroup = function(id){
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
export default
|
|
||||||
['i18n', function(i18n) {
|
|
||||||
return function(params) {
|
|
||||||
var active_failures = params.active_failures,
|
|
||||||
total_hosts = params.total_hosts,
|
|
||||||
tip, failures, html_class;
|
|
||||||
|
|
||||||
// Return values for use on host status indicator
|
|
||||||
|
|
||||||
if (active_failures > 0) {
|
|
||||||
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + i18n._(' with failed jobs.');
|
|
||||||
html_class = 'error';
|
|
||||||
failures = true;
|
|
||||||
} else {
|
|
||||||
failures = false;
|
|
||||||
if (total_hosts === 0) {
|
|
||||||
// no hosts
|
|
||||||
tip = i18n._("Contains 0 hosts.");
|
|
||||||
html_class = 'none';
|
|
||||||
} else {
|
|
||||||
// many hosts with 0 failures
|
|
||||||
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + i18n._('No job failures');
|
|
||||||
html_class = 'success';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tooltip: tip,
|
|
||||||
failures: failures,
|
|
||||||
'class': html_class
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
@@ -18,22 +18,6 @@
|
|||||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/groups/',
|
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/groups/',
|
||||||
layoutClass: 'List-staticColumnLayout--groups',
|
layoutClass: 'List-staticColumnLayout--groups',
|
||||||
actionHolderClass: 'List-actionHolder List-actionHolder--rootGroups',
|
actionHolderClass: 'List-actionHolder List-actionHolder--rootGroups',
|
||||||
staticColumns: [
|
|
||||||
{
|
|
||||||
field: 'failed_hosts',
|
|
||||||
content: {
|
|
||||||
label: '',
|
|
||||||
nosort: true,
|
|
||||||
mode: 'all',
|
|
||||||
iconOnly: true,
|
|
||||||
awToolTip: "{{ group.hosts_status_tip }}",
|
|
||||||
dataPlacement: "top",
|
|
||||||
icon: "{{ 'fa icon-job-' + group.hosts_status_class }}",
|
|
||||||
columnClass: 'status-column'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
label: i18n._('Groups'),
|
label: i18n._('Groups'),
|
||||||
|
|||||||
@@ -5,13 +5,11 @@
|
|||||||
*************************************************/
|
*************************************************/
|
||||||
export default
|
export default
|
||||||
['$scope', '$state', '$stateParams', 'listDefinition', 'InventoryUpdate',
|
['$scope', '$state', '$stateParams', 'listDefinition', 'InventoryUpdate',
|
||||||
'GroupsService', 'CancelSourceUpdate',
|
'GroupsService', 'CancelSourceUpdate', 'Dataset', 'inventoryData', 'canAdd',
|
||||||
'GetHostsStatusMsg', 'Dataset', 'inventoryData', 'canAdd',
|
'InventoryHostsStrings', '$transitions', 'GetBasePath', 'Rest',
|
||||||
'InventoryHostsStrings', '$transitions',
|
|
||||||
function($scope, $state, $stateParams, listDefinition, InventoryUpdate,
|
function($scope, $state, $stateParams, listDefinition, InventoryUpdate,
|
||||||
GroupsService, CancelSourceUpdate,
|
GroupsService, CancelSourceUpdate, Dataset, inventoryData, canAdd,
|
||||||
GetHostsStatusMsg, Dataset, inventoryData, canAdd,
|
InventoryHostsStrings, $transitions, GetBasePath, Rest){
|
||||||
InventoryHostsStrings, $transitions){
|
|
||||||
|
|
||||||
let list = listDefinition;
|
let list = listDefinition;
|
||||||
|
|
||||||
@@ -70,18 +68,6 @@
|
|||||||
group.isSelected = true;
|
group.isSelected = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let hosts_status;
|
|
||||||
|
|
||||||
hosts_status = GetHostsStatusMsg({
|
|
||||||
active_failures: group.hosts_with_active_failures,
|
|
||||||
total_hosts: group.total_hosts,
|
|
||||||
inventory_id: $scope.inventory_id,
|
|
||||||
group_id: group.id
|
|
||||||
});
|
|
||||||
_.assign(group,
|
|
||||||
{hosts_status_tip: hosts_status.tooltip},
|
|
||||||
{hosts_status_class: hosts_status.class});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.createGroup = function(){
|
$scope.createGroup = function(){
|
||||||
@@ -102,35 +88,51 @@
|
|||||||
$state.go('inventories.edit.groups.edit.nested_groups', {group_id: id});
|
$state.go('inventories.edit.groups.edit.nested_groups', {group_id: id});
|
||||||
};
|
};
|
||||||
$scope.deleteGroup = function(group){
|
$scope.deleteGroup = function(group){
|
||||||
$scope.toDelete = {};
|
const promises = [];
|
||||||
$scope.strings.deleteModal = {};
|
|
||||||
angular.extend($scope.toDelete, group);
|
Rest.setUrl(group.related.hosts);
|
||||||
if($scope.toDelete.total_groups === 0 && $scope.toDelete.total_hosts === 0) {
|
promises.push(Rest.get());
|
||||||
// This group doesn't have any child groups or hosts - the user is just trying to delete
|
|
||||||
// the group
|
Rest.setUrl(group.related.children);
|
||||||
$scope.deleteOption = "delete";
|
promises.push(Rest.get());
|
||||||
}
|
|
||||||
else {
|
Promise.all(promises)
|
||||||
$scope.strings.deleteModal.group = InventoryHostsStrings.get('deletegroup.GROUP', $scope.toDelete.total_groups);
|
.then(([hostResponse, groupResponse]) => {
|
||||||
$scope.strings.deleteModal.host = InventoryHostsStrings.get('deletegroup.HOST', $scope.toDelete.total_hosts);
|
$scope.toDelete = {};
|
||||||
|
$scope.strings.deleteModal = {};
|
||||||
|
$scope.toDelete.hostCount = _.get(hostResponse, ['data', 'count'], 0);
|
||||||
|
$scope.toDelete.groupCount = _.get(groupResponse, ['data', 'count'], 0);
|
||||||
|
angular.extend($scope.toDelete, group);
|
||||||
|
|
||||||
|
if($scope.toDelete.groupCount === 0 && $scope.toDelete.hostCount === 0) {
|
||||||
|
// This group doesn't have any child groups or hosts - the user is just trying to delete
|
||||||
|
// the group
|
||||||
|
$scope.deleteOption = "delete";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.strings.deleteModal.group = InventoryHostsStrings.get('deletegroup.GROUP', $scope.toDelete.groupCount);
|
||||||
|
$scope.strings.deleteModal.host = InventoryHostsStrings.get('deletegroup.HOST', $scope.toDelete.hostCount);
|
||||||
|
|
||||||
|
if($scope.toDelete.groupCount === 0 || $scope.toDelete.groupCount === 0) {
|
||||||
|
if($scope.toDelete.groupCount === 0) {
|
||||||
|
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_HOST', $scope.toDelete.hostCount);
|
||||||
|
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_HOST', $scope.toDelete.hostCount);
|
||||||
|
}
|
||||||
|
else if($scope.toDelete.hostCount === 0) {
|
||||||
|
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUP', $scope.toDelete.groupCount);
|
||||||
|
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUP', $scope.toDelete.groupCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.groupCount, hosts: $scope.toDelete.hostCount});
|
||||||
|
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.groupCount, hosts: $scope.toDelete.hostCount});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#group-delete-modal').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
if($scope.toDelete.total_groups === 0 || $scope.toDelete.total_hosts === 0) {
|
|
||||||
if($scope.toDelete.total_groups === 0) {
|
|
||||||
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_HOST', $scope.toDelete.total_hosts);
|
|
||||||
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_HOST', $scope.toDelete.total_hosts);
|
|
||||||
}
|
|
||||||
else if($scope.toDelete.total_hosts === 0) {
|
|
||||||
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUP', $scope.toDelete.total_groups);
|
|
||||||
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUP', $scope.toDelete.total_groups);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.total_groups, hosts: $scope.toDelete.total_hosts});
|
|
||||||
$scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.total_groups, hosts: $scope.toDelete.total_hosts});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#group-delete-modal').modal('show');
|
|
||||||
};
|
};
|
||||||
$scope.confirmDelete = function(){
|
$scope.confirmDelete = function(){
|
||||||
let reloadListStateParams = null;
|
let reloadListStateParams = null;
|
||||||
|
|||||||
@@ -18,12 +18,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="Modal-body">
|
<div class="Modal-body">
|
||||||
<div ng-show="toDelete.total_groups > 0 || toDelete.total_hosts > 0">
|
<div ng-show="toDelete.groupCount > 0 || toDelete.hostCount > 0">
|
||||||
<div>
|
<div>
|
||||||
<p class="Prompt-bodyQuery">Deleting group <em>{{ toDelete.name }}</em>.
|
<p class="Prompt-bodyQuery">Deleting group <em>{{ toDelete.name }}</em>.
|
||||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0"> <translate>This group contains</translate> {{ toDelete.total_groups }} {{:: strings.deleteModal.group }} <translate>and</translate> {{ toDelete.total_hosts }} {{:: strings.deleteModal.host }}. </span>
|
<span> <translate>This group contains at least one group or host</translate>.</span>
|
||||||
<span ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0"> <translate>This group contains</translate> {{ toDelete.total_hosts }} {{:: strings.deleteModal.host }}. </span>
|
|
||||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0"> <translate>This group contains</translate> {{ toDelete.total_groups }} {{:: strings.deleteModal.group }}. </span>
|
|
||||||
<translate>Delete or promote the group's children?</translate></p>
|
<translate>Delete or promote the group's children?</translate></p>
|
||||||
<div style="margin: 15px auto;">
|
<div style="margin: 15px auto;">
|
||||||
|
|
||||||
@@ -43,13 +41,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="toDelete.total_groups == 0 && toDelete.total_hosts == 0">
|
<div ng-show="toDelete.groupCount == 0 && toDelete.hostCount == 0">
|
||||||
<div class="Prompt-bodyQuery" translate>Are you sure you want to permanently delete the group below from the inventory?</div>
|
<div class="Prompt-bodyQuery" translate>Are you sure you want to permanently delete the group below from the inventory?</div>
|
||||||
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
|
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="Modal-footer">
|
<div class="Modal-footer">
|
||||||
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton" translate>CANCEL</a>
|
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton" translate>CANCEL</a>
|
||||||
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" ng-disabled="!deleteOption && (toDelete.total_groups > 0 || toDelete.total_hosts > 0)" class="btn Modal-footerButton Modal-errorButton" translate>DELETE</a>
|
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" ng-disabled="!deleteOption && (toDelete.groupCount > 0 || toDelete.hostCount > 0)" class="btn Modal-footerButton Modal-errorButton" translate>DELETE</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import groupAdd from './add/main';
|
|||||||
import groupEdit from './edit/main';
|
import groupEdit from './edit/main';
|
||||||
import groupFormDefinition from './groups.form';
|
import groupFormDefinition from './groups.form';
|
||||||
import groupListDefinition from './groups.list';
|
import groupListDefinition from './groups.list';
|
||||||
import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory';
|
|
||||||
import nestedGroups from './related/nested-groups/main';
|
import nestedGroups from './related/nested-groups/main';
|
||||||
import nestedHosts from './related/nested-hosts/main';
|
import nestedHosts from './related/nested-hosts/main';
|
||||||
|
|
||||||
@@ -22,5 +21,4 @@ export default
|
|||||||
nestedHosts.name
|
nestedHosts.name
|
||||||
])
|
])
|
||||||
.factory('GroupForm', groupFormDefinition)
|
.factory('GroupForm', groupFormDefinition)
|
||||||
.factory('GroupList', groupListDefinition)
|
.factory('GroupList', groupListDefinition);
|
||||||
.factory('GetHostsStatusMsg', GetHostsStatusMsg);
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
export default
|
export default
|
||||||
['$scope', '$rootScope', '$state', '$stateParams', 'NestedGroupListDefinition', 'InventoryUpdate',
|
['$scope', '$rootScope', '$state', '$stateParams', 'NestedGroupListDefinition', 'InventoryUpdate',
|
||||||
'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
||||||
'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'groupData', 'ProcessErrors',
|
'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'groupData', 'ProcessErrors',
|
||||||
'$transitions',
|
'$transitions',
|
||||||
function($scope, $rootScope, $state, $stateParams, NestedGroupListDefinition, InventoryUpdate,
|
function($scope, $rootScope, $state, $stateParams, NestedGroupListDefinition, InventoryUpdate,
|
||||||
GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
||||||
GetHostsStatusMsg, Dataset, Find, qs, inventoryData, canAdd, groupData, ProcessErrors,
|
Dataset, Find, qs, inventoryData, canAdd, groupData, ProcessErrors,
|
||||||
$transitions){
|
$transitions){
|
||||||
|
|
||||||
let list = NestedGroupListDefinition;
|
let list = NestedGroupListDefinition;
|
||||||
@@ -35,10 +35,6 @@
|
|||||||
|
|
||||||
$scope.inventory_id = $stateParams.inventory_id;
|
$scope.inventory_id = $stateParams.inventory_id;
|
||||||
|
|
||||||
$scope.$watchCollection(list.name, function(){
|
|
||||||
_.forEach($scope[list.name], processRow);
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('selectedOrDeselected', function(e, value) {
|
$scope.$on('selectedOrDeselected', function(e, value) {
|
||||||
let item = value.value;
|
let item = value.value;
|
||||||
|
|
||||||
@@ -57,30 +53,6 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRow(group){
|
|
||||||
if (group === undefined || group === null) {
|
|
||||||
group = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.forEach($scope.groupsSelected, function(selectedGroup){
|
|
||||||
if(selectedGroup.id === group.id) {
|
|
||||||
group.isSelected = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let hosts_status;
|
|
||||||
|
|
||||||
hosts_status = GetHostsStatusMsg({
|
|
||||||
active_failures: group.hosts_with_active_failures,
|
|
||||||
total_hosts: group.total_hosts,
|
|
||||||
inventory_id: $scope.inventory_id,
|
|
||||||
group_id: group.id
|
|
||||||
});
|
|
||||||
_.assign(group,
|
|
||||||
{hosts_status_tip: hosts_status.tooltip},
|
|
||||||
{hosts_status_class: hosts_status.class});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.disassociateGroup = function(group){
|
$scope.disassociateGroup = function(group){
|
||||||
$scope.toDisassociate = {};
|
$scope.toDisassociate = {};
|
||||||
angular.extend($scope.toDisassociate, group);
|
angular.extend($scope.toDisassociate, group);
|
||||||
|
|||||||
@@ -17,22 +17,6 @@
|
|||||||
trackBy: 'nested_group.id',
|
trackBy: 'nested_group.id',
|
||||||
basePath: 'api/v2/groups/{{$stateParams.group_id}}/children/',
|
basePath: 'api/v2/groups/{{$stateParams.group_id}}/children/',
|
||||||
layoutClass: 'List-staticColumnLayout--groups',
|
layoutClass: 'List-staticColumnLayout--groups',
|
||||||
staticColumns: [
|
|
||||||
{
|
|
||||||
field: 'failed_hosts',
|
|
||||||
content: {
|
|
||||||
label: '',
|
|
||||||
nosort: true,
|
|
||||||
mode: 'all',
|
|
||||||
iconOnly: true,
|
|
||||||
awToolTip: "{{ nested_group.hosts_status_tip }}",
|
|
||||||
dataPlacement: "top",
|
|
||||||
icon: "{{ 'fa icon-job-' + nested_group.hosts_status_class }}",
|
|
||||||
columnClass: 'status-column'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
label: i18n._('Groups'),
|
label: i18n._('Groups'),
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
export default
|
export default
|
||||||
['$scope', '$rootScope', '$state', '$stateParams', 'HostNestedGroupListDefinition', 'InventoryUpdate',
|
['$scope', '$rootScope', '$state', '$stateParams', 'HostNestedGroupListDefinition', 'InventoryUpdate',
|
||||||
'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath',
|
||||||
'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'ProcessErrors', 'host',
|
'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'ProcessErrors', 'host',
|
||||||
function($scope, $rootScope, $state, $stateParams, HostNestedGroupListDefinition, InventoryUpdate,
|
function($scope, $rootScope, $state, $stateParams, HostNestedGroupListDefinition, InventoryUpdate,
|
||||||
GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath,
|
||||||
GetHostsStatusMsg, Dataset, Find, qs, inventoryData, canAdd, ProcessErrors, host){
|
Dataset, Find, qs, inventoryData, canAdd, ProcessErrors, host){
|
||||||
|
|
||||||
let list = HostNestedGroupListDefinition;
|
let list = HostNestedGroupListDefinition;
|
||||||
|
|
||||||
@@ -26,10 +26,6 @@
|
|||||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||||
|
|
||||||
$scope.$watchCollection(list.name, function(){
|
|
||||||
_.forEach($scope[list.name], buildStatusIndicators);
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('selectedOrDeselected', function(e, value) {
|
$scope.$on('selectedOrDeselected', function(e, value) {
|
||||||
let item = value.value;
|
let item = value.value;
|
||||||
|
|
||||||
@@ -48,24 +44,6 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStatusIndicators(group){
|
|
||||||
if (group === undefined || group === null) {
|
|
||||||
group = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let hosts_status;
|
|
||||||
|
|
||||||
hosts_status = GetHostsStatusMsg({
|
|
||||||
active_failures: group.hosts_with_active_failures,
|
|
||||||
total_hosts: group.total_hosts,
|
|
||||||
inventory_id: $scope.inventory_id,
|
|
||||||
group_id: group.id
|
|
||||||
});
|
|
||||||
_.assign(group,
|
|
||||||
{hosts_status_tip: hosts_status.tooltip},
|
|
||||||
{hosts_status_class: hosts_status.class});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.associateGroup = function() {
|
$scope.associateGroup = function() {
|
||||||
$state.go('.associate');
|
$state.go('.associate');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
multiSelect: true,
|
multiSelect: true,
|
||||||
trackBy: 'nested_group.id',
|
trackBy: 'nested_group.id',
|
||||||
basePath: 'api/v2/hosts/{{$stateParams.host_id}}/all_groups/',
|
basePath: 'api/v2/hosts/{{$stateParams.host_id}}/all_groups/',
|
||||||
layoutClass: 'List-staticColumnLayout--groups',
|
layoutClass: 'List-staticColumnLayout--hostNestedGroups',
|
||||||
staticColumns: [
|
staticColumns: [
|
||||||
{
|
{
|
||||||
field: 'failed_hosts',
|
field: 'failed_hosts',
|
||||||
|
|||||||
@@ -18,57 +18,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="Modal-body">
|
<div class="Modal-body">
|
||||||
<div ng-show="toDelete.total_groups > 0 || toDelete.total_hosts > 0">
|
|
||||||
<div>
|
|
||||||
<p class="Prompt-bodyQuery"><translate>Deleting group</translate> <em>{{ toDelete.name }}</em>.
|
|
||||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0"> <translate>This group contains</translate> {{ toDelete.total_groups }} <translate>groups and</translate> {{ toDelete.total_hosts }} <translate>hosts</translate>. </span>
|
|
||||||
<span ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0"> <translate>This group contains</translate> {{ toDelete.total_hosts }} <translate>hosts</translate>. </span>
|
|
||||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0"> <translate>This group contains</translate> {{ toDelete.total_groups }} <translate>groups</translate>. </span>
|
|
||||||
<translate>Delete or promote the group's children?</translate></p>
|
|
||||||
<div style="margin: 15px auto;">
|
|
||||||
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="promote"> <translate>Promote groups and hosts</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="delete"> <translate>Delete groups and hosts</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="promote"> <translate>Promote groups</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="delete"> <translate>Delete groups</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="promote"> <translate>Promote hosts</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
|
|
||||||
<label>
|
|
||||||
<input type="radio" ng-model="deleteOption" value="delete"> <translate>Delete hosts</translate>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div ng-show="toDelete.total_groups == 0 && toDelete.total_hosts == 0">
|
|
||||||
<div class="Prompt-bodyQuery" translate>Are you sure you want to permanently delete the inventory source below from the inventory?</div>
|
|
||||||
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="Modal-footer">
|
<div class="Modal-footer">
|
||||||
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton" translate>CANCEL</a>
|
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton" translate>CANCEL</a>
|
||||||
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" class="btn Modal-footerButton Modal-errorButton" translate>DELETE</a>
|
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" class="btn Modal-footerButton Modal-errorButton" translate>DELETE</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user