elements
$(this).remove();
});
+ if (properties_scope.codeMirror) {
+ properties_scope.codeMirror.destroy();
+ }
+ if (sources_scope.codeMirror) {
+ sources_scope.codeMirror.destroy();
+ }
$('#group-modal-dialog').dialog('destroy');
$('#group-modal-dialog').hide();
+ $('#properties-tab').empty();
+ $('#sources-tab').empty();
+ $('#schedules-list').empty();
+ $('#schedules-form').empty();
+ $('#schedules-detail').empty();
modal_scope.cancelModal();
},
open: function () {
diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js
index 2489dee95e..e824bb3201 100644
--- a/awx/ui/static/js/helpers/Hosts.js
+++ b/awx/ui/static/js/helpers/Hosts.js
@@ -14,7 +14,7 @@
angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'HostListDefinition',
'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'AuthService', 'HostsHelper',
'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper',
- 'HostGroupsFormDefinition', 'VariablesHelper'
+ 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog'
])
.factory('SetEnabledMsg', [ function() {
@@ -420,10 +420,10 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis',
- 'WatchInventoryWindowResize', 'ToJSON', 'ParseVariableString',
+ 'WatchInventoryWindowResize', 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize',
function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, WatchInventoryWindowResize, ToJSON,
- ParseVariableString) {
+ ParseVariableString, CreateDialog, TextareaResize) {
return function(params) {
var parent_scope = params.scope,
@@ -432,14 +432,64 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
generator = GenerateForm,
form = HostForm,
defaultUrl = GetBasePath('hosts') + host_id + '/',
- scope = generator.inject(form, { mode: 'edit', modal: true, related: false, show_modal: false }),
+ scope = parent_scope.$new(),
master = {},
- relatedSets = {};
+ relatedSets = {},
+ buttons;
+ generator.inject(HostForm, { mode: 'edit', id: 'host-modal-dialog', breadCrumbs: false, related: false, scope: scope });
generator.reset();
- scope.formModalActionLabel = 'Save';
- scope.formModalHeader = 'Host Properties';
- scope.formModalCancelShow = true;
+
+ buttons = [{
+ label: "Cancel",
+ onClick: function() {
+ scope.cancelModal();
+ },
+ icon: "fa-times",
+ "class": "btn btn-default",
+ "id": "host-cancel-button"
+ },{
+ label: "Save",
+ onClick: function() {
+ scope.saveModal();
+ },
+ icon: "fa-check",
+ "class": "btn btn-primary",
+ "id": "host-save-button"
+ }];
+
+ CreateDialog({
+ scope: scope,
+ buttons: buttons,
+ width: 675,
+ height: 750,
+ minWidth: 400,
+ title: 'Host Properties',
+ id: 'host-modal-dialog',
+ onClose: function() {
+ Wait('stop');
+ scope.codeMirror.destroy();
+ $('#host-modal-dialog').empty();
+ WatchInventoryWindowResize();
+ },
+ onResizeStop: function() {
+ TextareaResize({
+ scope: scope,
+ textareaId: 'host_variables',
+ modalId: 'host-modal-dialog',
+ formId: 'host_form'
+ });
+ },
+ onOpen: function() {
+ TextareaResize({
+ scope: scope,
+ textareaId: 'host_variables',
+ modalId: 'host-modal-dialog',
+ formId: 'host_form'
+ });
+ }
+ });
+
scope.parseType = 'yaml';
if (scope.hostVariablesLoadedRemove) {
@@ -447,7 +497,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
}
scope.hostVariablesLoadedRemove = scope.$on('hostVariablesLoaded', function() {
var callback = function() { Wait('stop'); };
- $('#form-modal').modal('show');
+ $('#host-modal-dialog').dialog('open');
ParseTypeChange({ scope: scope, field_id: 'host_variables', onReady: callback });
});
@@ -496,6 +546,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
}
}
scope.variable_url = data.related.variable_data;
+ scope.has_inventory_sources = data.has_inventory_sources;
scope.$emit('hostLoaded');
})
.error( function(data, status) {
@@ -512,8 +563,8 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
var host = Find({ list: parent_scope.hosts, key: 'id', val: host_id }),
old_name = host.name;
host.name = scope.name;
- host.enabled = scope.enabled;
- host.enabled_flag = scope.enabled;
+ host.enabled = (scope.enabled) ? true : false;
+ host.enabled_flag = host.enabled;
SetStatus({ scope: parent_scope, host: host });
// Update any titles attributes created by ApplyEllipsis
@@ -522,40 +573,44 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
$('#hosts_table .host-name a[title="' + old_name + '"]').attr('title', host.name);
ApplyEllipsis('#hosts_table .host-name a');
// Close modal
- Wait('stop');
- $('#form-modal').modal('hide');
+ $('#host-modal-dialog').dialog('close');
}, 2000);
}
else {
// Close modal
Wait('stop');
- $('#form-modal').modal('hide');
+ $('#host-modal-dialog').dialog('close');
}
// Restore ellipsis response to window resize
WatchInventoryWindowResize();
});
// Save changes to the parent
- scope.formModalAction = function() {
-
+ scope.saveModal = function() {
+
Wait('start');
var fld, data={};
- data.variables = ToJSON(scope.parseType, scope.variables, true);
- for (fld in form.fields) {
- data[fld] = scope[fld];
+ try {
+ data.variables = ToJSON(scope.parseType, scope.variables, true);
+ for (fld in form.fields) {
+ data[fld] = scope[fld];
+ }
+ data.inventory = inventory_id;
+ Rest.setUrl(defaultUrl);
+ Rest.put(data)
+ .success( function() {
+ scope.$emit('saveCompleted');
+ })
+ .error( function(data, status) {
+ Wait('stop');
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status });
+ });
+ }
+ catch(e) {
+ // ignore. ToJSON will have already alerted the user
}
- data.inventory = inventory_id;
- Rest.setUrl(defaultUrl);
- Rest.put(data)
- .success( function() {
- scope.$emit('saveCompleted');
- })
- .error( function(data, status) {
- Wait('stop');
- ProcessErrors(scope, data, status, form,
- { hdr: 'Error!', msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status });
- });
};
// Cancel
@@ -568,7 +623,7 @@ function($rootScope, $location, $log, $routeParams, Rest, Alert, HostForm, Gener
};
scope.cancelModal = function() {
- WatchInventoryWindowResize();
+ $('#host-modal-dialog').dialog('close');
};
};
diff --git a/awx/ui/static/js/helpers/Variables.js b/awx/ui/static/js/helpers/Variables.js
index 0a94b244e8..da6f3a6f04 100644
--- a/awx/ui/static/js/helpers/Variables.js
+++ b/awx/ui/static/js/helpers/Variables.js
@@ -124,7 +124,7 @@ angular.module('VariablesHelper', ['Utilities'])
var i, keys = Object.keys(objToSort), newObj = {};
keys = keys.sort();
for (i=0; i < keys.length; i++) {
- if (typeof objToSort[keys[i]] === 'object' && !Array.isArray(objToSort[keys[i]])) {
+ if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) {
newObj[keys[i]] = sortIt(objToSort[keys[i]]);
}
else {
@@ -133,7 +133,6 @@ angular.module('VariablesHelper', ['Utilities'])
}
return newObj;
}
-
newObj = sortIt(variableObj);
return newObj;
};
diff --git a/awx/ui/static/less/jquery-ui-overrides.less b/awx/ui/static/less/jquery-ui-overrides.less
index d9f2feedf1..5d0882fc73 100644
--- a/awx/ui/static/less/jquery-ui-overrides.less
+++ b/awx/ui/static/less/jquery-ui-overrides.less
@@ -83,3 +83,7 @@ table.ui-datepicker-calendar {
background-color: #000;
opacity: .6;
}
+
+.ui-dialog-content.ui-widget-content {
+ padding-top: 20px;
+}
diff --git a/awx/ui/static/lib/ansible/Modal.js b/awx/ui/static/lib/ansible/Modal.js
new file mode 100644
index 0000000000..be43142756
--- /dev/null
+++ b/awx/ui/static/lib/ansible/Modal.js
@@ -0,0 +1,189 @@
+/************************************
+ *
+ * Copyright (c) 2014 AnsibleWorks, Inc.
+ *
+ * Modal.js
+ *
+ * Create a draggable, resizable modal dialog using jQueryUI.
+ *
+ *
+ */
+
+'use strict';
+
+angular.module('ModalDialog', ['Utilities', 'ParseHelper'])
+
+ /**
+ *
+ * CreateDialog({
+ * scope: - Required, $scope associated with the #id DOM element
+ * buttons: - Required, Array of button objects. See example below.
+ * width: - Desired width of modal dialog on open. Defaults to 500.
+ * height: - Desired height of modal on open. Defaults to 600.
+ * minWidth: - Minimum width that must be maintained regardless of reize attempts. Defaults to 400.
+ * title: - Modal window title, optional
+ * onResizeStop: - Function to call when user stops resizing the dialog, optional
+ * onClose: - Function to call after window closes, optional
+ * onOpen: - Function to call after window opens, optional
+ * callback: - String to pass to scope.$emit() after dialog is created, optional
+ * })
+ *
+ * Note that the dialog will be created but not opened. It's up to the caller to open it. Use callback
+ * option to respond to dialog created event.
+ */
+ .factory('CreateDialog', ['Empty', function(Empty) {
+
+ return function(params) {
+
+ var scope = params.scope,
+ buttonSet = params.buttons,
+ width = params.width || 500,
+ height = params.height || 600,
+ minWidth = params.minWidth || 300,
+ title = params.title || '',
+ onResizeStop = params.onResizeStop,
+ onClose = params.onClose,
+ onOpen = params.onOpen,
+ callback = params.callback,
+ buttons,
+ id = params.id,
+ x, y, wh, ww;
+
+ if (Empty(buttonSet)) {
+ // Default button object
+ buttonSet = [{
+ label: "OK",
+ onClick: function() {
+ scope.modalOK();
+ },
+ icon: "",
+ "class": "btn btn-primary",
+ "id": "dialog-ok-button"
+ }];
+ }
+
+ buttons = {};
+ buttonSet.forEach( function(btn) {
+ buttons[btn.label] = btn.onClick;
+ });
+
+ // Set modal dimensions based on viewport width
+ ww = $(document).width();
+ wh = $('body').height();
+ x = (width > ww) ? ww - 10 : width;
+ y = (height > wh) ? wh - 10 : height;
+
+ // Create the modal
+ $('#' + id).dialog({
+ buttons: buttons,
+ modal: true,
+ width: x,
+ height: y,
+ autoOpen: false,
+ minWidth: minWidth,
+ title: title,
+ create: function () {
+ // Fix the close button
+ $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x');
+
+ // Make buttons bootstrapy
+ $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-buttonset button').each(function () {
+ var txt = $(this).text(), self = $(this);
+ buttonSet.forEach(function(btn) {
+ if (txt === btn.label) {
+ self.attr({ "class": btn['class'], "id": btn.id });
+ if (btn.icon) {
+ self.empty().html('
' + btn.label);
+ }
+ }
+ });
+ });
+
+ setTimeout(function() {
+ scope.$apply(function() {
+ scope.$emit(callback);
+ });
+ }, 300);
+ },
+ resizeStop: function () {
+ // for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
+ var dialog = $('.ui-dialog[aria-describedby="' + id + '"]'),
+ content = dialog.find('#' + id);
+ content.width(dialog.width() - 28);
+ if (onResizeStop) {
+ onResizeStop();
+ }
+ },
+ close: function () {
+ // Destroy on close
+ $('.tooltip').each(function () {
+ // Remove any lingering tooltip
elements
+ $(this).remove();
+ });
+ $('.popover').each(function () {
+ // remove lingering popover
elements
+ $(this).remove();
+ });
+ $('#' + id).dialog('destroy');
+ $('#' + id).hide();
+ if (onClose) {
+ onClose();
+ }
+ },
+ open: function () {
+ if (onOpen) {
+ onOpen();
+ }
+ }
+ });
+ };
+ }])
+
+ /**
+ * TextareaResize({
+ * scope: - $scope associated with the textarea element
+ * textareaId: - id attribute value of the textarea
+ * modalId: - id attribute of the
element used to create the modal
+ * formId: - id attribute of the textarea's parent form
+ * })
+ *
+ * Use to resize a textarea field contained on a modal. Has only been tested where the
+ * form contains 1 textarea and the the textarea is at the bottom of the form/modal.
+ *
+ **/
+ .factory('TextareaResize', ['ParseTypeChange', 'Wait', function(ParseTypeChange, Wait){
+ return function(params) {
+
+ var scope = params.scope,
+ textareaId = params.textareaId,
+ modalId = params.modalId,
+ formId = params.formId,
+ textarea,
+ formHeight, model, windowHeight, offset, rows;
+
+ function waitStop() {
+ Wait('stop');
+ }
+
+ // Attempt to create the largest textarea field that will fit on the window. Minimum
+ // height is 6 rows, so on short windows you will see vertical scrolling
+ textarea = $('#' + textareaId);
+ if (scope.codeMirror) {
+ model = textarea.attr('ng-model');
+ scope[model] = scope.codeMirror.getValue();
+ scope.codeMirror.destroy();
+ }
+ textarea.attr('rows', 1);
+ formHeight = $('#' + formId).height();
+ windowHeight = $('#' + modalId).height() - 20; //leave a margin of 20px
+ offset = Math.floor(windowHeight - formHeight);
+ rows = Math.floor(offset / 20);
+ rows = (rows < 6) ? 6 : rows;
+ textarea.attr('rows', rows);
+ while(rows > 6 && $('#' + formId).height() > $('#' + modalId).height()) {
+ rows--;
+ textarea.attr('rows', rows);
+ }
+ ParseTypeChange({ scope: scope, field_id: textareaId, onReady: waitStop });
+ };
+ }]);
\ No newline at end of file
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 06fb471935..04bb1f7b60 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -483,6 +483,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += (field.falseValue !== undefined) ? Attr(field, 'falseValue') : "";
html += (field.checked) ? "checked " : "";
html += (field.readonly) ? "disabled " : "";
+ html += (field.ngDisabled) ? "ng-disabled=\"" + field.ngDisabled + "\" " : "";
html += " > ";
if (label) {
diff --git a/awx/ui/static/partials/inventory-edit.html b/awx/ui/static/partials/inventory-edit.html
index c9966999e6..8927f6f7d2 100644
--- a/awx/ui/static/partials/inventory-edit.html
+++ b/awx/ui/static/partials/inventory-edit.html
@@ -47,6 +47,8 @@
+
+
\ No newline at end of file
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html
index a7d1a9da5f..85079598ad 100644
--- a/awx/ui/templates/ui/index.html
+++ b/awx/ui/templates/ui/index.html
@@ -62,6 +62,7 @@
+