Merge remote branch 'origin/master'

This commit is contained in:
Michael DeHaan
2013-06-28 12:02:35 -04:00
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 return None
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.failed = bool(self.event in self.FAILED_EVENTS) if self.event in self.FAILED_EVENTS:
# FIXME: Propagage failed flag to parent events. self.failed = True
try: try:
if not self.host and self.event_data.get('host', ''): if not self.host and self.event_data.get('host', ''):
self.host = self.job.inventory.hosts.get(name=self.event_data['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.task = self.event_data.get('task', '')
self.parent = self._find_parent() self.parent = self._find_parent()
super(JobEvent, self).save(*args, **kwargs) super(JobEvent, self).save(*args, **kwargs)
self.update_parent_failed()
self.update_hosts() self.update_hosts()
self.update_host_summary_from_stats() 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): def update_hosts(self, extra_hosts=None):
extra_hosts = extra_hosts or [] extra_hosts = extra_hosts or []
hostnames = set() hostnames = set()

View File

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

View File

@@ -213,6 +213,53 @@ class RunJobTest(BaseCeleryTest):
'expected no traceback, got:\n%s' % 'expected no traceback, got:\n%s' %
job.result_traceback) 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): def test_run_job(self):
self.create_test_project(TEST_PLAYBOOK) self.create_test_project(TEST_PLAYBOOK)
job_template = self.create_test_job_template() job_template = self.create_test_job_template()
@@ -223,17 +270,7 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk) job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'successful') self.check_job_result(job, 'successful')
job_events = job.job_events.all() self.check_job_events(job, 'ok', 1, 2)
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)
for job_host_summary in job.job_host_summaries.all(): for job_host_summary in job.job_host_summaries.all():
unicode(job_host_summary) # For test coverage. unicode(job_host_summary) # For test coverage.
self.assertFalse(job_host_summary.failed) self.assertFalse(job_host_summary.failed)
@@ -261,14 +298,7 @@ class RunJobTest(BaseCeleryTest):
self.assertEqual(job.status, 'pending') self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk) job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'successful') self.check_job_result(job, 'successful')
job_events = job.job_events.all() self.check_job_events(job, 'skipped', 1, 2)
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)
for job_host_summary in job.job_host_summaries.all(): for job_host_summary in job.job_host_summaries.all():
self.assertFalse(job_host_summary.failed) self.assertFalse(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) 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') self.assertEqual(job.status, 'pending')
job = Job.objects.get(pk=job.pk) job = Job.objects.get(pk=job.pk)
self.check_job_result(job, 'failed') self.check_job_result(job, 'failed')
job_events = job.job_events.all() self.check_job_events(job, 'failed', 1, 1)
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)
for job_host_summary in job.job_host_summaries.all(): for job_host_summary in job.job_host_summaries.all():
self.assertTrue(job_host_summary.failed) self.assertTrue(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) 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 # Since we don't actually run the task, the --check should indicate
# everything is successful. # everything is successful.
self.check_job_result(job, 'successful') self.check_job_result(job, 'successful')
job_events = job.job_events.all() self.check_job_events(job, 'skipped', 1, 1)
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)
for job_host_summary in job.job_host_summaries.all(): for job_host_summary in job.job_host_summaries.all():
self.assertFalse(job_host_summary.failed) self.assertFalse(job_host_summary.failed)
self.assertEqual(job_host_summary.host.last_job_host_summary, job_host_summary) 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) auth = TokenAuth(self.auth_token)
else: else:
auth = None auth = None
port = parts.port or (443 if parts.scheme == 'https' else 80)
url = urlparse.urlunsplit([parts.scheme, url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, parts.port), '%s:%d' % (parts.hostname, port),
parts.path, parts.query, parts.fragment]) parts.path, parts.query, parts.fragment])
url_path = '/api/v1/jobs/%d/job_events/' % self.job_id url_path = '/api/v1/jobs/%d/job_events/' % self.job_id
url = urlparse.urljoin(url, url_path) url = urlparse.urljoin(url, url_path)
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
response = requests.post(url, data=data, headers=headers, auth=auth) response = requests.post(url, data=data, headers=headers, auth=auth)
print response.content
response.raise_for_status() response.raise_for_status()
def _log_event(self, event, **event_data): def _log_event(self, event, **event_data):

View File

@@ -71,8 +71,9 @@ class InventoryScript(object):
auth = TokenAuth(self.auth_token) auth = TokenAuth(self.auth_token)
else: else:
auth = None auth = None
port = parts.port or (443 if parts.scheme == 'https' else 80)
url = urlparse.urlunsplit([parts.scheme, url = urlparse.urlunsplit([parts.scheme,
'%s:%d' % (parts.hostname, parts.port), '%s:%d' % (parts.hostname, port),
parts.path, parts.query, parts.fragment]) parts.path, parts.query, parts.fragment])
url_path = '/api/v1/inventories/%d/script/' % self.inventory_id url_path = '/api/v1/inventories/%d/script/' % self.inventory_id
if self.hostname: if self.hostname:
@@ -80,7 +81,8 @@ class InventoryScript(object):
url = urlparse.urljoin(url, url_path) url = urlparse.urljoin(url, url_path)
response = requests.get(url, auth=auth) response = requests.get(url, auth=auth)
response.raise_for_status() 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): def run(self):
try: try:

View File

@@ -22,13 +22,13 @@ DATABASES = {
} }
} }
# Continue to use SQLite for unit tests instead of PostgreSQL. # Use SQLite for unit tests instead of PostgreSQL.
if 'test' in sys.argv: if len(sys.argv) >= 2 and sys.argv[1] == 'test':
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'awx.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'), '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 { body {
color: #36454F; color: #36454F;
padding-top: 40px; padding-top: 60px;
} }
.text-center { .text-center {
@@ -42,6 +42,7 @@
background-repeat: repeat-x; background-repeat: repeat-x;
border-color: #36454F; border-color: #36454F;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#36454F', endColorstr='#36454F', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#36454F', endColorstr='#36454F', GradientType=0);
padding-right: 15px;
} }
.navbar-inverse .nav > li > a { .navbar-inverse .nav > li > a {
@@ -55,10 +56,15 @@
.navbar .brand { .navbar .brand {
margin-left: 15px; margin-left: 15px;
padding: 0;
} }
.navbar .brand img { .navbar .brand img {
width: 130px; width: 260px;
}
.navbar .nav {
margin-top: 15px;
} }
a:hover { 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 form = InventoryForm;
var generator = GenerateForm; var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', related: false}); var scope = generator.inject(form, {mode: 'add', related: false});
scope.parseType = 'json'; scope.parseType = 'yaml';
generator.reset(); generator.reset();
LoadBreadCrumbs(); LoadBreadCrumbs();
@@ -239,7 +239,7 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa
}); });
} }
catch(err) { 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); ParseTypeChange(scope);
scope.parseType = 'json'; scope.parseType = 'yaml';
scope['inventory_id'] = id; scope['inventory_id'] = id;
// Retrieve each related set and any lookups // Retrieve each related set and any lookups
@@ -300,10 +300,10 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
Rest.get() Rest.get()
.success( function(data, status, headers, config) { .success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) { if ($.isEmptyObject(data)) {
scope.variables = "\{\}"; scope.variables = "---";
} }
else { else {
scope.variables = JSON.stringify(data, null, " "); scope.variables = jsyaml.safeDump(data);
} }
}) })
.error( function(data, status, headers, config) { .error( function(data, status, headers, config) {
@@ -313,7 +313,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
}); });
} }
else { else {
scope.variables = "\{\}"; scope.variables = "---";
} }
}); });
@@ -371,7 +371,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
}); });
} }
catch(err) { 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, function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, 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 ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope. //scope.
@@ -170,6 +170,9 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
var generator = GenerateForm; var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', related: false}); var scope = generator.inject(form, {mode: 'add', related: false});
var master = {}; var master = {};
scope.parseType = 'yaml';
ParseTypeChange(scope);
scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }];
scope.verbosity_options = [ scope.verbosity_options = [
@@ -243,25 +246,43 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
// Save // Save
scope.formSave = function() { scope.formSave = function() {
Rest.setUrl(defaultUrl);
var data = {} var data = {}
for (var fld in form.fields) { try {
if (form.fields[fld].type == 'select' && fld != 'playbook') { // Make sure we have valid variable data
data[fld] = scope[fld].value; if (scope.parseType == 'json') {
var myjson = JSON.parse(scope.variables); //make sure JSON parses
var json_data = scope.variables;
} }
else { 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 // Reset
@@ -277,13 +298,13 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', '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, function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit,
RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList, 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 ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope. //scope.
@@ -293,6 +314,9 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
var form = JobTemplateForm; var form = JobTemplateForm;
var scope = generator.inject(form, {mode: 'edit', related: true}); var scope = generator.inject(form, {mode: 'edit', related: true});
scope.parseType = 'yaml';
ParseTypeChange(scope);
// Our job type options // Our job type options
scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }];
scope.verbosity_options = [ scope.verbosity_options = [
@@ -365,7 +389,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
.success( function(data, status, headers, config) { .success( function(data, status, headers, config) {
LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name });
for (var fld in form.fields) { 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 (form.fields[fld].type == 'select') {
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) { if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
for (var i=0; i < scope[fld + '_options'].length; i++) { 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]; 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]) { if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; 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 // Save changes to the parent
scope.formSave = function() { scope.formSave = function() {
var data = {} var data = {}
for (var fld in form.fields) { try {
if (form.fields[fld].type == 'select' && fld != 'playbook') { // Make sure we have valid variable data
data[fld] = scope[fld].value; if (scope.parseType == 'json') {
var myjson = JSON.parse(scope.variables); //make sure JSON parses
var json_data = scope.variables;
} }
else { else {
data[fld] = scope[fld]; var json_data = jsyaml.load(scope.variables); //parse yaml
} }
}
Rest.setUrl(defaultUrl + $routeParams.id + '/'); for (var fld in form.fields) {
Rest.put(data) if (form.fields[fld].type == 'select' && fld != 'playbook') {
.success( function(data, status, headers, config) { data[fld] = scope[fld].value;
var base = $location.path().replace(/^\//,'').split('/')[0]; }
(base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); else {
}) if (fld != 'variables') {
.error( function(data, status, headers, config) { data[fld] = scope[fld];
ProcessErrors(scope, data, status, form, }
{ hdr: 'Error!', msg: 'Failed to update team: ' + $routeParams.id + '. PUT status: ' + status }); }
}); }
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 // Cancel
@@ -461,6 +513,7 @@ function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $route
for (var fld in master) { for (var fld in master) {
scope[fld] = master[fld]; scope[fld] = master[fld];
} }
scope.parseType = 'yaml';
$('#forks-slider').slider("option", "value", scope.forks); $('#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', JobTemplatesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit',
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', '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, function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, PermissionList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, 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 ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope. //scope.
@@ -22,13 +22,13 @@ function PermissionsList ($scope, $rootScope, $location, $log, $routeParams, Res
LoadBreadCrumbs(); LoadBreadCrumbs();
scope.addPermission = function() { scope.addPermission = function() {
if (checkAccess()) { if (CheckAccess()) {
$location.path($location.path() + '/add'); $location.path($location.path() + '/add');
} }
} }
scope.editPermission = function(id) { scope.editPermission = function(id) {
if (checkAccess()) { if (CheckAccess()) {
$location.path($location.path() + '/' + id); $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', PermissionsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'PermissionList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
'ClearScope', 'ProcessErrors', 'GetBasePath' 'ClearScope', 'ProcessErrors', 'GetBasePath', 'CheckAccess'
]; ];

View File

@@ -36,7 +36,7 @@ angular.module('GroupFormDefinition', [])
editRequird: false, editRequird: false,
rows: 10, rows: 10,
"class": 'modal-input-xlarge', "class": 'modal-input-xlarge',
"default": "\{\}", "default": "---",
dataTitle: 'Group Variables', dataTitle: 'Group Variables',
dataPlacement: 'right', dataPlacement: 'right',
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" + 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, editRequird: false,
rows: 10, rows: 10,
"class": "modal-input-xlarge", "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>" + 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 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>', '<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, editRequird: false,
rows: 10, rows: 10,
"class": "modal-input-xlarge", "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>" + 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 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>', '<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', dataTitle: 'Verbosity',
dataPlacement: 'left' dataPlacement: 'left'
}, },
extra_vars: { variables: {
label: 'Extra Variables', label: 'Extra Variables',
type: 'textarea', type: 'textarea',
rows: 6, rows: 6,
"class": 'span12', "class": 'span12',
addRequired: false, addRequired: false,
editRequired: false, editRequired: false,
"default": "\{\}", "default": "---",
column: 2, column: 2,
awPopOver: "<p>Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter " + 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>", "#passing-variables-on-the-command-line\" target=\"_blank\">Click here to view documentation and examples.</a></p>",
dataTitle: 'Extra Variables', dataTitle: 'Extra Variables',
dataPlacement: 'left' dataPlacement: 'left'

View File

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

View File

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

View File

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

View File

@@ -165,7 +165,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
scope.formModalActionLabel = 'Save'; scope.formModalActionLabel = 'Save';
scope.formModalHeader = 'Create Host'; scope.formModalHeader = 'Create Host';
scope.formModalCancelShow = true; scope.formModalCancelShow = true;
scope.parseType = 'json'; scope.parseType = 'yaml';
ParseTypeChange(scope); ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@@ -223,7 +223,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}); });
} }
catch(err) { 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.formModalActionLabel = 'Save';
scope.formModalHeader = 'Edit Host'; scope.formModalHeader = 'Edit Host';
scope.formModalCancelShow = true; scope.formModalCancelShow = true;
scope.parseType = 'json'; scope.parseType = 'yaml';
ParseTypeChange(scope); ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
@@ -275,10 +275,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
Rest.get() Rest.get()
.success( function(data, status, headers, config) { .success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) { if ($.isEmptyObject(data)) {
scope.variables = "\{\}"; scope.variables = "---";
} }
else { else {
scope.variables = JSON.stringify(data, null, " "); scope.variables = jsyaml.safeDump(data);
} }
}) })
.error( function(data, status, headers, config) { .error( function(data, status, headers, config) {
@@ -288,8 +288,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}); });
} }
else { else {
scope.variables = "\{\}"; scope.variables = "---";
} }
master.variables = scope.variables;
}); });
// Retrieve detail record and prepopulate the form // Retrieve detail record and prepopulate the form
@@ -363,7 +364,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
}); });
} }
catch(err) { 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) { for (var fld in master) {
scope[fld] = master[fld]; 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) { for (var related in form.related) {
if ( form.related[related].iterator == iterator ) { 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 + 'HideSearchType'] = false;
scope[iterator + 'InputHide'] = false; scope[iterator + 'InputHide'] = false;
if (f.searchType && f.searchType == 'gtzero') { if (f.searchType !== undefined && f.searchType == 'gtzero') {
scope[iterator + "InputHide"] = true; scope[iterator + "InputHide"] = true;
} }
if (f.searchType && (f.searchType == 'boolean' if (f.searchType !== undefined && (f.searchType == 'boolean'
|| f.searchType == 'select')) { || f.searchType == 'select')) {
scope[iterator + 'SelectShow'] = true; scope[iterator + 'SelectShow'] = true;
scope[iterator + 'SearchSelectOpts'] = f.searchOptions; scope[iterator + 'SearchSelectOpts'] = f.searchOptions;
} }
if (f.searchType && f.searchType == 'int') { if (f.searchType !== undefined && f.searchType == 'int') {
scope[iterator + 'HideSearchType'] = true; scope[iterator + 'HideSearchType'] = true;
} }

View File

@@ -37,7 +37,7 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
else { else {
sort_order = (list.fields[fld].desc) ? '-' + fld : fld; 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 + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label;
} }
@@ -46,9 +46,9 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
} }
if (!scope[iterator + 'SearchField']) { 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) { 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 + 'SearchField'] = fld;
scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label; scope[iterator + 'SearchFieldLabel'] = list.fields[fld].label;
break; break;

View File

@@ -19,10 +19,10 @@ angular.module('JobEventsListDefinition', [])
fields: { fields: {
created: { created: {
label: 'Creation Date', label: 'Date',
key: true, key: true,
nosort: true, nosort: true,
notSearchable: true, searchable: false,
ngClick: "viewJobEvent(\{\{ jobevent.id \}\})", ngClick: "viewJobEvent(\{\{ jobevent.id \}\})",
}, },
event_display: { event_display: {
@@ -30,7 +30,7 @@ angular.module('JobEventsListDefinition', [])
hasChildren: true, hasChildren: true,
ngClick: "viewJobEvent(\{\{ jobevent.id \}\})", ngClick: "viewJobEvent(\{\{ jobevent.id \}\})",
nosort: true, nosort: true,
notSearchable: true searchable: false
}, },
host: { host: {
label: 'Host', label: 'Host',
@@ -47,7 +47,8 @@ angular.module('JobEventsListDefinition', [])
searchField: 'failed', searchField: 'failed',
searchType: 'boolean', searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }], 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: { ok: {
label: 'Success', label: 'Success',
notSearchable: true searchable: false
}, },
changed: { changed: {
label: 'Changed', label: 'Changed',
notSearchable: true searchable: false
}, },
failures: { failures: {
label: 'Failure', label: 'Failure',
@@ -39,11 +39,11 @@ angular.module('JobHostDefinition', [])
}, },
dark: { dark: {
label: 'Unreachable', label: 'Unreachable',
notSearchable: true searchable: false
}, },
skipped: { skipped: {
label: 'Skipped', label: 'Skipped',
notSearchable: true searchable: false
} }
}, },

View File

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

View File

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

View File

@@ -233,8 +233,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
if (field.genMD5) { if (field.genMD5) {
html += " \n<button class=\"btn\" ng-click=\"genMD5('" + fld + "')\" " + 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"; "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 + "')\" " + /*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"; "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"; html += "</div>\n";
} }
if (field.ask) { if (field.ask) {
@@ -278,10 +278,14 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
} }
html += field.label + '</label>' + "\n"; html += field.label + '</label>' + "\n";
html += "<div class=\"controls\">\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"; // Variable editing
html += "<label class=\"radio inline\"><input type=\"radio\" ng-model=\"parseType\" value=\"yaml\"> YAML</label></div>\n"; 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 += "<textarea ";
html += (field.rows) ? this.attr(field, 'rows') : ""; html += (field.rows) ? this.attr(field, 'rows') : "";
html += "ng-model=\"" + fld + '" '; html += "ng-model=\"" + fld + '" ';

View File

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

View File

@@ -93,7 +93,7 @@
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container"> <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"> <ul class="nav pull-right">
<li ng-show="current_user.username != null && current_user.username != undefined"> <li ng-show="current_user.username != null && current_user.username != undefined">
<a href="" ng-click="viewCurrentUser()" ng-bind="'Hello! ' + current_user.username"></a></li> <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' STATIC_ROOT = '/var/lib/awx/public/static'
PROJECTS_ROOT = '/var/lib/awx/projects' 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' STATIC_ROOT = '/var/lib/awx/public/static'
PROJECTS_ROOT = '/var/lib/awx/projects' PROJECTS_ROOT = '/var/lib/awx/projects'

View File

@@ -1,5 +1,5 @@
# PIP requirements for AWX development/build environment (using only local # 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 distribute-0.6.45.tar.gz