From d5cf0b273ad511d6cb99fbcb900eff528fe172df Mon Sep 17 00:00:00 2001 From: Petr Vobornik Date: Thu, 13 Feb 2014 13:10:18 +0100 Subject: webui: move RPC code from IPA module to its own module - moves RPC code from ipa.js to it's own module - part of ongoing effort where the ultimate goal is to get rid of ipa.js and IPA namespace Reviewed-By: Adam Misnyovszki --- install/ui/src/freeipa/ipa.js | 884 --------------------------------------- install/ui/src/freeipa/rpc.js | 930 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 930 insertions(+), 884 deletions(-) create mode 100644 install/ui/src/freeipa/rpc.js (limited to 'install/ui') diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js index 1d58712de..7ff7872a8 100644 --- a/install/ui/src/freeipa/ipa.js +++ b/install/ui/src/freeipa/ipa.js @@ -621,799 +621,6 @@ IPA.password_selfservice = function() { reset_dialog.open(); }; -/** - * Call an IPA command over JSON-RPC. - * - * @class IPA.command - * - * @param {Object} spec - construct specification - * @param {string} spec.name - command name (optional) - * @param {string} spec.entity - command entity(name) (optional) - * @param {string} spec.method - command method - * @param {string[]} spec.args - list of arguments, e.g. ['username'] - * @param {Object} spec.options - dict of options, e.g. {givenname: 'Petr'} - * @param {Function} spec.on_success - callback function if command succeeds - * @param {Function} spec.on_error - callback function if command fails - * - */ -IPA.command = function(spec) { - - spec = spec || {}; - - var that = IPA.object(); - - /** @property {string} name Name */ - that.name = spec.name; - - /** @property {entity.entity} entity Entity */ - that.entity = spec.entity; - - /** @property {string} method Method */ - that.method = spec.method; - - /** @property {string[]} args Command Arguments */ - that.args = $.merge([], spec.args || []); - - /** @property {Object} options Option map */ - that.options = $.extend({}, spec.options || {}); - - /** - * Success handler - * @property {Function} - * @param {Object} data - * @param {string} text_status - * @param {XMLHttpRequest} xhr - */ - that.on_success = spec.on_success; - - /** - * Error handler - * @property {Function} - * @param {XMLHttpRequest} xhr - * @param {string} text_status - * @param {{name:string,message:string}} error_thrown - */ - that.on_error = spec.on_error; - - /** - * Allow retrying of execution if previous ended as error - * - * Manifested by error dialog. Set it to `false` for custom error dialogs or - * error handling without any dialog. - * @property {Boolean} retry=true - */ - that.retry = typeof spec.retry == 'undefined' ? true : spec.retry; - - /** @property {string} error_message Default error message */ - that.error_message = text.get(spec.error_message || '@i18n:dialogs.batch_error_message', 'Some operations failed.'); - - /** @property {ordered_map.} error_messages Error messages map */ - that.error_messages = $.ordered_map({ - 911: 'Missing HTTP referer.
You have to configure your browser to send HTTP referer header.' - }); - - /** - * Get command name - * - * - it's `entity.name + '_' + method` - * - or `method` - * @return {string} - */ - that.get_command = function() { - return (that.entity ? that.entity+'_' : '') + that.method; - }; - - /** - * Add argument - * @param {string} arg - */ - that.add_arg = function(arg) { - that.args.push(arg); - }; - - /** - * Add arguments - * @param {string[]} args - */ - that.add_args = function(args) { - $.merge(that.args, args); - }; - - /** - * Set option - * @param {string} name - * @param {Mixed} value - */ - that.set_option = function(name, value) { - that.options[name] = value; - }; - - /** - * Extends options map with another options map - * - * @param {{opt1:Mixed, opt2:Mixed}} options - */ - that.set_options = function(options) { - $.extend(that.options, options); - }; - - /** - * Add value to an option - * - * - creates a new option if it does not exist yet - * - for option overriding use `set_option` method - * @param {string} name - * @param {Mixed} value - */ - that.add_option = function(name, value) { - var values = that.options[name]; - if (!values) { - values = []; - that.options[name] = values; - } - values.push(value); - }; - - /** - * Get option value - * @return {Mixed} - */ - that.get_option = function(name) { - return that.options[name]; - }; - - /** - * Remove option from option map - */ - that.remove_option = function(name) { - delete that.options[name]; - }; - - /** - * Execute the command. - * - * Set `on_success` and/or `on_error` handlers to be informed about result. - */ - that.execute = function() { - - function dialog_open(xhr, text_status, error_thrown) { - - var ajax = this; - - var dialog = IPA.error_dialog({ - xhr: xhr, - text_status: text_status, - error_thrown: error_thrown, - command: that - }); - - dialog.on_cancel = function() { - dialog.close(); - if (that.on_error) { - that.on_error.call(ajax, xhr, text_status, error_thrown); - } - }; - - dialog.open(); - } - - function auth_dialog_open(xhr, text_status, error_thrown) { - - var ajax = this; - - var dialog = IPA.unauthorized_dialog({ - xhr: xhr, - text_status: text_status, - error_thrown: error_thrown, - close_on_escape: false, - command: that - }); - - dialog.open(); - } - - /* - * Special error handler used the first time this command is - * submitted. It checks to see if the session credentials need - * to be acquired and if so sends a request to a special url - * to establish the sesion credentials. If acquiring the - * session credentials is successful it simply resubmits the - * exact same command after setting the error handler back to - * the normal error handler. If aquiring the session - * credentials fails the normal error handler is invoked to - * process the error returned from the attempt to aquire the - * session credentials. - */ - function error_handler_login(xhr, text_status, error_thrown) { - if (xhr.status === 401) { - var login_status = IPA.get_credentials(); - - if (login_status === 200) { - that.request.error = error_handler; - $.ajax(that.request); - return; - } - } - // error_handler() calls IPA.hide_activity_icon() - error_handler.call(this, xhr, text_status, error_thrown); - } - - /* - * Normal error handler, handles all errors. - * error_handler_login() is initially used to trap the - * special case need to aquire session credentials, this is - * not a true error, rather it's an indication an extra step - * needs to be taken before normal processing can continue. - */ - function error_handler(xhr, text_status, error_thrown) { - - IPA.hide_activity_icon(); - - if (xhr.status === 401) { - auth_dialog_open(xhr, text_status, error_thrown); - return; - } else if (!error_thrown) { - error_thrown = { - name: xhr.responseText || text.get('@i18n:errors.unknown_error', 'Unknown Error'), - message: xhr.statusText || text.get('@i18n:errors.unknown_error', 'Unknown Error') - }; - - } else if (typeof error_thrown == 'string') { - error_thrown = { - name: error_thrown, - message: error_thrown - }; - } - - // custom messages for set of codes - var error_msg = that.error_messages.get(error_thrown.code); - if (error_msg) { - error_msg = error_msg.replace('${message}', error_thrown.message); - error_thrown.message = error_msg; - } - - // global specical cases error handlers section - - // With trusts, user from trusted domain can use his ticket but he - // doesn't have rights for LDAP modify. It will throw internal errror. - // We should offer form base login. - if (xhr.status === 500 && IPA.ui.logged_kerberos && !IPA.ui.initialized) { - auth_dialog_open(xhr, text_status, error_thrown); - return; - } - - if (that.retry) { - dialog_open.call(this, xhr, text_status, error_thrown); - - } else if (that.on_error) { - //custom error handling, maintaining AJAX call's context - that.on_error.call(this, xhr, text_status, error_thrown); - } - } - - function success_handler(data, text_status, xhr) { - - if (!data) { - // error_handler() calls IPA.hide_activity_icon() - error_handler.call(this, xhr, text_status, /* error_thrown */ { - name: text.get('@i18n:errors.http_error', 'HTTP Error')+' '+xhr.status, - url: this.url, - message: data ? xhr.statusText : text.get('@i18n:errors.no_response', 'No response') - }); - - } else if (IPA.version && data.version && IPA.version !== data.version) { - window.location.reload(); - - } else if (IPA.principal && data.principal && IPA.principal !== data.principal) { - window.location.reload(); - - } else if (data.error) { - // error_handler() calls IPA.hide_activity_icon() - error_handler.call(this, xhr, text_status, /* error_thrown */ { - name: text.get('@i18n:errors.ipa_error', 'IPA Error') + ' ' + - data.error.code + ': ' + data.error.name, - code: data.error.code, - message: data.error.message, - data: data - }); - - } else { - IPA.hide_activity_icon(); - - var ajax = this; - var failed = that.get_failed(that, data.result, text_status, xhr); - if (!failed.is_empty()) { - var dialog = IPA.error_dialog({ - xhr: xhr, - text_status: text_status, - error_thrown: { - name: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), - message: that.error_message - }, - command: that, - errors: failed.errors, - visible_buttons: ['ok'] - }); - - dialog.on_ok = function() { - dialog.close(); - if (that.on_success) that.on_success.call(ajax, data, text_status, xhr); - }; - - dialog.open(); - - } else { - //custom success handling, maintaining AJAX call's context - if (that.on_success) that.on_success.call(this, data, text_status, xhr); - } - } - } - - that.data = { - method: that.get_command(), - params: [that.args, that.options] - }; - - that.request = { - url: IPA.json_url || IPA.json_path + '/' + (that.name || that.data.method) + '.json', - data: JSON.stringify(that.data), - success: success_handler, - error: error_handler_login - }; - - IPA.display_activity_icon(); - $.ajax(that.request); - }; - - /** - * Parse successful command result and get all errors. - * @protected - * @param {IPA.command} command - * @param {Object} result - * @param {string} text_status - * @param {XMLHttpRequest} xhr - * @return {IPA.error_list} - */ - that.get_failed = function(command, result, text_status, xhr) { - var errors = IPA.error_list(); - if(result && result.failed) { - for(var association in result.failed) { - for(var member_name in result.failed[association]) { - var member = result.failed[association][member_name]; - for(var i = 0; i < member.length; i++) { - if(member[i].length > 1) { - var name = text.get('@i18n:errors.ipa_error', 'IPA Error'); - var message = member[i][1]; - if(member[i][0]) - message = member[i][0] + ': ' + message; - errors.add(command, name, message, text_status); - } - } - } - } - } - return errors; - }; - - /** - * Check if command accepts option - * @param {string} option_name - * @return {Boolean} - */ - that.check_option = function(option_name) { - - var metadata = IPA.get_command_option(that.get_command(), option_name); - return metadata !== null; - }; - - /** - * Encodes command into JSON-RPC command object - * @return {Object} - */ - that.to_json = function() { - var json = {}; - - json.method = that.get_command(); - - json.params = []; - json.params[0] = that.args || []; - json.params[1] = that.options || {}; - - return json; - }; - - /** - * Encodes command into CLI command string - * @return {string} - */ - that.to_string = function() { - var string = that.get_command().replace(/_/g, '-'); - - for (var i=0; i} spec.commands - IPA commands to be executed - * @param {Function} spec.on_success - callback function if command succeeds - * @param {Function} spec.on_error - callback function if command fails - */ -IPA.batch_command = function(spec) { - - spec = spec || {}; - - spec.method = 'batch'; - - var that = IPA.command(spec); - - /** @property {IPA.command[]} commands Commands */ - that.commands = []; - /** @property {IPA.error_list} errors Errors */ - that.errors = IPA.error_list(); - - /** - * Show error if some command fail - * @property {Boolean} show_error=true - */ - that.show_error = typeof spec.show_error == 'undefined' ? - true : spec.show_error; - - /** - * Add command - * @param {IPA.command} command - */ - that.add_command = function(command) { - that.commands.push(command); - that.add_arg(command.to_json()); - }; - - /** - * Add commands - * @param {IPA.command[]} commands - */ - that.add_commands = function(commands) { - for (var i=0; i 0) { - var ajax = this; - var dialog = IPA.error_dialog({ - xhr: xhr, - text_status: text_status, - error_thrown: { - name: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), - message: that.error_message - }, - command: that, - errors: that.errors.errors, - visible_buttons: [ 'ok' ] - }); - - dialog.on_ok = function() { - dialog.close(); - if (that.on_success) that.on_success.call(ajax, data, text_status, xhr); - }; - - dialog.open(); - - } else { - if (that.on_success) that.on_success.call(this, data, text_status, xhr); - } - }; - - /** - * Internal XHR error handler - * @protected - * @param {XMLHttpRequest} xhr - * @param {string} text_status - * @param {{name:string,message:string}} error_thrown - */ - that.batch_command_on_error = function(xhr, text_status, error_thrown) { - // TODO: undefined behavior - if (that.on_error) { - that.on_error.call(this, xhr, text_status, error_thrown); - } - }; - - return that; -}; - -/** - * Call multiple IPA commands over JSON-RPC separately and wait for every - * command's response. - * - * - concurrent command fails if any command fails - * - result is reported when each command finishes - * - * @class IPA.concurrent_command - * - * @param {Object} spec - construct specification - * @param {Array.} spec.commands - IPA commands to execute - * @param {Function} spec.on_success - callback function if each command succeed - * @param {Function} spec.on_error - callback function one command fails - * - */ -IPA.concurrent_command = function(spec) { - - spec = spec || {}; - var that = IPA.object(); - - /** @property {IPA.command[]} commands Commands */ - that.commands = []; - - /** - * Success handler - * @property {Function} - */ - that.on_success = spec.on_success; - - /** - * Error handler - * @property {Function} - */ - that.on_error = spec.on_error; - - /** - * Add commands - * @param {IPA.command[]} commands - */ - that.add_commands = function(commands) { - - if(commands && commands.length) { - for(var i=0; i < commands.length; i++) { - that.commands.push({ - command: commands[i] - }); - } - } - }; - - /** - * Execute the commands one by one. - */ - that.execute = function() { - - var command_info, command, i; - - //prepare for execute - for(i=0; i < that.commands.length; i++) { - command_info = that.commands[i]; - command = command_info.command; - if(!command) { - var dialog = IPA.message_dialog({ - name: 'internal_error', - title: text.get('@i18n:errors.error', 'Error'), - message: text.get('@i18n:errors.internal_error', 'Internal error.') - }); - break; - } - command_info.completed = false; - command_info.success = false; - command_info.on_success = command_info.on_success || command.on_success; - command_info.on_error = command_info.on_error || command.on_error; - command.on_success = function(command_info) { - return function(data, text_status, xhr) { - that.success_handler.call(this, command_info, data, text_status, xhr); - }; - }(command_info); - command.on_error = function(command_info) { - return function(xhr, text_status, error_thrown) { - that.error_handler.call(this, command_info, xhr, text_status, error_thrown); - }; - }(command_info); - } - - //execute - for(i=0; i < that.commands.length; i++) { - command = that.commands[i].command; - command.execute(); - } - }; - - /** - * Internal error handler - * @protected - */ - that.error_handler = function(command_info, xhr, text_status, error_thrown) { - - command_info.completed = true; - command_info.success = false; - command_info.xhr = xhr; - command_info.text_status = text_status; - command_info.error_thrown = error_thrown; - command_info.context = this; - that.command_completed(); - }; - - /** - * Internal success handler - * @protected - */ - that.success_handler = function(command_info, data, text_status, xhr) { - - command_info.completed = true; - command_info.success = true; - command_info.data = data; - command_info.text_status = text_status; - command_info.xhr = xhr; - command_info.context = this; - that.command_completed(); - }; - - /** - * Check if all commands finished. - * If so, report it. - * @protected - */ - that.command_completed = function() { - - var all_completed = true; - var all_success = true; - - for(var i=0; i < that.commands.length; i++) { - var command_info = that.commands[i]; - all_completed = all_completed && command_info.completed; - all_success = all_success && command_info.success; - } - - if(all_completed) { - if(all_success) { - that.on_success_all(); - } else { - that.on_error_all(); - } - } - }; - - /** - * Call each command's success handler and `on_success`. - * @protected - */ - that.on_success_all = function() { - - for(var i=0; i < that.commands.length; i++) { - var command_info = that.commands[i]; - if(command_info.on_success) { - command_info.on_success.call( - command_info.context, - command_info.data, - command_info.text_status, - command_info.xhr); - } - } - - if(that.on_success) { - that.on_success(); - } - }; - - /** - * Call each command's error handler and `on_success`. - * @protected - */ - that.on_error_all = function() { - - if(that.on_error) { - that.on_error(); - - } else { - var dialog = IPA.message_dialog({ - name: 'operation_error', - title: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), - message: text.get('@i18n:dialogs.batch_error_message', 'Some operations failed.') - }); - - dialog.open(); - } - }; - - that.add_commands(spec.commands); - - return that; -}; - /** * Build object with {@link builder}. * @member IPA @@ -1794,97 +1001,6 @@ IPA.error_dialog = function(spec) { return that; }; -/** - * Error list - * - * Collection for RPC command errors. - * - * @class IPA.error_list - * @private - */ -IPA.error_list = function() { - var that = IPA.object(); - - /** Clear errors */ - that.clear = function() { - that.errors = []; - }; - - /** Add error */ - that.add = function(command, name, message, status) { - that.errors.push({ - command: command, - name: name, - message: message, - status: status - }); - }; - - /** Add errors */ - that.add_range = function(error_list) { - that.errors = that.errors.concat(error_list.errors); - }; - - /** - * Check if there are no errors - * @return {Boolean} - */ - that.is_empty = function () { - return that.errors.length === 0; - }; - - that.clear(); - return that; -}; - -/** - * Error handler for IPA.command which handles error #4304 as success. - * - * 4304 is raised when part of an operation succeeds and the part that failed - * isn't critical. - * @member IPA - * @param {IPA.entity_adder_dialog} adder_dialog - */ -IPA.create_4304_error_handler = function(adder_dialog) { - - var set_pkey = function(result) { - - var pkey_name = adder_dialog.entity.metadata.primary_key; - var args = adder_dialog.command.args; - var pkey = args[args.length-1]; - result[pkey_name] = pkey; - }; - - return function (xhr, text_status, error_thrown) { - - var ajax = this; - var command = adder_dialog.command; - var data = error_thrown.data; - var dialog = null; - - if (data && data.error && data.error.code === 4304) { - dialog = IPA.message_dialog({ - name: 'error_4304_info', - message: data.error.message, - title: adder_dialog.title, - on_ok: function() { - data.result = { result: {} }; - set_pkey(data.result.result); - command.on_success.call(ajax, data, text_status, xhr); - } - }); - } else { - dialog = IPA.error_dialog({ - xhr: xhr, - text_status: text_status, - error_thrown: error_thrown, - command: command - }); - } - - dialog.open(); - }; -}; /** * Unauthorized dialog diff --git a/install/ui/src/freeipa/rpc.js b/install/ui/src/freeipa/rpc.js new file mode 100644 index 000000000..f2878dda3 --- /dev/null +++ b/install/ui/src/freeipa/rpc.js @@ -0,0 +1,930 @@ +/* Authors: + * Pavel Zuna + * Adam Young + * Endi Dewata + * John Dennis + * Petr Vobornik + * + * Copyright (C) 2014 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +define([ + 'dojo/_base/lang', + './ipa', + './text', + 'exports' + ], + function(lang, IPA, text, exports) { + +var rpc = {}; + +/** + * Call an IPA command over JSON-RPC. + * + * @class rpc.command + * + * @param {Object} spec - construct specification + * @param {string} spec.name - command name (optional) + * @param {string} spec.entity - command entity(name) (optional) + * @param {string} spec.method - command method + * @param {string[]} spec.args - list of arguments, e.g. ['username'] + * @param {Object} spec.options - dict of options, e.g. {givenname: 'Petr'} + * @param {Function} spec.on_success - callback function if command succeeds + * @param {Function} spec.on_error - callback function if command fails + * + */ +rpc.command = function(spec) { + + spec = spec || {}; + + var that = IPA.object(); + + /** @property {string} name Name */ + that.name = spec.name; + + /** @property {entity.entity} entity Entity */ + that.entity = spec.entity; + + /** @property {string} method Method */ + that.method = spec.method; + + /** @property {string[]} args Command Arguments */ + that.args = $.merge([], spec.args || []); + + /** @property {Object} options Option map */ + that.options = $.extend({}, spec.options || {}); + + /** + * Success handler + * @property {Function} + * @param {Object} data + * @param {string} text_status + * @param {XMLHttpRequest} xhr + */ + that.on_success = spec.on_success; + + /** + * Error handler + * @property {Function} + * @param {XMLHttpRequest} xhr + * @param {string} text_status + * @param {{name:string,message:string}} error_thrown + */ + that.on_error = spec.on_error; + + /** + * Allow retrying of execution if previous ended as error + * + * Manifested by error dialog. Set it to `false` for custom error dialogs or + * error handling without any dialog. + * @property {Boolean} retry=true + */ + that.retry = typeof spec.retry == 'undefined' ? true : spec.retry; + + /** @property {string} error_message Default error message */ + that.error_message = text.get(spec.error_message || '@i18n:dialogs.batch_error_message', 'Some operations failed.'); + + /** @property {ordered_map.} error_messages Error messages map */ + that.error_messages = $.ordered_map({ + 911: 'Missing HTTP referer.
You have to configure your browser to send HTTP referer header.' + }); + + /** + * Get command name + * + * - it's `entity.name + '_' + method` + * - or `method` + * @return {string} + */ + that.get_command = function() { + return (that.entity ? that.entity+'_' : '') + that.method; + }; + + /** + * Add argument + * @param {string} arg + */ + that.add_arg = function(arg) { + that.args.push(arg); + }; + + /** + * Add arguments + * @param {string[]} args + */ + that.add_args = function(args) { + $.merge(that.args, args); + }; + + /** + * Set option + * @param {string} name + * @param {Mixed} value + */ + that.set_option = function(name, value) { + that.options[name] = value; + }; + + /** + * Extends options map with another options map + * + * @param {{opt1:Mixed, opt2:Mixed}} options + */ + that.set_options = function(options) { + $.extend(that.options, options); + }; + + /** + * Add value to an option + * + * - creates a new option if it does not exist yet + * - for option overriding use `set_option` method + * @param {string} name + * @param {Mixed} value + */ + that.add_option = function(name, value) { + var values = that.options[name]; + if (!values) { + values = []; + that.options[name] = values; + } + values.push(value); + }; + + /** + * Get option value + * @return {Mixed} + */ + that.get_option = function(name) { + return that.options[name]; + }; + + /** + * Remove option from option map + */ + that.remove_option = function(name) { + delete that.options[name]; + }; + + /** + * Execute the command. + * + * Set `on_success` and/or `on_error` handlers to be informed about result. + */ + that.execute = function() { + + function dialog_open(xhr, text_status, error_thrown) { + + var ajax = this; + + var dialog = IPA.error_dialog({ + xhr: xhr, + text_status: text_status, + error_thrown: error_thrown, + command: that + }); + + dialog.on_cancel = function() { + dialog.close(); + if (that.on_error) { + that.on_error.call(ajax, xhr, text_status, error_thrown); + } + }; + + dialog.open(); + } + + function auth_dialog_open(xhr, text_status, error_thrown) { + + var ajax = this; + + var dialog = IPA.unauthorized_dialog({ + xhr: xhr, + text_status: text_status, + error_thrown: error_thrown, + close_on_escape: false, + command: that + }); + + dialog.open(); + } + + /* + * Special error handler used the first time this command is + * submitted. It checks to see if the session credentials need + * to be acquired and if so sends a request to a special url + * to establish the sesion credentials. If acquiring the + * session credentials is successful it simply resubmits the + * exact same command after setting the error handler back to + * the normal error handler. If aquiring the session + * credentials fails the normal error handler is invoked to + * process the error returned from the attempt to aquire the + * session credentials. + */ + function error_handler_login(xhr, text_status, error_thrown) { + if (xhr.status === 401) { + var login_status = IPA.get_credentials(); + + if (login_status === 200) { + that.request.error = error_handler; + $.ajax(that.request); + return; + } + } + // error_handler() calls IPA.hide_activity_icon() + error_handler.call(this, xhr, text_status, error_thrown); + } + + /* + * Normal error handler, handles all errors. + * error_handler_login() is initially used to trap the + * special case need to aquire session credentials, this is + * not a true error, rather it's an indication an extra step + * needs to be taken before normal processing can continue. + */ + function error_handler(xhr, text_status, error_thrown) { + + IPA.hide_activity_icon(); + + if (xhr.status === 401) { + auth_dialog_open(xhr, text_status, error_thrown); + return; + } else if (!error_thrown) { + error_thrown = { + name: xhr.responseText || text.get('@i18n:errors.unknown_error', 'Unknown Error'), + message: xhr.statusText || text.get('@i18n:errors.unknown_error', 'Unknown Error') + }; + + } else if (typeof error_thrown == 'string') { + error_thrown = { + name: error_thrown, + message: error_thrown + }; + } + + // custom messages for set of codes + var error_msg = that.error_messages.get(error_thrown.code); + if (error_msg) { + error_msg = error_msg.replace('${message}', error_thrown.message); + error_thrown.message = error_msg; + } + + // global specical cases error handlers section + + // With trusts, user from trusted domain can use his ticket but he + // doesn't have rights for LDAP modify. It will throw internal errror. + // We should offer form base login. + if (xhr.status === 500 && IPA.ui.logged_kerberos && !IPA.ui.initialized) { + auth_dialog_open(xhr, text_status, error_thrown); + return; + } + + if (that.retry) { + dialog_open.call(this, xhr, text_status, error_thrown); + + } else if (that.on_error) { + //custom error handling, maintaining AJAX call's context + that.on_error.call(this, xhr, text_status, error_thrown); + } + } + + function success_handler(data, text_status, xhr) { + + if (!data) { + // error_handler() calls IPA.hide_activity_icon() + error_handler.call(this, xhr, text_status, /* error_thrown */ { + name: text.get('@i18n:errors.http_error', 'HTTP Error')+' '+xhr.status, + url: this.url, + message: data ? xhr.statusText : text.get('@i18n:errors.no_response', 'No response') + }); + + } else if (IPA.version && data.version && IPA.version !== data.version) { + window.location.reload(); + + } else if (IPA.principal && data.principal && IPA.principal !== data.principal) { + window.location.reload(); + + } else if (data.error) { + // error_handler() calls IPA.hide_activity_icon() + error_handler.call(this, xhr, text_status, /* error_thrown */ { + name: text.get('@i18n:errors.ipa_error', 'IPA Error') + ' ' + + data.error.code + ': ' + data.error.name, + code: data.error.code, + message: data.error.message, + data: data + }); + + } else { + IPA.hide_activity_icon(); + + var ajax = this; + var failed = that.get_failed(that, data.result, text_status, xhr); + if (!failed.is_empty()) { + var dialog = IPA.error_dialog({ + xhr: xhr, + text_status: text_status, + error_thrown: { + name: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), + message: that.error_message + }, + command: that, + errors: failed.errors, + visible_buttons: ['ok'] + }); + + dialog.on_ok = function() { + dialog.close(); + if (that.on_success) that.on_success.call(ajax, data, text_status, xhr); + }; + + dialog.open(); + + } else { + //custom success handling, maintaining AJAX call's context + if (that.on_success) that.on_success.call(this, data, text_status, xhr); + } + } + } + + that.data = { + method: that.get_command(), + params: [that.args, that.options] + }; + + that.request = { + url: IPA.json_url || IPA.json_path + '/' + (that.name || that.data.method) + '.json', + data: JSON.stringify(that.data), + success: success_handler, + error: error_handler_login + }; + + IPA.display_activity_icon(); + $.ajax(that.request); + }; + + /** + * Parse successful command result and get all errors. + * @protected + * @param {IPA.command} command + * @param {Object} result + * @param {string} text_status + * @param {XMLHttpRequest} xhr + * @return {IPA.error_list} + */ + that.get_failed = function(command, result, text_status, xhr) { + var errors = IPA.error_list(); + if(result && result.failed) { + for(var association in result.failed) { + for(var member_name in result.failed[association]) { + var member = result.failed[association][member_name]; + for(var i = 0; i < member.length; i++) { + if(member[i].length > 1) { + var name = text.get('@i18n:errors.ipa_error', 'IPA Error'); + var message = member[i][1]; + if(member[i][0]) + message = member[i][0] + ': ' + message; + errors.add(command, name, message, text_status); + } + } + } + } + } + return errors; + }; + + /** + * Check if command accepts option + * @param {string} option_name + * @return {Boolean} + */ + that.check_option = function(option_name) { + + var metadata = IPA.get_command_option(that.get_command(), option_name); + return metadata !== null; + }; + + /** + * Encodes command into JSON-RPC command object + * @return {Object} + */ + that.to_json = function() { + var json = {}; + + json.method = that.get_command(); + + json.params = []; + json.params[0] = that.args || []; + json.params[1] = that.options || {}; + + return json; + }; + + /** + * Encodes command into CLI command string + * @return {string} + */ + that.to_string = function() { + var string = that.get_command().replace(/_/g, '-'); + + for (var i=0; i} spec.commands - IPA commands to be executed + * @param {Function} spec.on_success - callback function if command succeeds + * @param {Function} spec.on_error - callback function if command fails + */ +rpc.batch_command = function(spec) { + + spec = spec || {}; + + spec.method = 'batch'; + + var that = IPA.command(spec); + + /** @property {IPA.command[]} commands Commands */ + that.commands = []; + /** @property {IPA.error_list} errors Errors */ + that.errors = IPA.error_list(); + + /** + * Show error if some command fail + * @property {Boolean} show_error=true + */ + that.show_error = typeof spec.show_error == 'undefined' ? + true : spec.show_error; + + /** + * Add command + * @param {IPA.command} command + */ + that.add_command = function(command) { + that.commands.push(command); + that.add_arg(command.to_json()); + }; + + /** + * Add commands + * @param {IPA.command[]} commands + */ + that.add_commands = function(commands) { + for (var i=0; i 0) { + var ajax = this; + var dialog = IPA.error_dialog({ + xhr: xhr, + text_status: text_status, + error_thrown: { + name: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), + message: that.error_message + }, + command: that, + errors: that.errors.errors, + visible_buttons: [ 'ok' ] + }); + + dialog.on_ok = function() { + dialog.close(); + if (that.on_success) that.on_success.call(ajax, data, text_status, xhr); + }; + + dialog.open(); + + } else { + if (that.on_success) that.on_success.call(this, data, text_status, xhr); + } + }; + + /** + * Internal XHR error handler + * @protected + * @param {XMLHttpRequest} xhr + * @param {string} text_status + * @param {{name:string,message:string}} error_thrown + */ + that.batch_command_on_error = function(xhr, text_status, error_thrown) { + // TODO: undefined behavior + if (that.on_error) { + that.on_error.call(this, xhr, text_status, error_thrown); + } + }; + + return that; +}; + +/** + * Call multiple IPA commands over JSON-RPC separately and wait for every + * command's response. + * + * - concurrent command fails if any command fails + * - result is reported when each command finishes + * + * @class rpc.concurrent_command + * + * @param {Object} spec - construct specification + * @param {Array.} spec.commands - IPA commands to execute + * @param {Function} spec.on_success - callback function if each command succeed + * @param {Function} spec.on_error - callback function one command fails + * + */ +rpc.concurrent_command = function(spec) { + + spec = spec || {}; + var that = IPA.object(); + + /** @property {rpc.command[]} commands Commands */ + that.commands = []; + + /** + * Success handler + * @property {Function} + */ + that.on_success = spec.on_success; + + /** + * Error handler + * @property {Function} + */ + that.on_error = spec.on_error; + + /** + * Add commands + * @param {rpc.command[]} commands + */ + that.add_commands = function(commands) { + + if(commands && commands.length) { + for(var i=0; i < commands.length; i++) { + that.commands.push({ + command: commands[i] + }); + } + } + }; + + /** + * Execute the commands one by one. + */ + that.execute = function() { + + var command_info, command, i; + + //prepare for execute + for(i=0; i < that.commands.length; i++) { + command_info = that.commands[i]; + command = command_info.command; + if(!command) { + var dialog = IPA.message_dialog({ + name: 'internal_error', + title: text.get('@i18n:errors.error', 'Error'), + message: text.get('@i18n:errors.internal_error', 'Internal error.') + }); + break; + } + command_info.completed = false; + command_info.success = false; + command_info.on_success = command_info.on_success || command.on_success; + command_info.on_error = command_info.on_error || command.on_error; + command.on_success = function(command_info) { + return function(data, text_status, xhr) { + that.success_handler.call(this, command_info, data, text_status, xhr); + }; + }(command_info); + command.on_error = function(command_info) { + return function(xhr, text_status, error_thrown) { + that.error_handler.call(this, command_info, xhr, text_status, error_thrown); + }; + }(command_info); + } + + //execute + for(i=0; i < that.commands.length; i++) { + command = that.commands[i].command; + command.execute(); + } + }; + + /** + * Internal error handler + * @protected + */ + that.error_handler = function(command_info, xhr, text_status, error_thrown) { + + command_info.completed = true; + command_info.success = false; + command_info.xhr = xhr; + command_info.text_status = text_status; + command_info.error_thrown = error_thrown; + command_info.context = this; + that.command_completed(); + }; + + /** + * Internal success handler + * @protected + */ + that.success_handler = function(command_info, data, text_status, xhr) { + + command_info.completed = true; + command_info.success = true; + command_info.data = data; + command_info.text_status = text_status; + command_info.xhr = xhr; + command_info.context = this; + that.command_completed(); + }; + + /** + * Check if all commands finished. + * If so, report it. + * @protected + */ + that.command_completed = function() { + + var all_completed = true; + var all_success = true; + + for(var i=0; i < that.commands.length; i++) { + var command_info = that.commands[i]; + all_completed = all_completed && command_info.completed; + all_success = all_success && command_info.success; + } + + if(all_completed) { + if(all_success) { + that.on_success_all(); + } else { + that.on_error_all(); + } + } + }; + + /** + * Call each command's success handler and `on_success`. + * @protected + */ + that.on_success_all = function() { + + for(var i=0; i < that.commands.length; i++) { + var command_info = that.commands[i]; + if(command_info.on_success) { + command_info.on_success.call( + command_info.context, + command_info.data, + command_info.text_status, + command_info.xhr); + } + } + + if(that.on_success) { + that.on_success(); + } + }; + + /** + * Call each command's error handler and `on_success`. + * @protected + */ + that.on_error_all = function() { + + if(that.on_error) { + that.on_error(); + + } else { + var dialog = IPA.message_dialog({ + name: 'operation_error', + title: text.get('@i18n:dialogs.batch_error_title', 'Operations Error'), + message: text.get('@i18n:dialogs.batch_error_message', 'Some operations failed.') + }); + + dialog.open(); + } + }; + + that.add_commands(spec.commands); + + return that; +}; + +/** + * Error list + * + * Collection for RPC command errors. + * + * @class IPA.error_list + * @private + */ +rpc.error_list = function() { + var that = IPA.object(); + + /** Clear errors */ + that.clear = function() { + that.errors = []; + }; + + /** Add error */ + that.add = function(command, name, message, status) { + that.errors.push({ + command: command, + name: name, + message: message, + status: status + }); + }; + + /** Add errors */ + that.add_range = function(error_list) { + that.errors = that.errors.concat(error_list.errors); + }; + + /** + * Check if there are no errors + * @return {Boolean} + */ + that.is_empty = function () { + return that.errors.length === 0; + }; + + that.clear(); + return that; +}; + +/** + * Error handler for IPA.command which handles error #4304 as success. + * + * 4304 is raised when part of an operation succeeds and the part that failed + * isn't critical. + * @member IPA + * @param {IPA.entity_adder_dialog} adder_dialog + */ +rpc.create_4304_error_handler = function(adder_dialog) { + + var set_pkey = function(result) { + + var pkey_name = adder_dialog.entity.metadata.primary_key; + var args = adder_dialog.command.args; + var pkey = args[args.length-1]; + result[pkey_name] = pkey; + }; + + return function (xhr, text_status, error_thrown) { + + var ajax = this; + var command = adder_dialog.command; + var data = error_thrown.data; + var dialog = null; + + if (data && data.error && data.error.code === 4304) { + dialog = IPA.message_dialog({ + name: 'error_4304_info', + message: data.error.message, + title: adder_dialog.title, + on_ok: function() { + data.result = { result: {} }; + set_pkey(data.result.result); + command.on_success.call(ajax, data, text_status, xhr); + } + }); + } else { + dialog = IPA.error_dialog({ + xhr: xhr, + text_status: text_status, + error_thrown: error_thrown, + command: command + }); + } + + dialog.open(); + }; +}; + +// backwards compatibility: +IPA.error_list = rpc.error_list; +IPA.create_4304_error_handler = rpc.create_4304_error_handler; +IPA.command = rpc.command; +IPA.batch_command = rpc.batch_command; +IPA.concurrent_command = rpc.concurrent_command; + +lang.mixin(exports, rpc); + +return rpc; +}); \ No newline at end of file -- cgit