diff --git a/ansibleworks/ui/static/css/ansible-ui.css b/ansibleworks/ui/static/css/ansible-ui.css index 253eed55eb..4f87cf4bc5 100644 --- a/ansibleworks/ui/static/css/ansible-ui.css +++ b/ansibleworks/ui/static/css/ansible-ui.css @@ -281,12 +281,13 @@ } /* Enable table-hover to work when table is in a well */ -.table-hover tbody tr:hover > td, -.table-hover tbody tr:hover > th { + .table-hover tbody tr:hover > td, + .table-hover tbody tr:hover > th { background-color: #fff; } - /* Jobs page */ + +/* Jobs page */ .job-error, .job-failed, input[type="text"].job-failed, @@ -328,6 +329,7 @@ /* End Jobs Page */ + /* Inventory detail */ .inventory-title { @@ -352,6 +354,24 @@ margin-bottom: 15px; } + .parse-selection { + font-size: 12px; + margin: 5px 0 10px 0; + } + + .parse-selection .radio.inline { + padding-top: 0; + font-size: 12px; + } + + .parse-selection label:first-child { + margin-left: 5px; + } + + .modal-input-xlarge { + width: 325px; + } + #tree-view { min-height: 100px; } diff --git a/ansibleworks/ui/static/js/app.js b/ansibleworks/ui/static/js/app.js index c81efc66ae..70a4d719f4 100644 --- a/ansibleworks/ui/static/js/app.js +++ b/ansibleworks/ui/static/js/app.js @@ -51,7 +51,8 @@ angular.module('ansible', [ 'JobEventFormDefinition', 'JobHostDefinition', 'GroupsHelper', - 'HostsHelper' + 'HostsHelper', + 'ParseHelper' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. diff --git a/ansibleworks/ui/static/js/forms/Groups.js b/ansibleworks/ui/static/js/forms/Groups.js index fa5224cbbc..a228305a3c 100644 --- a/ansibleworks/ui/static/js/forms/Groups.js +++ b/ansibleworks/ui/static/js/forms/Groups.js @@ -34,14 +34,13 @@ angular.module('GroupFormDefinition', []) addRequired: false, editRequird: false, rows: 10, + "class": 'modal-input-xlarge', "default": "\{\}", dataTitle: 'Group Variables', dataPlacement: 'right', - awPopOver: '
Enter variables as JSON. Both the key and value must be wrapped in double quotes. ' + - 'Separate variables with commas, and wrap the entire string with { }. ' + - ' For example:
{
"ntp_server": "ntp.example.com",
' +
- '"proxy": "proxy.example.com"
}
See additional JSON examples at www.json.org
' + awPopOver: "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + + 'View JSON examples at www.json.org
' + + 'View YAML examples at ansiblewors.com
' } }, diff --git a/ansibleworks/ui/static/js/forms/Hosts.js b/ansibleworks/ui/static/js/forms/Hosts.js index 4eaf79dabf..8a545cb548 100644 --- a/ansibleworks/ui/static/js/forms/Hosts.js +++ b/ansibleworks/ui/static/js/forms/Hosts.js @@ -39,12 +39,11 @@ angular.module('HostFormDefinition', []) addRequired: false, editRequird: false, rows: 10, + "class": "modal-input-xlarge", "default": "\{\}", - awPopOver: "Enter variables as JSON. Both the key and value must be wrapped in double quotes. " + - "Separate variables with commas, and wrap the entire string with { }. " + - " For example:
{
"ntp_server": "ntp.example.com",
" +
- '"proxy": "proxy.example.com"
}
See additional JSON examples at www.json.org
', + awPopOver: "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + + 'View JSON examples at www.json.org
' + + 'View YAML examples at ansiblewors.com
', dataTitle: 'Host Variables', dataPlacement: 'right' } diff --git a/ansibleworks/ui/static/js/helpers/Groups.js b/ansibleworks/ui/static/js/helpers/Groups.js index e9bd31c2fa..b54becb0d6 100644 --- a/ansibleworks/ui/static/js/helpers/Groups.js +++ b/ansibleworks/ui/static/js/helpers/Groups.js @@ -12,7 +12,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' 'InventoryHelper' ]) - .factory('GroupsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'RefreshTree', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, LoadBreadCrumbs, SearchInit, @@ -155,9 +154,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' .factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', 'ParseTypeChange', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, RefreshTree) { + GetBasePath, RefreshTree, ParseTypeChange) { return function(params) { var inventory_id = params.inventory_id; @@ -169,8 +168,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' var form = GroupForm; var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', modal: true, related: false}); - scope.formModalActionLabel = 'Save' - scope.formModalHeader = 'Create Group' + + scope.formModalActionLabel = 'Save'; + scope.formModalHeader = 'Create Group'; + scope.parseType = 'json'; + ParseTypeChange(scope); + generator.reset(); var master={}; @@ -181,8 +184,15 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' // Save scope.formModalAction = function() { try { - // Make sure we have valid JSON - var myjson = JSON.parse(scope.variables); + + // 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 { + var json_data = jsyaml.load(scope.variables); //parse yaml + } var data = {} for (var fld in form.fields) { @@ -200,7 +210,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' .success( function(data, status, headers, config) { if (scope.variables) { Rest.setUrl(data.related.variable_data); - Rest.put({data: scope.variables}) + Rest.put(json_data) .success( function(data, status, headers, config) { $('#form-modal').modal('hide'); RefreshTree({ scope: scope }); @@ -236,9 +246,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' .factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', 'ParseTypeChange', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, RefreshTree) { + GetBasePath, RefreshTree, ParseTypeChange) { return function(params) { var group_id = params.group_id; @@ -251,8 +261,10 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' var master = {}; var relatedSets = {}; - scope.formModalActionLabel = 'Save' - scope.formModalHeader = 'Edit Group' + scope.formModalActionLabel = 'Save'; + scope.formModalHeader = 'Edit Group'; + scope.parseType = 'json'; + ParseTypeChange(scope); // After the group record is loaded, retrieve any group variables if (scope.groupLoadedRemove) { @@ -266,11 +278,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' Rest.setUrl(scope.variable_url); Rest.get() .success( function(data, status, headers, config) { - if ($.isEmptyObject(data.data)) { + if ($.isEmptyObject(data)) { scope.variables = "\{\}"; } else { - scope.variables = data.data; + scope.variables = JSON.stringify(data, null, " "); } }) .error( function(data, status, headers, config) { @@ -315,8 +327,15 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' // Save changes to the parent scope.formModalAction = function() { try { - // Make sure we have valid JSON - var myjson = JSON.parse(scope.variables); + + // 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 { + var json_data = jsyaml.load(scope.variables); //parse yaml + } var data = {} for (var fld in form.fields) { @@ -329,7 +348,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' if (scope.variables) { //update group variables Rest.setUrl(GetBasePath('groups') + data.id + '/variable_data/'); - Rest.put({data: scope.variables}) + Rest.put(json_data) .success( function(data, status, headers, config) { $('#form-modal').modal('hide'); RefreshTree({ scope: scope }); diff --git a/ansibleworks/ui/static/js/helpers/Hosts.js b/ansibleworks/ui/static/js/helpers/Hosts.js index 5c8dc606a4..0f1e977619 100644 --- a/ansibleworks/ui/static/js/helpers/Hosts.js +++ b/ansibleworks/ui/static/js/helpers/Hosts.js @@ -146,9 +146,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H .factory('HostsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, HostsReload) { + GetBasePath, HostsReload, ParseTypeChange) { return function(params) { var inventory_id = params.inventory_id; @@ -160,9 +160,11 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', modal: true, related: false}); - scope.formModalActionLabel = 'Save' - scope.formModalHeader = 'Create Host' - + scope.formModalActionLabel = 'Save'; + scope.formModalHeader = 'Create Host'; + scope.parseType = 'json'; + ParseTypeChange(scope); + $('#form-modal').unbind('hidden'); $('#form-modal').on('hidden', function () { HostsReload(params); }); @@ -176,9 +178,15 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H // Save scope.formModalAction = function() { try { - // Make sure we have valid JSON - var myjson = JSON.parse(scope.variables); - + // 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 { + var json_data = jsyaml.load(scope.variables); //parse yaml + } + var data = {} for (var fld in form.fields) { if (fld != 'variables') { @@ -192,7 +200,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H .success( function(data, status, headers, config) { if (scope.variables) { Rest.setUrl(data.related.variable_data); - Rest.put({data: scope.variables}) + Rest.put(json_data) .success( function(data, status, headers, config) { $('#form-modal').modal('hide'); }) @@ -211,7 +219,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }); } catch(err) { - Alert("Error", "Error parsing host variables. Expecting valid JSON. Parser returned " + err); + Alert("Error", "Error parsing host variables. Parser returned " + err); } } @@ -226,9 +234,9 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H .factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm', - 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', + 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors, - GetBasePath, HostsReload) { + GetBasePath, HostsReload, ParseTypeChange) { return function(params) { var host_id = params.host_id; @@ -243,9 +251,11 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H var master = {}; var relatedSets = {}; - scope.formModalActionLabel = 'Save' - scope.formModalHeader = 'Edit Host' - + scope.formModalActionLabel = 'Save'; + scope.formModalHeader = 'Edit Host'; + scope.parseType = 'json'; + ParseTypeChange(scope); + $('#form-modal').unbind('hidden'); $('#form-modal').on('hidden', function () { HostsReload(params); }); @@ -258,11 +268,11 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H Rest.setUrl(scope.variable_url); Rest.get() .success( function(data, status, headers, config) { - if ($.isEmptyObject(data.data)) { + if ($.isEmptyObject(data)) { scope.variables = "\{\}"; } else { - scope.variables = data.data; + scope.variables = JSON.stringify(data, null, " "); } }) .error( function(data, status, headers, config) { @@ -307,8 +317,15 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H // Save changes to the parent scope.formModalAction = function() { try { - // Make sure we have valid JSON - var myjson = JSON.parse(scope.variables); + + // 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 { + var json_data = jsyaml.load(scope.variables); //parse yaml + } var data = {} for (var fld in form.fields) { @@ -321,7 +338,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H if (scope.variables) { //update host variables Rest.setUrl(GetBasePath('hosts') + data.id + '/variable_data/'); - Rest.put({data: scope.variables}) + Rest.put(json_data) .success( function(data, status, headers, config) { $('#form-modal').modal('hide'); }) diff --git a/ansibleworks/ui/static/js/helpers/Parse.js b/ansibleworks/ui/static/js/helpers/Parse.js new file mode 100644 index 0000000000..0ff4121301 --- /dev/null +++ b/ansibleworks/ui/static/js/helpers/Parse.js @@ -0,0 +1,60 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * ParseHelper + * + * Routines for parsing variable data and toggling + * between JSON and YAML. + * + */ + +angular.module('ParseHelper', []) + .factory('ParseTypeChange', [function() { + return function(scope) { + + // Toggle displayed variable string between JSON and YAML + + scope.blockParseTypeWatch = false; + scope.blockVariableDataWatch = false; + + if (scope.removeParseTypeWatch) { + scope.removeParseTypeWatch(); + } + scope.removeParseTypeWatch = scope.$watch('parseType', function(newVal, oldVal) { + if (newVal !== oldVal) { + if (newVal == 'json') { + if ( scope.variables && !/^---$/.test(scope.variables)) { + // convert YAML to JSON + try { + var json_obj = jsyaml.load(scope.variables); //parse yaml into an obj + scope.variables = JSON.stringify(json_obj, null, " "); + } + catch (err) { + // ignore parse errors. allow the user to paste values in and sync the + // radio button later. parse errors will be flagged on save. + } + } + else { + scope.variables = "\{\}"; + } + } + else { + if ( scope.variables && !/^\{\}$/.test(scope.variables) ) { + // convert JSON to YAML + try { + var json_obj = JSON.parse(scope.variables); + scope.variables = jsyaml.safeDump(json_obj); + } + catch (err) { + // ignore the errors. allow the user to paste values in and sync the + // radio button later. parse errors will be flagged on save. + } + } + else { + scope.variables = "---"; + } + } + } + }); + } + }]); diff --git a/ansibleworks/ui/static/lib/ansible/form-generator.js b/ansibleworks/ui/static/lib/ansible/form-generator.js index c4dff2c527..fd582a076d 100644 --- a/ansibleworks/ui/static/lib/ansible/form-generator.js +++ b/ansibleworks/ui/static/lib/ansible/form-generator.js @@ -257,7 +257,11 @@ angular.module('FormGenerator', ['GeneratorHelpers']) html += "> "; } html += field.label + '' + "\n"; - html += "