summaryrefslogtreecommitdiffstats
path: root/install/ui
diff options
context:
space:
mode:
authorPetr Vobornik <pvoborni@redhat.com>2014-02-13 13:10:18 +0100
committerPetr Vobornik <pvoborni@redhat.com>2014-03-27 14:54:08 +0100
commitd5cf0b273ad511d6cb99fbcb900eff528fe172df (patch)
tree4a9b78d4e6f0dfbad7627d7135cfcbdebcd54910 /install/ui
parente7bfac1e63360993aea2be91082ca69c478f3cf1 (diff)
downloadfreeipa-d5cf0b273ad511d6cb99fbcb900eff528fe172df.tar.gz
freeipa-d5cf0b273ad511d6cb99fbcb900eff528fe172df.tar.xz
freeipa-d5cf0b273ad511d6cb99fbcb900eff528fe172df.zip
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 <amisnyov@redhat.com>
Diffstat (limited to 'install/ui')
-rw-r--r--install/ui/src/freeipa/ipa.js884
-rw-r--r--install/ui/src/freeipa/rpc.js930
2 files changed, 930 insertions, 884 deletions
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
@@ -622,799 +622,6 @@ IPA.password_selfservice = function() {
};
/**
- * 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.<number,string>} error_messages Error messages map */
- that.error_messages = $.ordered_map({
- 911: 'Missing HTTP referer. <br/> 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<that.args.length; i++) {
- string += ' '+that.args[i];
- }
-
- for (var name in that.options) {
- string += ' --'+name+'=\''+that.options[name]+'\'';
- }
-
- return string;
- };
-
- return that;
-};
-
-/**
- * Call multiple IPA commands in a batch over JSON-RPC.
- *
- * @class IPA.batch_command
- * @extends IPA.command
- *
- * @param {Object} spec
- * @param {Array.<IPA.command>} 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<commands.length; i++) {
- that.add_command(commands[i]);
- }
- };
-
- /**
- * @inheritDoc
- */
- that.execute = function() {
- that.errors.clear();
-
- var command = IPA.command({
- name: that.name,
- entity: that.entity,
- method: that.method,
- args: that.args,
- options: that.options,
- retry: that.retry
- });
-
- command.on_success = that.batch_command_on_success;
- command.on_error = that.batch_command_on_error;
-
- command.execute();
- };
-
- /**
- * Internal XHR success handler
- *
- * Parses data and looks for errors. `on_success` or `on_error` is then
- * called.
- * @protected
- * @param {Object} data
- * @param {string} text_status
- * @param {XMLHttpRequest} xhr
- */
- that.batch_command_on_success = function(data, text_status, xhr) {
-
- for (var i=0; i<that.commands.length; i++) {
- var command = that.commands[i];
- var result = data.result.results[i];
-
- var name = '';
- var message = '';
-
- if (!result) {
- name = text.get('@i18n:errors.internal_error', 'Internal Error')+' '+xhr.status;
- message = result ? xhr.statusText : text.get('@i18n:errors.internal_error', 'Internal Error');
-
- that.errors.add(command, name, message, text_status);
-
- if (command.on_error) command.on_error.call(
- this,
- xhr,
- text_status,
- {
- name: name,
- message: message
- }
- );
-
- } else if (result.error) {
- var code = result.error.code || result.error_code;
- name = text.get('@i18n:errors.ipa_error', 'IPA Error')+(code ? ' '+code : '');
- message = result.error.message || result.error;
-
- if (command.retry) that.errors.add(command, name, message, text_status);
-
- if (command.on_error) command.on_error.call(
- this,
- xhr,
- text_status,
- {
- name: name,
- code: code,
- message: message,
- data: result
- }
- );
-
- } else {
- var failed = that.get_failed(command, result, text_status, xhr);
- that.errors.add_range(failed);
-
- if (command.on_success) command.on_success.call(this, result, text_status, xhr);
- }
- }
-
- //check for partial errors and show error dialog
- if (that.show_error && that.errors.errors.length > 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.<IPA.command>} 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
* @param {Object} spec - contruction spec
@@ -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 <pzuna@redhat.com>
+ * Adam Young <ayoung@redhat.com>
+ * Endi Dewata <edewata@redhat.com>
+ * John Dennis <jdennis@redhat.com>
+ * Petr Vobornik <pvoborni@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+*/
+
+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.<number,string>} error_messages Error messages map */
+ that.error_messages = $.ordered_map({
+ 911: 'Missing HTTP referer. <br/> 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<that.args.length; i++) {
+ string += ' '+that.args[i];
+ }
+
+ for (var name in that.options) {
+ string += ' --'+name+'=\''+that.options[name]+'\'';
+ }
+
+ return string;
+ };
+
+ return that;
+};
+
+/**
+ * Call multiple IPA commands in a batch over JSON-RPC.
+ *
+ * @class rpc.batch_command
+ * @extends rpc.command
+ *
+ * @param {Object} spec
+ * @param {Array.<IPA.command>} 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<commands.length; i++) {
+ that.add_command(commands[i]);
+ }
+ };
+
+ /**
+ * @inheritDoc
+ */
+ that.execute = function() {
+ that.errors.clear();
+
+ var command = IPA.command({
+ name: that.name,
+ entity: that.entity,
+ method: that.method,
+ args: that.args,
+ options: that.options,
+ retry: that.retry
+ });
+
+ command.on_success = that.batch_command_on_success;
+ command.on_error = that.batch_command_on_error;
+
+ command.execute();
+ };
+
+ /**
+ * Internal XHR success handler
+ *
+ * Parses data and looks for errors. `on_success` or `on_error` is then
+ * called.
+ * @protected
+ * @param {Object} data
+ * @param {string} text_status
+ * @param {XMLHttpRequest} xhr
+ */
+ that.batch_command_on_success = function(data, text_status, xhr) {
+
+ for (var i=0; i<that.commands.length; i++) {
+ var command = that.commands[i];
+ var result = data.result.results[i];
+
+ var name = '';
+ var message = '';
+
+ if (!result) {
+ name = text.get('@i18n:errors.internal_error', 'Internal Error')+' '+xhr.status;
+ message = result ? xhr.statusText : text.get('@i18n:errors.internal_error', 'Internal Error');
+
+ that.errors.add(command, name, message, text_status);
+
+ if (command.on_error) command.on_error.call(
+ this,
+ xhr,
+ text_status,
+ {
+ name: name,
+ message: message
+ }
+ );
+
+ } else if (result.error) {
+ var code = result.error.code || result.error_code;
+ name = text.get('@i18n:errors.ipa_error', 'IPA Error')+(code ? ' '+code : '');
+ message = result.error.message || result.error;
+
+ if (command.retry) that.errors.add(command, name, message, text_status);
+
+ if (command.on_error) command.on_error.call(
+ this,
+ xhr,
+ text_status,
+ {
+ name: name,
+ code: code,
+ message: message,
+ data: result
+ }
+ );
+
+ } else {
+ var failed = that.get_failed(command, result, text_status, xhr);
+ that.errors.add_range(failed);
+
+ if (command.on_success) command.on_success.call(this, result, text_status, xhr);
+ }
+ }
+
+ //check for partial errors and show error dialog
+ if (that.show_error && that.errors.errors.length > 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.<rpc.command>} 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