Merge remote branch 'origin/master'

This commit is contained in:
Michael DeHaan 2013-06-28 12:02:35 -04:00
commit 5d22ebd5b5
35 changed files with 280 additions and 154 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
awx/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1118,8 +1118,8 @@ class JobEvent(models.Model):
return None
def save(self, *args, **kwargs):
self.failed = bool(self.event in self.FAILED_EVENTS)
# FIXME: Propagage failed flag to parent events.
if self.event in self.FAILED_EVENTS:
self.failed = True
try:
if not self.host and self.event_data.get('host', ''):
self.host = self.job.inventory.hosts.get(name=self.event_data['host'])
@ -1129,9 +1129,18 @@ class JobEvent(models.Model):
self.task = self.event_data.get('task', '')
self.parent = self._find_parent()
super(JobEvent, self).save(*args, **kwargs)
self.update_parent_failed()
self.update_hosts()
self.update_host_summary_from_stats()
def update_parent_failed(self):
# Propagage failed flag to parent events.
if self.failed and self.parent and not self.parent.failed:
p = self.parent
p.failed = True
p.save()
p.update_parent_failed()
def update_hosts(self, extra_hosts=None):
extra_hosts = extra_hosts or []
hostnames = set()

View File

@ -432,7 +432,7 @@ class JobEventSerializer(BaseSerializer):
model = JobEvent
fields = ('id', 'url', 'created', 'job', 'event', 'event_display',
'event_data', 'failed', 'host', 'related', 'summary_fields',
'parent')
'parent', 'play', 'task')
def get_related(self, obj):
res = super(JobEventSerializer, self).get_related(obj)

View File

@ -213,6 +213,53 @@ class RunJobTest(BaseCeleryTest):
'expected no traceback, got:\n%s' %
job.result_traceback)
def check_job_events(self, job, runner_status='ok', plays=1, tasks=1):
job_events = job.job_events.all()
for job_event in job_events:
unicode(job_event) # For test coverage.
job_event.save()
should_be_failed = bool(runner_status not in ('ok', 'skipped'))
host_pks = set([self.host.pk])
qs = job_events.filter(event='playbook_on_start')
self.assertEqual(qs.count(), 1)
for evt in qs:
self.assertFalse(evt.host, evt)
self.assertFalse(evt.play, evt)
self.assertFalse(evt.task, evt)
self.assertEqual(evt.failed, should_be_failed)
self.assertEqual(set(evt.hosts.values_list('pk', flat=True)),
host_pks)
qs = job_events.filter(event='playbook_on_play_start')
self.assertEqual(qs.count(), plays)
for evt in qs:
self.assertFalse(evt.host, evt)
self.assertTrue(evt.play, evt)
self.assertFalse(evt.task, evt)
self.assertEqual(evt.failed, should_be_failed)
self.assertEqual(set(evt.hosts.values_list('pk', flat=True)),
host_pks)
qs = job_events.filter(event='playbook_on_task_start')
self.assertEqual(qs.count(), tasks)
for evt in qs:
self.assertFalse(evt.host, evt)
self.assertTrue(evt.play, evt)
self.assertTrue(evt.task, evt)
self.assertEqual(evt.failed, should_be_failed)
self.assertEqual(set(evt.hosts.values_list('pk', flat=True)),
host_pks)
qs = job_events.filter(event=('runner_on_%s' % runner_status))
self.assertEqual(qs.count(), tasks)
for evt in qs:
self.assertEqual(evt.host, self.host)
self.assertTrue(evt.play, evt)
self.assertTrue(evt.task, evt)
self.assertEqual(evt.failed, should_be_failed)
self.assertEqual(set(evt.hosts.values_list('pk', flat=True)),
host_pks)
qs = job_events.filter(event__startswith='runner_')
qs = qs.exclude(event=('runner_on_%s' % runner_status))
self.assertEqual(qs.count(), 0)
def test_run_job(self):
self.create_test_project(TEST_PLAYBOOK)
job_template = self.create_test_job_template()
@ -223,17 +270,7 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'successful')
job_events = job.job_events.all()
for job_event in job_events:
unicode(job_event) # For test coverage.
job_event.save()
self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2)
self.assertEqual(job_events.filter(event='runner_on_ok').count(), 2)
for evt in job_events.filter(event='runner_on_ok'):
self.assertEqual(evt.host, self.host)
self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1)
self.check_job_events(job, 'ok', 1, 2)
for job_host_summary in job.job_host_summaries.all():
unicode(job_host_summary) # For test coverage.
self.assertFalse(job_host_summary.failed)
@ -261,14 +298,7 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'successful')
job_events = job.job_events.all()
self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 2)
self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 2)
for evt in job_events.filter(event='runner_on_skipped'):
self.assertEqual(evt.host, self.host)
self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1)
self.check_job_events(job, 'skipped', 1, 2)
for job_host_summary in job.job_host_summaries.all():
self.assertFalse(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary)
@ -295,13 +325,7 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'failed')
job_events = job.job_events.all()
self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1)
self.assertEqual(job_events.filter(event='runner_on_failed').count(), 1)
self.assertEqual(job_events.get(event='runner_on_failed').host, self.host)
self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1)
self.check_job_events(job, 'failed', 1, 1)
for job_host_summary in job.job_host_summaries.all():
self.assertTrue(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary)
@ -330,13 +354,7 @@ class RunJobTest(BaseCeleryTest):
# Since we don't actually run the task, the --check should indicate
# everything is successful.
self.check_job_result(job, 'successful')
job_events = job.job_events.all()
self.assertEqual(job_events.filter(event='playbook_on_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_play_start').count(), 1)
self.assertEqual(job_events.filter(event='playbook_on_task_start').count(), 1)
self.assertEqual(job_events.filter(event='runner_on_skipped').count(), 1)
self.assertEqual(job_events.get(event='runner_on_skipped').host, self.host)
self.assertEqual(job_events.filter(event='playbook_on_stats').count(), 1)
self.check_job_events(job, 'skipped', 1, 1)
for job_host_summary in job.job_host_summaries.all():
self.assertFalse(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary)

View File

@ -93,14 +93,14 @@ class CallbackModule(object):
auth = TokenAuth(self.auth_token)
else:
auth = None
port = parts.port or (443 if parts.scheme == 'https' else 80)
url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, parts.port),
'%s:%d' % (parts.hostname, port),
parts.path, parts.query, parts.fragment])
url_path = '/api/v1/jobs/%d/job_events/' % self.job_id
url = urlparse.urljoin(url, url_path)
headers = {'content-type': 'application/json'}
response = requests.post(url, data=data, headers=headers, auth=auth)
print response.content
response.raise_for_status()
def _log_event(self, event, **event_data):

View File

@ -71,8 +71,9 @@ class InventoryScript(object):
auth = TokenAuth(self.auth_token)
else:
auth = None
port = parts.port or (443 if parts.scheme == 'https' else 80)
url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, parts.port),
'%s:%d' % (parts.hostname, port),
parts.path, parts.query, parts.fragment])
url_path = '/api/v1/inventories/%d/script/' % self.inventory_id
if self.hostname:
@ -80,7 +81,8 @@ class InventoryScript(object):
url = urlparse.urljoin(url, url_path)
response = requests.get(url, auth=auth)
response.raise_for_status()
sys.stdout.write(json.dumps(response.json(), indent=self.indent) + '\n')
sys.stdout.write(json.dumps(json.loads(response.content),
indent=self.indent) + '\n')
def run(self):
try:

View File

@ -22,13 +22,13 @@ DATABASES = {
}
}
# Continue to use SQLite for unit tests instead of PostgreSQL.
if 'test' in sys.argv:
# Use SQLite for unit tests instead of PostgreSQL.
if len(sys.argv) >= 2 and sys.argv[1] == 'test':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'),
# Test database cannot be :memory: for celery/inventory tests to work.
# Test database cannot be :memory: for celery/inventory tests.
'TEST_NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3'),
}
}

BIN
awx/ui/.DS_Store vendored Normal file

Binary file not shown.

BIN
awx/ui/static/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -13,7 +13,7 @@
body {
color: #36454F;
padding-top: 40px;
padding-top: 60px;
}
.text-center {
@ -42,6 +42,7 @@
background-repeat: repeat-x;
border-color: #36454F;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#36454F', endColorstr='#36454F', GradientType=0);
padding-right: 15px;
}
.navbar-inverse .nav > li > a {
@ -55,10 +56,15 @@
.navbar .brand {
margin-left: 15px;
padding: 0;
}
.navbar .brand img {
width: 130px;
width: 260px;
}
.navbar .nav {
margin-top: 15px;
}
a:hover {

BIN
awx/ui/static/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -175,7 +175,7 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa
var form = InventoryForm;
var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', related: false});
scope.parseType = 'json';
scope.parseType = 'yaml';
generator.reset();
LoadBreadCrumbs();
@ -239,7 +239,7 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa
});
}
catch(err) {
Alert("Error", "Error parsing inventory variables. Parser returned " + err);
Alert("Error", "Error parsing inventory variables. Parser returned: " + err);
}
};
@ -275,7 +275,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
ParseTypeChange(scope);
scope.parseType = 'json';
scope.parseType = 'yaml';
scope['inventory_id'] = id;
// Retrieve each related set and any lookups
@ -300,10 +300,10 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
Rest.get()
.success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) {
scope.variables = "\{\}";
scope.variables = "---";
}
else {
scope.variables = JSON.stringify(data, null, " ");
scope.variables = jsyaml.safeDump(data);
}
})
.error( function(data, status, headers, config) {
@ -313,7 +313,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
});
}
else {
scope.variables = "\{\}";
scope.variables = "---";
}
});
@ -371,7 +371,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
});
}
catch(err) {
Alert("Error", "Error parsing inventory variables. Parser returned " + err);
Alert("Error", "Error parsing inventory variables. Parser returned: " + err);
}
};

View File

@ -159,7 +159,7 @@ JobTemplatesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$rout
function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup)
GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -170,6 +170,9 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', related: false});
var master = {};
scope.parseType = 'yaml';
ParseTypeChange(scope);
scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }];
scope.verbosity_options = [
@ -243,25 +246,43 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
// Save
scope.formSave = function() {
Rest.setUrl(defaultUrl);
var data = {}
for (var fld in form.fields) {
if (form.fields[fld].type == 'select' && fld != 'playbook') {
data[fld] = scope[fld].value;
try {
// Make sure we have valid variable data
if (scope.parseType == 'json') {
var myjson = JSON.parse(scope.variables); //make sure JSON parses
var json_data = scope.variables;
}
else {
data[fld] = scope[fld];
}
var json_data = jsyaml.load(scope.variables); //parse yaml
}
for (var fld in form.fields) {
if (form.fields[fld].type == 'select' && fld != 'playbook') {
data[fld] = scope[fld].value;
}
else {
if (fld != 'variables') {
data[fld] = scope[fld];
}
}
}
data.extra_vars = json_data;
Rest.setUrl(defaultUrl);
Rest.post(data)
.success( function(data, status, headers, config) {
var base = $location.path().replace(/^\//,'').split('/')[0];
(base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to add new job template. POST returned status: ' + status });
});
}
catch(err) {
Alert("Error", "Error parsing extra variables. Parser returned: " + err);
}
Rest.post(data)
.success( function(data, status, headers, config) {
var base = $location.path().replace(/^\//,'').split('/')[0];
(base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to add new project. POST returned status: ' + status });
});
};
// Reset
@ -277,13 +298,13 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'md5Setup' ];
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'md5Setup', 'ParseTypeChange' ];
function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit,
RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList,
ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup)
ProjectList, LookUpInit, PromptPasswords, GetBasePath, md5Setup, ParseTypeChange)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -293,6 +314,9 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
var form = JobTemplateForm;
var scope = generator.inject(form, {mode: 'edit', related: true});
scope.parseType = 'yaml';
ParseTypeChange(scope);
// Our job type options
scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }];
scope.verbosity_options = [
@ -365,7 +389,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
.success( function(data, status, headers, config) {
LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name });
for (var fld in form.fields) {
if (data[fld] !== null && data[fld] !== undefined) {
if (fld != 'variables' && data[fld] !== null && data[fld] !== undefined) {
if (form.fields[fld].type == 'select') {
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
for (var i=0; i < scope[fld + '_options'].length; i++) {
@ -383,6 +407,16 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
}
master[fld] = scope[fld];
}
if (fld == 'variables') {
// Parse extra_vars, converting to YAML.
if ($.isEmptyObject(data.extra_vars) || data.extra_vars == "\{\}") {
scope.variables = "---";
}
else {
scope.variables = jsyaml.safeDump(JSON.parse(data.extra_vars));
}
master.variables = scope.variables;
}
if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
@ -435,24 +469,42 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
// Save changes to the parent
scope.formSave = function() {
var data = {}
for (var fld in form.fields) {
if (form.fields[fld].type == 'select' && fld != 'playbook') {
data[fld] = scope[fld].value;
try {
// Make sure we have valid variable data
if (scope.parseType == 'json') {
var myjson = JSON.parse(scope.variables); //make sure JSON parses
var json_data = scope.variables;
}
else {
data[fld] = scope[fld];
}
}
Rest.setUrl(defaultUrl + $routeParams.id + '/');
Rest.put(data)
.success( function(data, status, headers, config) {
var base = $location.path().replace(/^\//,'').split('/')[0];
(base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to update team: ' + $routeParams.id + '. PUT status: ' + status });
});
var json_data = jsyaml.load(scope.variables); //parse yaml
}
for (var fld in form.fields) {
if (form.fields[fld].type == 'select' && fld != 'playbook') {
data[fld] = scope[fld].value;
}
else {
if (fld != 'variables') {
data[fld] = scope[fld];
}
}
}
data.extra_vars = json_data;
Rest.setUrl(defaultUrl + id + '/');
Rest.put(data)
.success( function(data, status, headers, config) {
var base = $location.path().replace(/^\//,'').split('/')[0];
(base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to update job template. PUT returned status: ' + status });
});
}
catch(err) {
Alert("Error", "Error parsing extra variables. Parser returned: " + err);
}
};
// Cancel
@ -461,6 +513,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
for (var fld in master) {
scope[fld] = master[fld];
}
scope.parseType = 'yaml';
$('#forks-slider').slider("option", "value", scope.forks);
};
@ -506,5 +559,5 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
JobTemplatesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit',
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList',
'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup'
'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath', 'md5Setup', 'ParseTypeChange'
];

View File

@ -1,7 +1,7 @@
function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath)
ClearScope, ProcessErrors, GetBasePath, CheckAccess)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@ -22,13 +22,13 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Res
LoadBreadCrumbs();
scope.addPermission = function() {
if (checkAccess()) {
if (CheckAccess()) {
$location.path($location.path() + '/add');
}
}
scope.editPermission = function(id) {
if (checkAccess()) {
if (CheckAccess()) {
$location.path($location.path() + '/' + id);
}
}
@ -60,7 +60,7 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Res
PermissionsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'PermissionList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
'ClearScope', 'ProcessErrors', 'GetBasePath'
'ClearScope', 'ProcessErrors', 'GetBasePath', 'CheckAccess'
];

View File

@ -36,7 +36,7 @@ angular.module('GroupFormDefinition', [])
editRequird: false,
rows: 10,
"class": 'modal-input-xlarge',
"default": "\{\}",
"default": "---",
dataTitle: 'Group Variables',
dataPlacement: 'right',
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +

View File

@ -41,7 +41,7 @@ angular.module('HostFormDefinition', [])
editRequird: false,
rows: 10,
"class": "modal-input-xlarge",
"default": "\{\}",
"default": "---",
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://www.ansibleworks.com/docs/YAMLSyntax.html" target="_blank">ansibleworks.com</a></p>',

View File

@ -63,7 +63,7 @@ angular.module('InventoryFormDefinition', [])
editRequird: false,
rows: 10,
"class": "modal-input-xlarge",
"default": "\{\}",
"default": "---",
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
'<p>View YAML examples at <a href="http://www.ansibleworks.com/docs/YAMLSyntax.html" target="_blank">ansibleworks.com</a></p>',

View File

@ -127,17 +127,17 @@ angular.module('JobTemplateFormDefinition', [])
dataTitle: 'Verbosity',
dataPlacement: 'left'
},
extra_vars: {
variables: {
label: 'Extra Variables',
type: 'textarea',
rows: 6,
"class": 'span12',
addRequired: false,
editRequired: false,
"default": "\{\}",
"default": "---",
column: 2,
awPopOver: "<p>Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter " +
"for ansible-playbook. Provide key=value pairs or JSON. <p><a href=\"http://www.ansibleworks.com/docs/playbooks2.html" +
"for ansible-playbook. Provide key/value pairs using either YAML or JSON. <p><a href=\"http://www.ansibleworks.com/docs/playbooks2.html" +
"#passing-variables-on-the-command-line\" target=\"_blank\">Click here to view documentation and examples.</a></p>",
dataTitle: 'Extra Variables',
dataPlacement: 'left'

View File

@ -126,17 +126,20 @@ angular.module('TeamFormDefinition', [])
label: 'Name',
ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')"
},
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name',
},
project: {
label: 'Project',
sourceModel: 'project',
sourceField: 'name',
ngBind: 'permission.summary_fields.project.name',
},
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name',
permission_type: {
label: 'Permission'
}
},

View File

@ -163,18 +163,22 @@ angular.module('UserFormDefinition', [])
label: 'Name',
ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')"
},
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name',
},
project: {
label: 'Project',
sourceModel: 'project',
sourceField: 'name',
ngBind: 'permission.summary_fields.project.name',
},
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name',
permission_type: {
label: 'Permission'
}
},
fieldActions: {

View File

@ -175,7 +175,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Create Group';
scope.formModalCancelShow = true;
scope.parseType = 'json';
scope.parseType = 'yaml';
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@ -237,7 +237,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
});
}
catch(err) {
Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err);
Alert("Error", "Error parsing group variables. Parser returned: " + err);
}
}
@ -270,7 +270,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Edit Group';
scope.formModalCancelShow = true;
scope.parseType = 'json';
scope.parseType = 'yaml';
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@ -288,10 +288,10 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
Rest.get()
.success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) {
scope.variables = "\{\}";
scope.variables = "---";
}
else {
scope.variables = JSON.stringify(data, null, " ");
scope.variables = jsyaml.safeDump(data);
}
})
.error( function(data, status, headers, config) {
@ -301,8 +301,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
});
}
else {
scope.variables = "\{\}";
scope.variables = "---";
}
master.variables = scope.variables;
});
// Retrieve detail record and prepopulate the form
@ -378,7 +379,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
});
}
catch(err) {
Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err);
Alert("Error", "Error parsing group variables. Parser returned: " + err);
}
};
@ -388,6 +389,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
for (var fld in master) {
scope[fld] = master[fld];
}
scope.parseType = 'yaml';
}
}
}])

View File

@ -165,7 +165,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Create Host';
scope.formModalCancelShow = true;
scope.parseType = 'json';
scope.parseType = 'yaml';
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@ -223,7 +223,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
});
}
catch(err) {
Alert("Error", "Error parsing host variables. Parser returned " + err);
Alert("Error", "Error parsing host variables. Parser returned: " + err);
}
}
@ -258,7 +258,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Edit Host';
scope.formModalCancelShow = true;
scope.parseType = 'json';
scope.parseType = 'yaml';
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@ -275,10 +275,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
Rest.get()
.success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) {
scope.variables = "\{\}";
scope.variables = "---";
}
else {
scope.variables = JSON.stringify(data, null, " ");
scope.variables = jsyaml.safeDump(data);
}
})
.error( function(data, status, headers, config) {
@ -288,8 +288,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
});
}
else {
scope.variables = "\{\}";
scope.variables = "---";
}
master.variables = scope.variables;
});
// Retrieve detail record and prepopulate the form
@ -363,7 +364,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
});
}
catch(err) {
Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err);
Alert("Error", "Error parsing group variables. Parser returned: " + err);
}
};
@ -373,6 +374,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
for (var fld in master) {
scope[fld] = master[fld];
}
scope.parseType = 'yaml';
}
}
}])

View File

@ -60,7 +60,7 @@ angular.module('RelatedSearchHelper', ['RestServices', 'Utilities','RefreshRelat
for (var related in form.related) {
if ( form.related[related].iterator == iterator ) {
var f = form.related[set].fields[fld];
var f = form.related[related].fields[fld];
}
}
@ -71,15 +71,15 @@ angular.module('RelatedSearchHelper', ['RestServices', 'Utilities','RefreshRelat
scope[iterator + 'HideSearchType'] = false;
scope[iterator + 'InputHide'] = false;
if (f.searchType && f.searchType == 'gtzero') {
if (f.searchType !== undefined && f.searchType == 'gtzero') {
scope[iterator + "InputHide"] = true;
}
if (f.searchType && (f.searchType == 'boolean'
if (f.searchType !== undefined && (f.searchType == 'boolean'
|| f.searchType == 'select')) {
scope[iterator + 'SelectShow'] = true;
scope[iterator + 'SearchSelectOpts'] = f.searchOptions;
}
if (f.searchType && f.searchType == 'int') {
if (f.searchType !== undefined && f.searchType == 'int') {
scope[iterator + 'HideSearchType'] = true;
}

View File

@ -37,7 +37,7 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
else {
sort_order = (list.fields[fld].desc) ? '-' + fld : fld;
}
if (list.fields[fld].notSearchable == undefined || list.fields[fld].notSearchable == false) {
if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) {
scope[iterator + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label;
}
@ -46,9 +46,9 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
}
if (!scope[iterator + 'SearchField']) {
// A field marked as key may also be notSearchable
// A field marked as key may not be 'searchable'
for (fld in list.fields) {
if (list.fields[fld].notSearchable == undefined || list.fields[fld].notSearchable == false) {
if (list.fields[fld].searchable == undefined || list.fields[fld].searchable == true) {
scope[iterator + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label;
break;

View File

@ -19,10 +19,10 @@ angular.module('JobEventsListDefinition', [])
fields: {
created: {
label: 'Creation Date',
label: 'Date',
key: true,
nosort: true,
notSearchable: true,
searchable: false,
ngClick: "viewJobEvent(\{\{ jobevent.id \}\})",
},
event_display: {
@ -30,7 +30,7 @@ angular.module('JobEventsListDefinition', [])
hasChildren: true,
ngClick: "viewJobEvent(\{\{ jobevent.id \}\})",
nosort: true,
notSearchable: true
searchable: false
},
host: {
label: 'Host',
@ -47,7 +47,8 @@ angular.module('JobEventsListDefinition', [])
searchField: 'failed',
searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }],
nosort: true
nosort: true,
searchable: false,
}
},

View File

@ -27,11 +27,11 @@ angular.module('JobHostDefinition', [])
},
ok: {
label: 'Success',
notSearchable: true
searchable: false
},
changed: {
label: 'Changed',
notSearchable: true
searchable: false
},
failures: {
label: 'Failure',
@ -39,11 +39,11 @@ angular.module('JobHostDefinition', [])
},
dark: {
label: 'Unreachable',
notSearchable: true
searchable: false
},
skipped: {
label: 'Skipped',
notSearchable: true
searchable: false
}
},

View File

@ -34,7 +34,7 @@ angular.module('JobsListDefinition', [])
link: true
},
created: {
label: 'Creation Date',
label: 'Date',
link: true
},
status: {

View File

@ -24,8 +24,11 @@ angular.module('PermissionListDefinition', [])
label: 'Name',
ngClick: 'editPermission(\{\{ permission.id \}\})'
},
description: {
label: 'Description'
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name'
},
project: {
label: 'Project',
@ -33,12 +36,9 @@ angular.module('PermissionListDefinition', [])
sourceField: 'name',
ngBind: 'permission.summary_fields.project.name'
},
inventory: {
label: 'Inventory',
sourceModel: 'inventory',
sourceField: 'name',
ngBind: 'permission.summary_fields.inventory.name'
},
permission_type: {
label: 'Permission'
}
},
actions: {

View File

@ -233,8 +233,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
if (field.genMD5) {
html += " \n<button class=\"btn\" ng-click=\"genMD5('" + fld + "')\" " +
"aw-tool-tip=\"Generate " + field.label + "\" data-placement=\"top\" id=\"" + fld + "-gen-btn\"><i class=\"icon-repeat\"></i></button>\n";
html += " \n<button style=\"margin-left: 10px;\" class=\"btn\" ng-click=\"selectAll('" + fld + "')\" " +
"aw-tool-tip=\"Select " + field.label + " for copy\" data-placement=\"top\" id=\"" + fld + "-copy-btn\"><i class=\"icon-copy\"></i></button>\n";
/*html += " \n<button style=\"margin-left: 10px;\" class=\"btn\" ng-click=\"selectAll('" + fld + "')\" " +
"aw-tool-tip=\"Select " + field.label + " for copy\" data-placement=\"top\" id=\"" + fld + "-copy-btn\"><i class=\"icon-copy\"></i></button>\n";*/
html += "</div>\n";
}
if (field.ask) {
@ -278,10 +278,14 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
}
html += field.label + '</label>' + "\n";
html += "<div class=\"controls\">\n";
if (fld == "variables") {
html += "<div class=\"parse-selection\">Parse as: <label class=\"radio inline\"><input type=\"radio\" ng-model=\"parseType\" value=\"json\"> JSON</label>\n";
html += "<label class=\"radio inline\"><input type=\"radio\" ng-model=\"parseType\" value=\"yaml\"> YAML</label></div>\n";
// Variable editing
if (fld == "variables" || fld == "extra_vars") {
html += "<div class=\"parse-selection\">Parse as: " +
"<label class=\"radio inline\"><input type=\"radio\" ng-model=\"parseType\" value=\"yaml\"> YAML</label>\n" +
"<label class=\"radio inline\"><input type=\"radio\" ng-model=\"parseType\" value=\"json\"> JSON</label></div>\n";
}
html += "<textarea ";
html += (field.rows) ? this.attr(field, 'rows') : "";
html += "ng-model=\"" + fld + '" ';

View File

@ -177,7 +177,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += "<ul class=\"dropdown-menu\" id=\"" + iterator + "SearchDropdown\">\n";
for ( var fld in form.fields) {
if (form.fields[fld].notSearchable == undefined || form.fields[fld].notSearchable == false) {
if (form.fields[fld].searchable == undefined || form.fields[fld].searchable == true) {
html += "<li><a href=\"\" ng-click=\"setSearchField('" + iterator + "','";
html += fld + "','" + form.fields[fld].label + "')\">"
+ form.fields[fld].label + "</a></li>\n";

View File

@ -93,7 +93,7 @@
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="#/"><img class="logo" src="{{ STATIC_URL }}img/ansibleworks-logo.png" /></a>
<a class="brand" href="#/"><img class="logo" src="{{ STATIC_URL }}img/logo.png" /></a>
<ul class="nav pull-right">
<li ng-show="current_user.username != null && current_user.username != undefined">
<a href="" ng-click="viewCurrentUser()" ng-bind="'Hello! ' + current_user.username"></a></li>

View File

@ -13,6 +13,17 @@ DATABASES = {
}
}
# Use SQLite for unit tests instead of PostgreSQL.
if len(sys.argv) >= 2 and sys.argv[1] == 'test':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'var/lib/awx/awx.sqlite3',
# Test database cannot be :memory: for celery/inventory tests.
'TEST_NAME': '/var/lib/awx/awx_test.sqlite3',
}
}
STATIC_ROOT = '/var/lib/awx/public/static'
PROJECTS_ROOT = '/var/lib/awx/projects'

View File

@ -13,6 +13,17 @@ DATABASES = {
}
}
# Use SQLite for unit tests instead of PostgreSQL.
if len(sys.argv) >= 2 and sys.argv[1] == 'test':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'var/lib/awx/awx.sqlite3',
# Test database cannot be :memory: for celery/inventory tests.
'TEST_NAME': '/var/lib/awx/awx_test.sqlite3',
}
}
STATIC_ROOT = '/var/lib/awx/public/static'
PROJECTS_ROOT = '/var/lib/awx/projects'

View File

@ -1,5 +1,5 @@
# PIP requirements for AWX development/build environment (using only local
packages). Install using "pip --no-index -r dev_local.txt".
# packages). Install using "pip --no-index -r dev_local.txt".
distribute-0.6.45.tar.gz