mirror of
https://github.com/ansible/awx.git
synced 2026-03-04 18:21:03 -03:30
Merge branch 'master' into db-backup-unstable
This commit is contained in:
@@ -1775,13 +1775,14 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
passwords = self.context.get('passwords')
|
passwords = self.context.get('passwords')
|
||||||
data = self.context.get('data')
|
data = self.context.get('data')
|
||||||
|
|
||||||
|
credential = attrs.get('credential', None) or obj.credential
|
||||||
# fill passwords dict with request data passwords
|
# fill passwords dict with request data passwords
|
||||||
if obj.passwords_needed_to_start:
|
if credential and credential.passwords_needed:
|
||||||
try:
|
try:
|
||||||
for p in obj.passwords_needed_to_start:
|
for p in credential.passwords_needed:
|
||||||
passwords[p] = data.get(p)
|
passwords[p] = data[p]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise serializers.ValidationError(obj.passwords_needed_to_start)
|
raise serializers.ValidationError(credential.passwords_needed)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class AdHocCommand(UnifiedJob):
|
|||||||
@property
|
@property
|
||||||
def passwords_needed_to_start(self):
|
def passwords_needed_to_start(self):
|
||||||
'''Return list of password field names needed to start the job.'''
|
'''Return list of password field names needed to start the job.'''
|
||||||
if self.credential:
|
if self.credential and self.credential.active:
|
||||||
return self.credential.passwords_needed
|
return self.credential.passwords_needed
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class JobOptions(BaseModel):
|
|||||||
@property
|
@property
|
||||||
def passwords_needed_to_start(self):
|
def passwords_needed_to_start(self):
|
||||||
'''Return list of password field names needed to start the job.'''
|
'''Return list of password field names needed to start the job.'''
|
||||||
if self.credential:
|
if self.credential and self.credential.active:
|
||||||
return self.credential.passwords_needed
|
return self.credential.passwords_needed
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from .jobs_monolithic import * # noqa
|
from .jobs_monolithic import * # noqa
|
||||||
|
from .job_launch import * # noqa
|
||||||
from .survey_password import * # noqa
|
from .survey_password import * # noqa
|
||||||
from .start_cancel import * # noqa
|
from .start_cancel import * # noqa
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
|||||||
@@ -264,6 +264,21 @@ class BaseJobTestMixin(BaseTestMixin):
|
|||||||
password=TEST_SSH_KEY_DATA,
|
password=TEST_SSH_KEY_DATA,
|
||||||
created_by=self.user_sue,
|
created_by=self.user_sue,
|
||||||
)
|
)
|
||||||
|
self.cred_sue_ask = self.user_sue.credentials.create(
|
||||||
|
username='sue',
|
||||||
|
password='ASK',
|
||||||
|
created_by=self.user_sue,
|
||||||
|
)
|
||||||
|
self.cred_sue_ask_many = self.user_sue.credentials.create(
|
||||||
|
username='sue',
|
||||||
|
password='ASK',
|
||||||
|
become_method='sudo',
|
||||||
|
become_username='root',
|
||||||
|
become_password='ASK',
|
||||||
|
ssh_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
||||||
|
ssh_key_unlock='ASK',
|
||||||
|
created_by=self.user_sue,
|
||||||
|
)
|
||||||
self.cred_bob = self.user_bob.credentials.create(
|
self.cred_bob = self.user_bob.credentials.create(
|
||||||
username='bob',
|
username='bob',
|
||||||
password='ASK',
|
password='ASK',
|
||||||
|
|||||||
191
awx/main/tests/jobs/job_launch.py
Normal file
191
awx/main/tests/jobs/job_launch.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
|
# All Rights Reserved
|
||||||
|
|
||||||
|
# Python
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
# Django
|
||||||
|
import django
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.models import * # noqa
|
||||||
|
from .base import BaseJobTestMixin
|
||||||
|
|
||||||
|
__all__ = ['JobTemplateLaunchTest', 'JobTemplateLaunchPasswordsTest']
|
||||||
|
|
||||||
|
class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(JobTemplateLaunchTest, self).setUp()
|
||||||
|
|
||||||
|
self.url = reverse('api:job_template_list')
|
||||||
|
self.data = dict(
|
||||||
|
name = 'launched job template',
|
||||||
|
job_type = PERM_INVENTORY_DEPLOY,
|
||||||
|
inventory = self.inv_eng.pk,
|
||||||
|
project = self.proj_dev.pk,
|
||||||
|
credential = self.cred_sue.pk,
|
||||||
|
playbook = self.proj_dev.playbooks[0],
|
||||||
|
)
|
||||||
|
self.data_no_cred = dict(
|
||||||
|
name = 'launched job template no credential',
|
||||||
|
job_type = PERM_INVENTORY_DEPLOY,
|
||||||
|
inventory = self.inv_eng.pk,
|
||||||
|
project = self.proj_dev.pk,
|
||||||
|
playbook = self.proj_dev.playbooks[0],
|
||||||
|
)
|
||||||
|
self.data_cred_ask = dict(self.data)
|
||||||
|
self.data_cred_ask['name'] = 'launched job templated with ask passwords'
|
||||||
|
self.data_cred_ask['credential'] = self.cred_sue_ask.pk
|
||||||
|
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.url, self.data, expect=201)
|
||||||
|
self.launch_url = reverse('api:job_template_launch',
|
||||||
|
args=(response['id'],))
|
||||||
|
|
||||||
|
def test_launch_job_template(self):
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.data['name'] = 'something different'
|
||||||
|
response = self.post(self.url, self.data, expect=201)
|
||||||
|
detail_url = reverse('api:job_template_detail',
|
||||||
|
args=(response['id'],))
|
||||||
|
self.assertEquals(response['url'], detail_url)
|
||||||
|
|
||||||
|
def test_no_cred_update_template(self):
|
||||||
|
# You can still post the job template without a credential, just can't launch it without one
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.url, self.data_no_cred, expect=201)
|
||||||
|
detail_url = reverse('api:job_template_detail',
|
||||||
|
args=(response['id'],))
|
||||||
|
self.assertEquals(response['url'], detail_url)
|
||||||
|
|
||||||
|
def test_invalid_auth_unauthorized(self):
|
||||||
|
# Invalid auth can't trigger the launch endpoint
|
||||||
|
self.check_invalid_auth(self.launch_url, {}, methods=('post',))
|
||||||
|
|
||||||
|
def test_credential_implicit(self):
|
||||||
|
# Implicit, attached credentials
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.launch_url, {}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertTrue(j.status == 'new')
|
||||||
|
|
||||||
|
def test_credential_explicit(self):
|
||||||
|
# Explicit, credential
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.cred_sue.mark_inactive()
|
||||||
|
response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertEqual(j.status, 'new')
|
||||||
|
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
||||||
|
|
||||||
|
def test_credential_explicit_via_credential_id(self):
|
||||||
|
# Explicit, credential
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.cred_sue.mark_inactive()
|
||||||
|
response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertEqual(j.status, 'new')
|
||||||
|
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
||||||
|
|
||||||
|
def test_credential_override(self):
|
||||||
|
# Explicit, credential
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertEqual(j.status, 'new')
|
||||||
|
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
||||||
|
|
||||||
|
def test_credential_override_via_credential_id(self):
|
||||||
|
# Explicit, credential
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertEqual(j.status, 'new')
|
||||||
|
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
||||||
|
|
||||||
|
def test_bad_credential_launch_fail(self):
|
||||||
|
# Can't launch a job template without a credential defined (or if we
|
||||||
|
# pass an invalid/inactive credential value).
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.cred_sue.mark_inactive()
|
||||||
|
self.post(self.launch_url, {}, expect=400)
|
||||||
|
self.post(self.launch_url, {'credential': 0}, expect=400)
|
||||||
|
self.post(self.launch_url, {'credential_id': 0}, expect=400)
|
||||||
|
self.post(self.launch_url, {'credential': 'one'}, expect=400)
|
||||||
|
self.post(self.launch_url, {'credential_id': 'one'}, expect=400)
|
||||||
|
self.cred_doug.mark_inactive()
|
||||||
|
self.post(self.launch_url, {'credential': self.cred_doug.pk}, expect=400)
|
||||||
|
self.post(self.launch_url, {'credential_id': self.cred_doug.pk}, expect=400)
|
||||||
|
|
||||||
|
def test_no_project_fail(self):
|
||||||
|
# Job Templates without projects can not be launched
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.data['name'] = "missing proj"
|
||||||
|
response = self.post(self.url, self.data, expect=201)
|
||||||
|
jt = JobTemplate.objects.get(pk=response['id'])
|
||||||
|
jt.project = None
|
||||||
|
jt.save()
|
||||||
|
launch_url2 = reverse('api:job_template_launch',
|
||||||
|
args=(response['id'],))
|
||||||
|
self.post(launch_url2, {}, expect=400)
|
||||||
|
|
||||||
|
def test_no_inventory_fail(self):
|
||||||
|
# Job Templates without inventory can not be launched
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.data['name'] = "missing inv"
|
||||||
|
response = self.post(self.url, self.data, expect=201)
|
||||||
|
jt = JobTemplate.objects.get(pk=response['id'])
|
||||||
|
jt.inventory = None
|
||||||
|
jt.save()
|
||||||
|
launch_url3 = reverse('api:job_template_launch',
|
||||||
|
args=(response['id'],))
|
||||||
|
self.post(launch_url3, {}, expect=400)
|
||||||
|
|
||||||
|
def test_deleted_credential_fail(self):
|
||||||
|
# Job Templates with deleted credentials cannot be launched.
|
||||||
|
self.cred_sue.mark_inactive()
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.post(self.launch_url, {}, expect=400)
|
||||||
|
|
||||||
|
class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(JobTemplateLaunchPasswordsTest, self).setUp()
|
||||||
|
|
||||||
|
self.url = reverse('api:job_template_list')
|
||||||
|
self.data = dict(
|
||||||
|
name = 'launched job template',
|
||||||
|
job_type = PERM_INVENTORY_DEPLOY,
|
||||||
|
inventory = self.inv_eng.pk,
|
||||||
|
project = self.proj_dev.pk,
|
||||||
|
credential = self.cred_sue_ask.pk,
|
||||||
|
playbook = self.proj_dev.playbooks[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.url, self.data, expect=201)
|
||||||
|
self.launch_url = reverse('api:job_template_launch',
|
||||||
|
args=(response['id'],))
|
||||||
|
|
||||||
|
# should return explicit credentials required passwords
|
||||||
|
def test_explicit_cred_with_ask_passwords_fail(self):
|
||||||
|
passwords_required = ['ssh_password', 'become_password', 'ssh_key_unlock']
|
||||||
|
# Job Templates with deleted credentials cannot be launched.
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
self.cred_sue_ask.mark_inactive()
|
||||||
|
response = self.post(self.launch_url, {'credential_id': self.cred_sue_ask_many.pk}, expect=400)
|
||||||
|
for p in passwords_required:
|
||||||
|
self.assertIn(p, response['passwords_needed_to_start'])
|
||||||
|
self.assertEqual(len(passwords_required), len(response['passwords_needed_to_start']))
|
||||||
|
|
||||||
|
def test_explicit_cred_with_ask_password(self):
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.launch_url, {'ssh_password': 'whatever'}, expect=202)
|
||||||
|
j = Job.objects.get(pk=response['job'])
|
||||||
|
self.assertEqual(j.status, 'new')
|
||||||
|
|
||||||
|
def test_explicit_cred_with_ask_password_empty_string_fail(self):
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.post(self.launch_url, {'ssh_password': ''}, expect=400)
|
||||||
|
self.assertIn('ssh_password', response['passwords_needed_to_start'])
|
||||||
|
|
||||||
@@ -443,105 +443,6 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
|||||||
with self.current_user(self.user_doug):
|
with self.current_user(self.user_doug):
|
||||||
self.get(detail_url, expect=403)
|
self.get(detail_url, expect=403)
|
||||||
|
|
||||||
def test_launch_job_template(self):
|
|
||||||
url = reverse('api:job_template_list')
|
|
||||||
data = dict(
|
|
||||||
name = 'launched job template',
|
|
||||||
job_type = PERM_INVENTORY_DEPLOY,
|
|
||||||
inventory = self.inv_eng.pk,
|
|
||||||
project = self.proj_dev.pk,
|
|
||||||
credential = self.cred_sue.pk,
|
|
||||||
playbook = self.proj_dev.playbooks[0],
|
|
||||||
)
|
|
||||||
data_no_cred = dict(
|
|
||||||
name = 'launched job template no credential',
|
|
||||||
job_type = PERM_INVENTORY_DEPLOY,
|
|
||||||
inventory = self.inv_eng.pk,
|
|
||||||
project = self.proj_dev.pk,
|
|
||||||
playbook = self.proj_dev.playbooks[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(url, data, expect=201)
|
|
||||||
detail_url = reverse('api:job_template_detail',
|
|
||||||
args=(response['id'],))
|
|
||||||
self.assertEquals(response['url'], detail_url)
|
|
||||||
|
|
||||||
launch_url = reverse('api:job_template_launch',
|
|
||||||
args=(response['id'],))
|
|
||||||
|
|
||||||
# You can still post the job template without a credential, just can't launch it without one
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(url, data_no_cred, expect=201)
|
|
||||||
detail_url = reverse('api:job_template_detail',
|
|
||||||
args=(response['id'],))
|
|
||||||
self.assertEquals(response['url'], detail_url)
|
|
||||||
|
|
||||||
no_launch_url = reverse('api:job_template_launch',
|
|
||||||
args=(response['id'],))
|
|
||||||
# Invalid auth can't trigger the launch endpoint
|
|
||||||
self.check_invalid_auth(launch_url, {}, methods=('post',))
|
|
||||||
|
|
||||||
# Implicit, attached credentials
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(launch_url, {}, expect=202)
|
|
||||||
j = Job.objects.get(pk=response['job'])
|
|
||||||
self.assertTrue(j.status == 'new')
|
|
||||||
|
|
||||||
# Explicit, override credentials
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(launch_url, {'credential': self.cred_doug.pk}, expect=202)
|
|
||||||
j = Job.objects.get(pk=response['job'])
|
|
||||||
self.assertTrue(j.status == 'new')
|
|
||||||
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
|
||||||
|
|
||||||
# Explicit, override credentials
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(launch_url, {'credential_id': self.cred_doug.pk}, expect=202)
|
|
||||||
j = Job.objects.get(pk=response['job'])
|
|
||||||
self.assertTrue(j.status == 'new')
|
|
||||||
self.assertEqual(j.credential.pk, self.cred_doug.pk)
|
|
||||||
|
|
||||||
# Can't launch a job template without a credential defined (or if we
|
|
||||||
# pass an invalid/inactive credential value).
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(no_launch_url, {}, expect=400)
|
|
||||||
response = self.post(no_launch_url, {'credential': 0}, expect=400)
|
|
||||||
response = self.post(no_launch_url, {'credential_id': 0}, expect=400)
|
|
||||||
response = self.post(no_launch_url, {'credential': 'one'}, expect=400)
|
|
||||||
response = self.post(no_launch_url, {'credential_id': 'one'}, expect=400)
|
|
||||||
self.cred_doug.mark_inactive()
|
|
||||||
response = self.post(no_launch_url, {'credential': self.cred_doug.pk}, expect=400)
|
|
||||||
response = self.post(no_launch_url, {'credential_id': self.cred_doug.pk}, expect=400)
|
|
||||||
|
|
||||||
# Job Templates without projects can not be launched
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
data['name'] = "missing proj"
|
|
||||||
response = self.post(url, data, expect=201)
|
|
||||||
jt = JobTemplate.objects.get(pk=response['id'])
|
|
||||||
jt.project = None
|
|
||||||
jt.save()
|
|
||||||
launch_url2 = reverse('api:job_template_launch',
|
|
||||||
args=(response['id'],))
|
|
||||||
self.post(launch_url2, {}, expect=400)
|
|
||||||
|
|
||||||
# Job Templates without inventory can not be launched
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
data['name'] = "missing inv"
|
|
||||||
response = self.post(url, data, expect=201)
|
|
||||||
jt = JobTemplate.objects.get(pk=response['id'])
|
|
||||||
jt.inventory = None
|
|
||||||
jt.save()
|
|
||||||
launch_url3 = reverse('api:job_template_launch',
|
|
||||||
args=(response['id'],))
|
|
||||||
self.post(launch_url3, {}, expect=400)
|
|
||||||
|
|
||||||
# Job Templates with deleted credentials cannot be launched.
|
|
||||||
self.cred_sue.mark_inactive()
|
|
||||||
with self.current_user(self.user_sue):
|
|
||||||
response = self.post(launch_url, {}, expect=400)
|
|
||||||
|
|
||||||
|
|
||||||
class JobTest(BaseJobTestMixin, django.test.TestCase):
|
class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||||
|
|
||||||
def test_get_job_list(self):
|
def test_get_job_list(self):
|
||||||
|
|||||||
@@ -438,8 +438,9 @@ export function InventoriesAdd($scope, $rootScope, $compile, $location, $log, $r
|
|||||||
}
|
}
|
||||||
$scope.removeUpdateInventoryVariables = $scope.$on('UpdateInventoryVariables', function(e, data) {
|
$scope.removeUpdateInventoryVariables = $scope.$on('UpdateInventoryVariables', function(e, data) {
|
||||||
var inventory_id = data.id;
|
var inventory_id = data.id;
|
||||||
|
var vars_to_send = {"variables": json_data};
|
||||||
Rest.setUrl(data.related.variable_data);
|
Rest.setUrl(data.related.variable_data);
|
||||||
Rest.put(json_data)
|
Rest.put(vars_to_send)
|
||||||
.success(function () {
|
.success(function () {
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
$location.path('/inventories/' + inventory_id + '/manage');
|
$location.path('/inventories/' + inventory_id + '/manage');
|
||||||
|
|||||||
@@ -1170,7 +1170,12 @@ export default
|
|||||||
color: '#ff5850'
|
color: '#ff5850'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
scope.graph_data = graph_data;
|
||||||
|
var total_count = 0, gd_obj;
|
||||||
|
for (gd_obj in graph_data) {
|
||||||
|
total_count += graph_data[gd_obj].value;
|
||||||
|
}
|
||||||
|
scope.total_count_for_graph = total_count;
|
||||||
// Adjust the size
|
// Adjust the size
|
||||||
width = $('#job-summary-container .job_well').width();
|
width = $('#job-summary-container .job_well').width();
|
||||||
height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15;
|
height = $('#job-summary-container .job_well').height() - $('#summary-well-top-section').height() - $('#graph-section .header').outerHeight() - 15;
|
||||||
@@ -1187,7 +1192,7 @@ export default
|
|||||||
}
|
}
|
||||||
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height);
|
svg = d3.select("#graph-section").append("svg").attr("width", svg_width).attr("height", svg_height);
|
||||||
svg.append("g").attr("id","completedHostsDonut");
|
svg.append("g").attr("id","completedHostsDonut");
|
||||||
Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2), Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4);
|
Donut3D.draw("completedHostsDonut", graph_data, Math.floor(svg_width / 2), Math.floor(svg_height / 2) - 35, Math.floor(svg_radius * 0.50), Math.floor(svg_radius * 0.25), 18, 0.4);
|
||||||
$('#graph-section .header .legend').show();
|
$('#graph-section .header .legend').show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -545,6 +545,12 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host_summary_row {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: -15px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
#job-detail-container {
|
#job-detail-container {
|
||||||
#job-status-form {
|
#job-status-form {
|
||||||
|
|||||||
@@ -257,7 +257,31 @@
|
|||||||
<td class="col-lg-1 col-md-2 col-sm-2 col-xs-2 status-column" aw-tool-tip="{{ task.status_tip }}"
|
<td class="col-lg-1 col-md-2 col-sm-2 col-xs-2 status-column" aw-tool-tip="{{ task.status_tip }}"
|
||||||
data-tip-watch="task.status_tip" data-placement="top"><i class="fa icon-job-{{ task.status }}"></i></td>
|
data-tip-watch="task.status_tip" data-placement="top"><i class="fa icon-job-{{ task.status }}"></i></td>
|
||||||
<td class="col-lg-3 col-md-3 col-sm-6 col-xs-4" id="">{{ task.name }}</td>
|
<td class="col-lg-3 col-md-3 col-sm-6 col-xs-4" id="">{{ task.name }}</td>
|
||||||
<td class="col-lg-4 col-md-3 hidden-sm hidden-xs"><div class="status-bar"><div class="successful-hosts inner-bar" id="{{ task.id }}-successful-bar" aw-tool-tip="Hosts OK" data-placement="top" ng-style="task.successfulStyle">{{ task.successfulCount }}</div><div class="changed-hosts inner-bar" id="{{ task.id }}-changed-bar" aw-tool-tip="Hosts Changed" data-placement="top" ng-style="task.changedStyle">{{ task.changedCount }}</div><div class="skipped-hosts inner-bar" id="{{ task.id }}-skipped-bar" aw-tool-tip="Hosts Skipped" data-placement="top" ng-style="task.skippedStyle">{{ task.skippedCount }}</div><div class="failed-hosts inner-bar" id="{{ task.id }}-failed-bar" aw-tool-tip="Hosts Failed" data-placement="top" ng-style="task.failedStyle">{{ task.failedCount }}</div><div class="unreachable-hosts inner-bar" id="{{ task.id }}-unreachable-hosts-bar" aw-tool-tip="Hosts Unreachable" data-placement="top" ng-style="task.unreachableStyle">{{ task.unreachableCount }}</div><div class="missing-hosts inner-bar" id="{{ task.id }}-misssing-hosts-bar" ng-style="task.missingStyle">{{ task.missingCount }}</div><div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found" data-placement="top" style="width: 100%;" ng-show="task.status === 'no-matching-hosts'">No matching hosts</div></div></td>
|
<td class="col-lg-4 col-md-3 hidden-sm hidden-xs">
|
||||||
|
<div class="status-bar">
|
||||||
|
<div class="successful-hosts inner-bar" id="{{ task.id }}-successful-bar" aw-tool-tip="{{ task.successfulCount }} Hosts OK" data-placement="top" ng-style="task.successfulStyle">
|
||||||
|
{{ task.successfulCount }}
|
||||||
|
</div>
|
||||||
|
<div class="changed-hosts inner-bar" id="{{ task.id }}-changed-bar" aw-tool-tip="{{ task.changedCount }} Hosts Changed" data-placement="top" ng-style="task.changedStyle">
|
||||||
|
{{ task.changedCount }}
|
||||||
|
</div>
|
||||||
|
<div class="skipped-hosts inner-bar" id="{{ task.id }}-skipped-bar" aw-tool-tip="{{ task.skippedCount }} Hosts Skipped" data-placement="top" ng-style="task.skippedStyle">
|
||||||
|
{{ task.skippedCount }}
|
||||||
|
</div>
|
||||||
|
<div class="failed-hosts inner-bar" id="{{ task.id }}-failed-bar" aw-tool-tip="{{ task.failedCount }} Hosts Failed" data-placement="top" ng-style="task.failedStyle">
|
||||||
|
{{ task.failedCount }}
|
||||||
|
</div>
|
||||||
|
<div class="unreachable-hosts inner-bar" id="{{ task.id }}-unreachable-hosts-bar" aw-tool-tip="{{ task.unreachableCount }} Hosts Unreachable" data-placement="top" ng-style="task.unreachableStyle">
|
||||||
|
{{ task.unreachableCount }}
|
||||||
|
</div>
|
||||||
|
<div class="missing-hosts inner-bar" id="{{ task.id }}-misssing-hosts-bar" aw-tool-tip="{{ task.missingCount }} Hosts Missing" data-placement="top" ng-style="task.missingStyle">
|
||||||
|
{{ task.missingCount }}
|
||||||
|
</div>
|
||||||
|
<div class="no-matching-hosts inner-bar" id="{{ task.id }}-{{ task.play_id }}-no-matching-hosts-bar" aw-tool-tip="No matching hosts were found" data-placement="top" style="width: 100%;" ng-show="task.status === 'no-matching-hosts'">
|
||||||
|
No matching hosts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="taskList.length === 0 && waiting">
|
<tr ng-show="taskList.length === 0 && waiting">
|
||||||
<td colspan="5" class="col-lg-12 loading-info">Waiting...</td>
|
<td colspan="5" class="col-lg-12 loading-info">Waiting...</td>
|
||||||
@@ -401,10 +425,10 @@
|
|||||||
<tr ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}">
|
<tr ng-repeat="host in summaryList = (hosts) track by $index" id="{{ host.id }}">
|
||||||
<td class="name col-lg-6 col-md-6 col-sm-6 col-xs-6"><a href="" ng-click="hostEventsViewer(host.id, host.name)" aw-tool-tip="View all events" data-placement="top">{{ host.name }}</a></td>
|
<td class="name col-lg-6 col-md-6 col-sm-6 col-xs-6"><a href="" ng-click="hostEventsViewer(host.id, host.name)" aw-tool-tip="View all events" data-placement="top">{{ host.name }}</a></td>
|
||||||
<td class="col-lg-5 col-md-5 col-sm-5 col-xs-5 badge-column">
|
<td class="col-lg-5 col-md-5 col-sm-5 col-xs-5 badge-column">
|
||||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'ok')" aw-tool-tip="OK" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'ok')" aw-tool-tip="{{ host.ok }} Hosts Okay" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok }}</span></a>
|
||||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'changed')" aw-tool-tip="Changed" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
|
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'changed')" aw-tool-tip="{{ host.changed }} Hosts Changed" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
|
||||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'unreachable')" aw-tool-tip="Unreachable" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
|
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'unreachable')" aw-tool-tip="{{ host.unreachable }} Hosts Unreachable" data-placement="top" ng-hide="host.unreachable == 0"><span class="badge unreachable-hosts">{{ host.unreachable }}</span></a>
|
||||||
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'failed')" aw-tool-tip="Failed" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a></td>
|
<a href="" ng-click="hostEventsViewer(host.id, host.name, 'failed')" aw-tool-tip="{{ host.failed }} Hosts Failed" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failed }}</span></a></td>
|
||||||
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-1"><a ng-show="host.id" href="" ng-click="editHost(host.id)" aw-tool-tip="Edit host" data-placement="top"><i class="fa fa-pencil"></i></a></td>
|
<td class="col-lg-1 col-md-1 col-sm-1 col-xs-1"><a ng-show="host.id" href="" ng-click="editHost(host.id)" aw-tool-tip="Edit host" data-placement="top"><i class="fa fa-pencil"></i></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-show="summaryList.length === 0 && waiting">
|
<tr ng-show="summaryList.length === 0 && waiting">
|
||||||
@@ -423,9 +447,14 @@
|
|||||||
</div><!-- section -->
|
</div><!-- section -->
|
||||||
</div><!-- summary-well-top-section -->
|
</div><!-- summary-well-top-section -->
|
||||||
|
|
||||||
|
<div class="row host_summary_row">
|
||||||
|
<div class="title">Host Summary</div>
|
||||||
|
<div ng-repeat="graph_data_object in graph_data">
|
||||||
|
<span>{{ (graph_data_object.value/total_count_for_graph) * 100 | number : 1 }}% of hosts <span style="color: {{ graph_data_object.color }}">{{ graph_data_object.label }}</span>.<br /></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="graph-section" class="section">
|
<div id="graph-section" class="section">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">Host Summary</div>
|
|
||||||
<div class="legend" style="display: none;"><i class="fa fa-circle successful-hosts-color"></i> OK <i class="fa fa-circle changed-hosts-color"></i> Changed
|
<div class="legend" style="display: none;"><i class="fa fa-circle successful-hosts-color"></i> OK <i class="fa fa-circle changed-hosts-color"></i> Changed
|
||||||
<i class="fa fa-circle unreachable-hosts-color"></i> Unreachable <i class="fa fa-circle failed-hosts-color"></i> Failed</div>
|
<i class="fa fa-circle unreachable-hosts-color"></i> Unreachable <i class="fa fa-circle failed-hosts-color"></i> Failed</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user