First part of API Browser - displaying the metadata in more consumable way.

Second part, how to use it in different languages will be written as wiki pages first.

The browser could be later enhanced with more infos and tooltips.

Patch 886 extends backend to send more metadata.
Patch 887,888,889 are webui fixes and prerequisites
Patch 890 is the API browser
--
Petr Vobornik
From 075b33c102e6f20164375cd5cb4e23d5df3b51a1 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 5 Jun 2015 19:03:46 +0200
Subject: [PATCH] webui: API browser

First part of API browser - displaying metadata in more consumable way.

https://fedorahosted.org/freeipa/ticket/3129
---
 install/ui/doc/categories.json                     |   4 +-
 install/ui/less/widgets.less                       |  14 +
 install/ui/src/freeipa/app.js                      |   1 +
 install/ui/src/freeipa/navigation/menu_spec.js     |  18 +
 install/ui/src/freeipa/plugins/api_browser.js      | 106 +++++
 install/ui/src/freeipa/widgets/APIBrowserWidget.js | 379 +++++++++++++++++
 install/ui/src/freeipa/widgets/browser_widgets.js  | 453 +++++++++++++++++++++
 7 files changed, 974 insertions(+), 1 deletion(-)
 create mode 100644 install/ui/src/freeipa/plugins/api_browser.js
 create mode 100644 install/ui/src/freeipa/widgets/APIBrowserWidget.js
 create mode 100644 install/ui/src/freeipa/widgets/browser_widgets.js

diff --git a/install/ui/doc/categories.json b/install/ui/doc/categories.json
index 83c24c5efa7f8e52188a30452a9b3119d831592b..3a7c2ebc2d6cdee34d48cc72f94ce845bd73d7e4 100644
--- a/install/ui/doc/categories.json
+++ b/install/ui/doc/categories.json
@@ -39,7 +39,8 @@
                 "classes": [
                     "facet.facet",
                     "facets.Facet",
-                    "*_facet"
+                    "*_facet",
+                    "*Facet"
                 ]
             },
             {
@@ -254,6 +255,7 @@
                     "stageuser",
                     "topology",
                     "user",
+                    "plugins.api_browser",
                     "plugins.load",
                     "plugins.login",
                     "plugins.login_process",
diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 066e074a044fc25f88ca8060c6ead2edd9d11cb0..7778f6bf46b3bbebf99fff4a7799fe4b0b090385 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -117,5 +117,19 @@
     padding-left: 10px
 }
 
+.apibrowser {
+    .item-select input[type=text] {
+        width: 100%;
+        padding-left: 5px;
+    }
+    .label {
+        margin-left: 5px; // spacing between param flags
+    }
+    .prop-label {
+        text-align: right;
+        font-weight: 300;
+    }
+}
+
 // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=409254
 tbody:empty { display: none; }
\ No newline at end of file
diff --git a/install/ui/src/freeipa/app.js b/install/ui/src/freeipa/app.js
index 9b290ab0eee216f8b8adb3181a1b3e7ac22fb351..f05e8213c0b17e21515fdfce5ab496516a02692e 100644
--- a/install/ui/src/freeipa/app.js
+++ b/install/ui/src/freeipa/app.js
@@ -24,6 +24,7 @@ define([
     './plugins/sync_otp',
     './plugins/login',
     './plugins/login_process',
+    './plugins/api_browser',
     // entities
     './aci',
     './automember',
diff --git a/install/ui/src/freeipa/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
index c35445f12e36c5dc49033723d8efdcaed7331002..28eb0f817f3574e085368a38bd9bce7d2cd0da8b 100644
--- a/install/ui/src/freeipa/navigation/menu_spec.js
+++ b/install/ui/src/freeipa/navigation/menu_spec.js
@@ -206,6 +206,24 @@ var nav = {};
                         }
                     ]
                 },
+                {
+                    name: 'apibrowser',
+                    label: 'API browser',
+                    children: [
+                        {
+                            name: 'commands',
+                            label: 'Commands',
+                            facet: 'apibrowser',
+                            args: { 'type': 'command' }
+                        },
+                        {
+                            name: 'objects',
+                            label: 'Objects',
+                            facet: 'apibrowser',
+                            args: { 'type': 'object' }
+                        }
+                    ]
+                },
                 { entity: 'config' }
             ]
         }
diff --git a/install/ui/src/freeipa/plugins/api_browser.js b/install/ui/src/freeipa/plugins/api_browser.js
new file mode 100644
index 0000000000000000000000000000000000000000..88dbe59c7358fd0c8c3fe41ff1b7140ce3086f7e
--- /dev/null
+++ b/install/ui/src/freeipa/plugins/api_browser.js
@@ -0,0 +1,106 @@
+//
+// Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+//
+
+define(['dojo/_base/declare',
+        'dojo/_base/lang',
+        'dojo/on',
+        '../facets/Facet',
+        '../phases',
+        '../reg',
+        '../widget',
+        '../widgets/APIBrowserWidget',
+        '../builder'
+       ],
+
+    function(declare, lang, on, Facet, phases, reg, widget,
+        APIBrowserWidget, builder) {
+
+
+var plugins = {}; // dummy namespace object
+
+/**
+ * API browser plugin
+ *
+ * @class
+ * @singleton
+ */
+plugins.api_browser = {};
+
+plugins.api_browser.facet_spec = {
+    name: 'apibrowser',
+    'class': 'apibrowser container-fluid',
+    widgets: [
+        {
+            $type: 'activity',
+            name: 'activity',
+            text: 'Working',
+            visible: false
+        },
+        {
+            $type: 'apibrowser',
+            name: 'apibrowser'
+        }
+    ]
+};
+
+/**
+ * API browser facet
+ * @class
+ */
+plugins.api_browser.APIBrowserFacet = declare([Facet], {
+
+    init: function(spec) {
+        this.inherited(arguments);
+        var browser = this.get_widget('apibrowser');
+
+        on(this, 'show', lang.hitch(this, function(args) {
+
+            var state = this.get_state();
+            var t = state.type;
+            var n = state.name;
+
+            if (t && n) {
+                browser.show_item(t, n);
+                return;
+            } else if (t) {
+                if (t == 'command') {
+                    browser.show_default_command();
+                    return;
+                } else {
+                    browser.show_default_object();
+                    return;
+                }
+            }
+            browser.show_default();
+            return;
+        }));
+
+        // Reflect item change in facet state and therefore URL hash
+        browser.watch('current', lang.hitch(this, function(name, old, value) {
+            var state = {};
+            if (value.type && value.name) {
+                state = { type: value.type, name: value.name };
+            }
+            this.set_state(state);
+        }));
+    }
+});
+
+phases.on('registration', function() {
+
+    var fa = reg.facet;
+    var w = reg.widget;
+
+    w.register('apibrowser', APIBrowserWidget);
+
+    fa.register({
+        type: 'apibrowser',
+        factory: plugins.api_browser.APIBrowserFacet,
+        spec: plugins.api_browser.facet_spec
+    });
+});
+
+return plugins.api_browser;
+
+});
\ No newline at end of file
diff --git a/install/ui/src/freeipa/widgets/APIBrowserWidget.js b/install/ui/src/freeipa/widgets/APIBrowserWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..5dd860f2aa93bb63ad7a65879d952bd0cccd779e
--- /dev/null
+++ b/install/ui/src/freeipa/widgets/APIBrowserWidget.js
@@ -0,0 +1,379 @@
+//
+// Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+//
+
+define([
+    'dojo/_base/declare',
+    'dojo/_base/lang',
+    'dojo/on',
+    'dojo/Evented',
+    'dojo/Stateful',
+    '../jquery',
+    '../ipa',
+    '../metadata',
+    '../reg',
+    '../text',
+    '../util',
+    './ListViewWidget',
+    './browser_widgets'
+], function(declare, lang, on, Evented, Stateful, $, IPA, metadata, reg, text,
+    util, ListViewWidget, browser_widgets) {
+
+var widgets = {};
+
+/**
+ * API browser widget
+ *
+ * Consists of two parts: command browser and details.
+ *
+ * Command browser consist of:
+ * - filter
+ * - list view with commands
+ *
+ * Details could be:
+ * - command
+ * - object
+ * - param
+ *
+ * @class
+ */
+widgets.APIBrowserWidget = declare([Stateful, Evented], {
+
+    // widgets
+    filter_w: null,
+    list_w: null,
+    object_detail_w: null,
+    command_detail_w: null,
+    param_detail_w: null,
+    current_details_w: null, // Current details widget, one of the three above
+
+    // nodes
+    container_node: null,
+    el: null,
+    default_view_el: null,
+    details_view_el: null,
+    current_view: null, // either default_view_el or details_view_el
+    filter_el: null,
+    list_el: null,
+    sidebar_el: null,
+    details_el: null,
+
+    /**
+     * Currently displayed item or view
+     *
+     * Monitor this property to reflect the change of item
+     *
+     * @property {Object}
+     */
+    current: {},
+
+
+    metadata_map: {
+        'object': '@mo:',
+        'command': '@mc:',
+        'param': '@mo-param:'
+    },
+
+    _to_list: function(objects) {
+        var names = [];
+        for (name in objects) {
+            if (objects.hasOwnProperty(name)) {
+                names.push(name);
+            }
+        }
+        names.sort();
+        var new_objects = [];
+        var o;
+        for (var i=0,l=names.length; i<l; i++) {
+            o = objects[names[i]];
+            if (!o.name) o.name = names[i];
+            if (o.only_webui) continue;
+            new_objects.push(o);
+        }
+        return new_objects;
+    },
+
+    _get_commands: function() {
+        var commands = metadata.get('@m:commands');
+        commands = this._to_list(commands);
+        return [{
+            name: "commands",
+            label: "Commands",
+            items: commands
+        }];
+    },
+
+    _get_objects: function() {
+        var objects = metadata.get('@m:objects');
+        objects = this._to_list(objects);
+        return [{
+            name: "commands",
+            label: "Objects",
+            items: objects
+        }];
+    },
+
+    _get_params: function(name) {
+        var object = metadata.get('@mo:'+name);
+        var params = object.takes_params;
+        return [{
+            name: object.name,
+            label:object.label_singular + ' params',
+            items: params
+        }];
+    },
+
+    _get_list: function(type, name, filter) {
+
+        var groups = null;
+        if (type === 'object') {
+            groups = this._get_objects();
+        } else if (type === 'command') {
+            groups = this._get_commands();
+        } else if (type === 'param') {
+            var parts = name.split(':');
+            groups = this._get_params(parts[0]);
+        }
+
+        if (filter) {
+            filter = filter.toLowerCase();
+            var new_groups = [];
+            for (var i=0,l=groups.length; i<l; i++) {
+                var filtered_list = [];
+                var items = groups[i].items;
+                for (var j=0,m=items.length; j<m; j++) {
+                    var item = items[j];
+                    if (item.name.match(filter) ||
+                        (item.label && item.label.toLowerCase().match(filter))) {
+                        filtered_list.push(item);
+                    }
+                }
+                groups[i].items = filtered_list;
+                if (filtered_list.length > 0) {
+                    new_groups.push(groups[i]);
+                }
+            }
+            return new_groups;
+        } else {
+            return groups;
+        }
+    },
+
+    /**
+     * Search metadata for object of given type and name. Display it if found.
+     * Display default view otherwise.
+     *
+     * Supported types and values:
+     * - 'object', value is object name, e.g., 'user'
+     * - 'command', value is command name, e.g., 'user_show'
+     * - 'param', value is tuple 'object_name:param_name', e.g., 'user:cn'
+     *
+     * @param  {string} type Type of the object
+     * @param  {string} name Object identifier
+     */
+    show_item: function (type, name) {
+        var item;
+        if (!this.metadata_map[type]) {
+            IPA.notify("Invalid object type requested: "+type, 'error');
+            this.show_default();
+        } else {
+            item = metadata.get(this.metadata_map[type] + name);
+            if (!item) {
+                IPA.notify("Requested "+ type +" does not exist: " + name, 'error');
+                this.show_default();
+                return;
+            }
+        }
+        this._set_item(type, item, name);
+    },
+
+    /**
+     * Show default view.
+     *
+     * For now a fallback if item is not found. Later could be extended to
+     * contain help info how to use the API.
+     */
+    show_default: function() {
+        // switch view
+        if (this.current_view !== this.default_view_el) {
+            this.el.empty();
+            this.el.append(this.default_view_el);
+            this.current_view = this.default_view_el;
+        }
+        this.set('current', {
+            view: 'default'
+        });
+    },
+
+    /**
+     * Shows default command
+     */
+    show_default_command: function() {
+        this.show_item('command', 'user_show'); // TODO: change
+    },
+
+    /**
+     * Shows default object
+     */
+    show_default_object: function() {
+        this.show_item('object', 'user'); // TODO: change
+    },
+
+    /**
+     * Show item
+     *
+     * @param {string} type Type of item
+     * @param {Object} item The item
+     * @param {string} name Name of the item
+     */
+    _set_item: function(type, item, name) {
+
+        // get widget
+        var widget = null;
+        if (type === 'object') {
+            widget = this.object_detail_w;
+        } else if (type === 'command') {
+            widget = this.command_detail_w;
+        } else if (type === 'param') {
+            widget = this.param_detail_w;
+        } else {
+            IPA.notify("Invalid type", 'error');
+            this.show_default();
+        }
+
+        // switch view
+        if (!this.details_view_el) {
+            this._render_details_view();
+        }
+        if (this.current_view !== this.details_view_el) {
+            this.el.empty();
+            this.el.append(this.details_view_el);
+            this.current_view = this.details_view_el;
+        }
+
+        // switch widget
+        if (!widget.el) widget.render();
+        if (this.current_details_w !== widget) {
+            this.details_el.empty();
+            this.details_el.append(widget.el);
+        }
+
+        // set list
+        var list = this._get_list(type, name, this.current.filter);
+        this.list_w.set('groups', list);
+        this.list_w.select(item);
+
+        // set item
+        widget.set('item', item);
+        this.set('current', {
+            item: item,
+            type: type,
+            name: name,
+            filter: this.current.filter,
+            view: 'details'
+        });
+
+        // update sidebar
+        $(window).trigger('resize');
+
+        $('html, body').animate({
+            scrollTop: 0
+        }, 500);
+    },
+
+    render: function() {
+        this.el = $('<div/>', { 'class': this.css_class });
+        this._render_default_view().appendTo(this.el);
+        if (this.container_node) {
+            this.el.appendTo(this.container_node);
+        }
+        return this.el;
+    },
+
+    _render_details_view: function() {
+        this.details_view_el = $('<div/>', { 'class': 'details-view' });
+        var row = $('<div/>', { 'class': 'row' });
+        this.sidebar_el = $('<div/>', { 'class': 'sidebar-pf sidebar-pf-left col-sm-4 col-md-3 col-sm-pull-8 col-md-pull-9' });
+        this.details_el = $('<div/>', { 'class': 'col-sm-8 col-md-9 col-sm-push-4 col-md-push-3' });
+        this.details_el.appendTo(row);
+        this.sidebar_el.appendTo(row);
+        row.appendTo(this.details_view_el);
+        this._render_select().appendTo(this.sidebar_el);
+        return this.details_view_el;
+    },
+
+    _render_select: function() {
+        var el  = $('<div/>', { 'class': 'item-select' });
+
+        $('<div/>', { 'class': 'nav-category' }).
+        append($('<h2/>', {
+            'class': 'item-select',
+            text: 'Browse'
+        })).
+        appendTo(el);
+
+        this.filter_el = this.filter_w.render();
+        this.list_el = this.list_w.render();
+        this.filter_el.appendTo(el);
+        this.list_el.appendTo(el);
+        return el;
+    },
+
+    _render_default_view: function() {
+        this.default_view_el = $('<div/>', { 'class': 'default-view' });
+        $('<h1/>', { text: "API Browser" }).appendTo(this.default_view_el);
+        var commands = $('<div/>').appendTo(this.default_view_el);
+        $('<p/>').append($('<a/>', {
+            href: "#/p/apibrowser/type=command",
+            text: "Browse Commands"
+        })).appendTo(commands);
+        var objects = $('<div/>').appendTo(this.default_view_el);
+        $('<p/>').append($('<a/>', {
+            href: "#/p/apibrowser/type=object",
+            text: "Browse Objects"
+        })).appendTo(commands);
+        return this.default_view_el;
+    },
+
+    _apply_filter: function(filter) {
+        var current = this.current;
+        current.filter = filter;
+        var list = this._get_list(current.type, current.name, current.filter);
+        this.list_w.set('groups', list);
+        this.list_w.select(current.item);
+    },
+
+    _item_selected: function(item) {
+        var t = this.current.type;
+        var n = item.name;
+        if (t == 'param') {
+            var obj = this.current.name.split(':')[0];
+            n = [obj, n].join(':');
+        }
+        this.show_item(t, n);
+    },
+
+    _init_widgets: function() {
+        this.filter_w = new browser_widgets.FilterWidget();
+        this.filter_w.watch('filter', lang.hitch(this, function(name, old, value) {
+            this._apply_filter(value);
+        }));
+
+        this.list_w = new ListViewWidget();
+        this.object_detail_w = new browser_widgets.ObjectDetailWidget();
+        this.command_detail_w = new browser_widgets.CommandDetailWidget();
+        this.param_detail_w = new browser_widgets.ParamDetailWidget();
+
+        on(this.list_w, 'item-click', lang.hitch(this, function(args) {
+            this._item_selected(args.context);
+        }));
+    },
+
+    constructor: function(spec) {
+        lang.mixin(this, spec);
+        this._init_widgets();
+    }
+});
+
+    return widgets.APIBrowserWidget;
+});
\ No newline at end of file
diff --git a/install/ui/src/freeipa/widgets/browser_widgets.js b/install/ui/src/freeipa/widgets/browser_widgets.js
new file mode 100644
index 0000000000000000000000000000000000000000..64672c04b97587b790803bd22b3514baed4427d5
--- /dev/null
+++ b/install/ui/src/freeipa/widgets/browser_widgets.js
@@ -0,0 +1,453 @@
+//
+// Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+//
+
+//
+// Contains API browser widgets
+//
+
+define([
+    'dojo/_base/declare',
+    'dojo/_base/lang',
+    'dojo/on',
+    'dojo/Evented',
+    'dojo/Stateful',
+    '../jquery',
+    '../ipa',
+    '../metadata',
+    '../navigation',
+    '../reg',
+    '../text',
+    '../util'
+], function(declare, lang, on, Evented, Stateful, $, IPA, metadata, navigation,
+     reg, text, util) {
+
+var widgets = { browser_widgets: {} }; //namespace
+
+var apibrowser_facet = 'apibrowser';
+
+/**
+ * Browser Widget Base
+ *
+ * Candidate for a base class for all widgets
+ *
+ * @class
+ */
+widgets.browser_widgets.Base = declare([Stateful, Evented], {
+
+    // nodes
+    el: null,
+
+    /**
+     * Render widget's HTML
+     * @return {jQuery} base node
+     */
+    render: function() {
+        this.el = $('<div/>', { 'class': this.css_class });
+        this.render_content();
+        return this.el;
+    },
+
+    /**
+     * Should be overridden
+     */
+    render_content: function() {
+    },
+
+    constructor: function(spec) {
+        lang.mixin(this, spec);
+    }
+});
+
+/**
+ * Detail Base
+ *
+ * A base class for showing details of various API objects
+ *
+ * @class
+ * @extends {widgets.browser_widgets.Base}
+ */
+widgets.browser_widgets.DetailBase = declare([widgets.browser_widgets.Base], {
+
+    /**
+     * Item to be displayed
+     * @property {Object}
+     */
+    item: null,
+
+    _itemSetter: function(value) {
+        this.item = value;
+        if (this.el) {
+            this.render_content();
+        }
+    },
+
+    _get_object: function(obj_name) {
+        var obj = metadata.get('@mo:' + obj_name);
+        if (!obj || obj.only_webui) return null;
+        return obj;
+    },
+
+    _get_command_object: function(command_name) {
+        var obj_name = command_name.split('_')[0];
+        var obj = this._get_object(obj_name);
+        return obj;
+    },
+
+    _get_objectparam: function(command_name, param_name) {
+        var obj = this._get_command_object(command_name);
+        if (!obj) return null;
+        var param = metadata.get('@mo-param:' + obj.name + ':' + param_name);
+        return param;
+    },
+
+    render_object_link: function(obj_name, text) {
+        var facet = reg.facet.get(apibrowser_facet);
+        var link = $('<a/>', {
+            href: "#" + navigation.create_hash(facet, {
+                type: 'object',
+                name: obj_name
+            }),
+            text: text || obj_name
+        });
+        return link;
+    },
+
+    render_command_link: function(command_name, text) {
+        var facet = reg.facet.get(apibrowser_facet);
+        var link = $('<a/>', {
+            href: "#" + navigation.create_hash(facet, {
+                type: 'command',
+                name: command_name
+            }),
+            text: text || command_name
+        });
+        return link;
+    },
+
+    render_param_link: function(obj_name, param_name, text) {
+        var name = obj_name + ':' + param_name;
+        var facet = reg.facet.get(apibrowser_facet);
+        var link = $('<a/>', {
+            href: "#" + navigation.create_hash(facet, {
+                type: 'param',
+                name: name
+            }),
+            text: text || param_name
+        });
+        return link;
+    },
+
+
+    render_title: function(type, text) {
+        var title = $('<h1/>', { 'class': 'api-title' });
+        $('<span/>', {
+            'class': 'api-title-type',
+            text: type
+        }).appendTo(title);
+        $('<span/>', {
+            'class': 'api-title-text',
+            text: text
+        }).appendTo(title);
+        return title;
+    },
+
+    render_doc: function(text) {
+        return $('<p/>', { text: text });
+    },
+
+    render_section_header: function(text, link) {
+        return $('<h2/>', {
+            text: text,
+            id: link
+        });
+    },
+
+    render_value_container: function() {
+        return $('<div/>', {
+            'class': 'properties'
+        });
+    },
+
+    render_value: function(label, value_node, container) {
+        if (!text) return $('');
+
+        var row = $('<div/>', {
+            'class': 'row'
+        });
+        $('<div/>', {
+            'class': 'col-sm-4 prop-label',
+            text: label
+        }).appendTo(row);
+        $('<div/>', {
+            'class': 'col-sm-8 prop-value'
+        }).append(value_node).appendTo(row);
+
+        if (container) {
+            container.append(row);
+        }
+        return row;
+    },
+
+    render_text: function(label, text, container) {
+        if (!text) return $('');
+        var node = document.createTextNode(text);
+        return this.render_value(label, node, container);
+    },
+
+    render_array: function(label, value, container) {
+        if (!value || value.length === 0) return $('');
+        var text = value.join(', ');
+        return this.render_text(label, text, container);
+    },
+
+    render_object: function(label, obj, container) {
+        if (obj === undefined || obj === null) return $('');
+        var text = JSON.stringify(obj);
+        return this.render_text(label, text, container);
+    },
+
+    render_command_object_link: function(label, command_name, container) {
+        var obj = this._get_command_object(command_name);
+        if (!obj) return $('');
+        var link = this.render_object_link(obj.name, obj.label_singular);
+        return this.render_value(label, link, container);
+    },
+
+
+    render_flags: function(flags, cnt) {
+        if (!cnt) cnt = $('<div/>');
+        for (var i=0,l=flags.length; i<l; i++) {
+            $('<span/>', {
+                'class': 'label label-default',
+                text: flags[i]
+            }).appendTo(cnt);
+        }
+        return cnt;
+    },
+
+    render_param: function(param, container) {
+        var prop_cnt = this.render_value_container();
+        var header = $('<h3/>', {
+            text: param.name
+        });
+        header.appendTo(prop_cnt);
+        this.render_param_properties(param, prop_cnt, header);
+        if (container) {
+            container.append(prop_cnt);
+        }
+        return prop_cnt;
+    },
+
+    render_param_properties: function(param, container, flags_container) {
+
+        this.render_doc(param.doc).appendTo(container);
+        this.render_flags(param.flags, flags_container);
+        this.render_text("label", param.label, container);
+        this.render_text("type", param.type, container);
+        this.render_text("class", param['class'], container);
+        this.render_text("cli_name", param.cli_name, container);
+        this.render_text("cli_short_name", param.cli_short_name, container);
+        this.render_text("required", param.required, container);
+        this.render_text("multivalue", param.multivalue, container);
+        this.render_text("primary_key", param.primary_key, container);
+        this.render_text("autofill", param.autofill, container);
+        //this.render_text("query", param.query, container);
+        this.render_array("include", param.include, container);
+        this.render_array("exclude", param.exclude, container);
+        this.render_text("hint", param.hint, container);
+        this.render_text("alwaysask", param.alwaysask, container);
+        //this.render_text("sortorder", param.sortorder, container);
+        this.render_text("option_group", param.option_group, container);
+        this.render_text("attribute", param.attribute, container);
+        this.render_text("version", param.version, container);
+        this.render_array("deprecated cli aliases", param.deprecated_cli_aliases, container);
+        this.render_text("noextrawhitespace", param.noextrawhitespace, container);
+    }
+});
+
+var base = widgets.browser_widgets.DetailBase;
+
+/**
+ * Object detail
+ * @class
+ * @extends {widgets.browser_widgets.DetailBase
+ */
+widgets.browser_widgets.ObjectDetailWidget = declare([base], {
+
+    render_content: function() {
+        var link, obj;
+        this.el.empty();
+        if (!this.item) {
+            this.el.append('No object selected');
+            return;
+        }
+        var item = this.item;
+        this.render_title('Object: ', item.name).appendTo(this.el);
+        if (item.doc) this.render_doc(item.doc).appendTo(this.el);
+        if (item.parent_object) {
+            obj = this._get_object(item.parent_object);
+            if (obj) {
+                link = this.render_object_link(item.parent_object, obj.label_singular);
+                this.render_value('parent_object', link, this.el);
+            }
+        }
+        //this.render_text("parent_object", item.parent_object, this.el);
+        this.render_text("label", item.label, this.el);
+        this.render_text("label_singular", item.label_singular, this.el);
+        this.render_text("container_dn", item.container_dn, this.el);
+        this.render_text("object_class", item.object_class, this.el);
+        this.render_text("object_class_config", item.object_class_config, this.el);
+        this.render_text("object_name", item.object_name, this.el);
+        this.render_text("object_name_plural", item.object_name_plural, this.el);
+        this.render_text("uuid_attribute", item.uuid_attribute, this.el);
+        this.render_text("rdn_attribute", item.rdn_attribute, this.el);
+        this.render_text("bindable", item.bindable, this.el);
+        this.render_array("aciattrs", item.aciattrs, this.el);
+        this.render_text("can_have_permissions", item.can_have_permissions, this.el);
+        this.render_array("default_attributes", item.default_attributes, this.el);
+        this.render_array("hidden_attributes", item.hidden_attributes, this.el);
+        this.render_object("attribute_members", item.attribute_members, this.el);
+        this.render_object("relationships", item.relationships, this.el);
+
+        if (item.methods) {
+            this.render_section_header('Methods').appendTo(this.el);
+            var cnt = $('<div/>');
+            for (i=0, l=item.methods.length; i<l; i++) {
+                var method_name = item.methods[i];
+                if (i>0) {
+                    cnt.append(', ');
+                }
+                var command_name = item.name + '_' + method_name;
+                link = this.render_command_link(command_name, method_name);
+                cnt.append(link);
+            }
+            this.render_value('', cnt, this.el);
+        }
+
+        if (item.takes_params) {
+            this.render_section_header('Params').appendTo(this.el);
+            for (var i=0,l=item.takes_params.length; i<l; i++) {
+                var opt = item.takes_params[i];
+                this.render_param(opt).appendTo(this.el);
+            }
+        }
+    }
+});
+
+/**
+ * Command Detail
+ * @class
+ * @extends {widgets.browser_widgets.DetailBase
+ */
+widgets.browser_widgets.CommandDetailWidget = declare([base], {
+
+    render_content: function() {
+        var i = 0, l = 0;
+        this.el.empty();
+        if (!this.item) {
+            this.el.append('No command selected');
+            return;
+        }
+        var item = this.item;
+        var obj = this._get_command_object(item.name);
+        this.render_title('Command: ', item.name).appendTo(this.el);
+        if (item.doc) this.render_doc(item.doc).appendTo(this.el);
+        this.render_command_object_link('object', item.name, this.el);
+        if (item.takes_args && item.takes_args.length > 0) {
+            this.render_section_header('Arguments').appendTo(this.el);
+            for (i=0, l=item.takes_args.length; i<l; i++) {
+                var arg = item.takes_args[i];
+                this.render_param(arg).appendTo(this.el);
+            }
+        }
+        if (item.takes_options && item.takes_options.length > 0) {
+            this.render_section_header('Options').appendTo(this.el);
+            for (i=0, l=item.takes_options.length; i<l; i++) {
+                var opt = item.takes_options[i];
+                this.render_param(opt).appendTo(this.el);
+            }
+        }
+        if (item.output_params && item.output_params.length > 0) {
+            this.render_section_header('Output Params').appendTo(this.el);
+            var out_params_cnt = $('<div/>');
+            for (i=0, l=item.output_params.length; i<l; i++) {
+                var param_name = item.output_params[i];
+                var param = this._get_objectparam(item.name, param_name);
+                if (i>0) {
+                    out_params_cnt.append(', ');
+                }
+                if (!param) {
+                    out_params_cnt.append(param_name);
+                } else {
+                    var link = this.render_param_link(obj.name, param_name);
+                    out_params_cnt.append(link);
+                }
+            }
+            out_params_cnt.appendTo(this.el);
+        }
+    }
+});
+
+/**
+ * Param Detail
+ * @class
+ * @extends {widgets.browser_widgets.DetailBase
+ */
+widgets.browser_widgets.ParamDetailWidget = declare([base], {
+
+    render_content: function() {
+        this.el.empty();
+        if (!this.item) {
+            this.el.append('No param selected');
+            return;
+        }
+        var item = this.item;
+        this.render_title('Param: ', item.name).appendTo(this.el);
+        var flags = $('<div/>').appendTo(this.el);
+        this.render_param_properties(item, this.el, flags);
+    }
+});
+
+/**
+ * Filter input
+ *
+ * @class
+ * @extends {widgets.browser_widgets.DetailBase
+ */
+widgets.browser_widgets.FilterWidget = declare([widgets.browser_widgets.Base], {
+
+    /**
+     * Filter text
+     * @property {String}
+     */
+    filter: '',
+
+    _filter_el: null,
+
+    _filterSetter: function(value) {
+        this.filter = value;
+        if (this.el) {
+            this._filter_el.val(value);
+        }
+    },
+
+    render_content: function() {
+        this.el.empty();
+        this._filter_el = $('<input/>', {
+            type: 'text',
+            name: 'filter',
+            placeholder: 'type to filter...',
+            title: 'accepts case insensitive regular expression'
+        });
+        this._filter_el.bind('input', lang.hitch(this, function() {
+            var filter = this._filter_el.val();
+            this.set('filter', filter);
+        }));
+        this._filter_el.appendTo(this.el);
+    }
+});
+
+
+    return widgets.browser_widgets;
+});
\ No newline at end of file
-- 
2.4.3

From c1cd89c6a794eaa1807e4b4bba808a54e50b4da2 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 26 Jun 2015 10:34:37 +0200
Subject: [PATCH] webui: menu and navigation fixes

fixes:

1. When navigation is initiated from clicking and a link with hash, update
of facet state causes that subsequent click on a link with hash will be
ignored. Caused by a code which prevents infinite loop because of facet
state update. Now hash update is done only if it was really changed.

2. registered correct handler for standalone pages

3. fix selection of menu item where the items differ only in args. Chooses
the item with the most similar state to current facet.

https://fedorahosted.org/freeipa/ticket/3129
---
 install/ui/src/freeipa/Application_controller.js | 31 ++++++++++++++++++++++--
 install/ui/src/freeipa/navigation/Router.js      |  2 +-
 install/ui/src/freeipa/navigation/routing.js     |  4 +--
 3 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/install/ui/src/freeipa/Application_controller.js b/install/ui/src/freeipa/Application_controller.js
index ca08770fe347213e6525c74b80da1eeca67fe009..69b9cd1c3c5873e10729f808ca3ec27688a1ecf2 100644
--- a/install/ui/src/freeipa/Application_controller.js
+++ b/install/ui/src/freeipa/Application_controller.js
@@ -379,9 +379,36 @@ define([
                 items = this.menu.query({ parent: null });
             }
 
-            // select first
             if (items.total) {
-                return items[0];
+                if (items.total === 1) return items[0];
+
+                // select the menu item with the most similar state as the facet
+                var best = items[0];
+                var best_score = 0;
+                var item, i, j, l, score;
+                var state = facet.state;
+                for (i=0, l=items.total; i<l; i++) {
+                    item = items[i];
+                    score = 0;
+                    if (item.pkeys && facet.get_pkeys) {
+                        var pkeys = facet.get_pkeys();
+                        for (j=0, j=item.pkeys.length; j<l; j++) {
+                            if (pkeys.indexOf(item.pkeys[j]) > -1) score++;
+                        }
+                    }
+                    if (item.args) {
+                        for (var name in item.args) {
+                            if (!item.args.hasOwnProperty(name)) continue;
+                            if (state[name] == item.args[name]) score++;
+                        }
+                    }
+                    if (score > best_score) {
+                        best_score = score;
+                        best = item;
+                    }
+                }
+
+                return best;
             }
         },
 
diff --git a/install/ui/src/freeipa/navigation/Router.js b/install/ui/src/freeipa/navigation/Router.js
index a65c60fd3644c8e89bfebd7174fd8724d42d1ab3..5523993f4ef13ac02f3ccb1f801a3200412711f0 100644
--- a/install/ui/src/freeipa/navigation/Router.js
+++ b/install/ui/src/freeipa/navigation/Router.js
@@ -66,7 +66,6 @@ define(['dojo/_base/declare',
          */
         ignore_next: false,
 
-
         /**
          * Register a route-handler pair to a dojo.router
          * Handler will be run in context of this object
@@ -111,6 +110,7 @@ define(['dojo/_base/declare',
          * @param {boolean} Whether to suppress following hash change handler
          */
         update_hash: function(hash, ignore_change) {
+            if (window.location.hash === "#" + hash) return;
             this.ignore_next = !!ignore_change;
             router.go(hash);
         },
diff --git a/install/ui/src/freeipa/navigation/routing.js b/install/ui/src/freeipa/navigation/routing.js
index 6e18b0228fc8bf2459499ef21dbdcaa0f0a22bd8..89a323dd5fd4f08ed692093f2b84c152c2a55a45 100644
--- a/install/ui/src/freeipa/navigation/routing.js
+++ b/install/ui/src/freeipa/navigation/routing.js
@@ -166,7 +166,7 @@ var routing = {
      */
     navigate_to_facet: function(facet, options) {
         var hash = this.create_hash(facet, options);
-        return this.router.navigate_to_hash(hash);
+        return this.router.navigate_to_hash(hash, facet);
     },
 
     update_hash: function(facet, options) {
@@ -494,7 +494,7 @@ routing.init = function(router) {
     var entity_n = new routing.EntityNavigator();
     this.add_hash_creator(generic_hc);
     this.add_hash_creator(entity_hc);
-    this.add_route(this.routes, generic_rh);
+    this.add_route(this.page_routes, generic_rh);
     this.add_route(this.entity_routes, entity_rh);
     this.add_navigator(generic_n);
     this.add_navigator(entity_n);
-- 
2.4.3

From 357aefd79d6a5848b180d4459d30bebde03ca67b Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 26 Jun 2015 10:33:58 +0200
Subject: [PATCH] webui: fix webui specific metadata

Mark all Web UI specific metadata so they could be filtered out
in the API Browser.

Fix cert name.

https://fedorahosted.org/freeipa/ticket/3129
---
 install/ui/src/freeipa/certificate.js | 3 ++-
 install/ui/src/freeipa/topology.js    | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index ccaa25807175be9cb917928d5795d76a456b6fc5..182ec7e66238375eb3140a449bd79c46d7937841 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -970,7 +970,7 @@ exp.create_cert_metadata = function() {
         'show',
         'status'
     ];
-    entity.name = "certificate";
+    entity.name = "cert";
     entity.object_name = "certificate";
     entity.object_name_plural = "certificates";
     entity.parent_object = "";
@@ -978,6 +978,7 @@ exp.create_cert_metadata = function() {
     entity.rdn_attribute = "";
     entity.relationships = {};
     entity.takes_params = lang.clone(entity.takes_options);
+    entity.only_webui = true;
 
     get_param(entity.takes_params, 'subject').flags = ['no_update'];
     var reason = get_param(entity.takes_params, 'revocation_reason');
diff --git a/install/ui/src/freeipa/topology.js b/install/ui/src/freeipa/topology.js
index 9ca31741f18e47e567c3e0a4248f5beaad77ccd1..4d585947bf0574ac74bbd94e0d3069701cba85d0 100644
--- a/install/ui/src/freeipa/topology.js
+++ b/install/ui/src/freeipa/topology.js
@@ -261,6 +261,7 @@ topology.domainlevel_metadata = function(spec, context) {
         name: 'domainlevel',
         label: text.get('@i18n:objects.domainlevel.label'),
         label_singular: text.get('@i18n:objects.domainlevel.label_singular'),
+        only_webui: true,
         takes_params: [
             {
                 'class': "Int",
-- 
2.4.3

From 607a4420b18fec10a4d0200678d044a46a1aa232 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 5 Jun 2015 15:55:11 +0200
Subject: [PATCH] webui: ListViewWidget

A widget for rendering a list of groups of items. Intended to be
used in sidebar. Plan is to serve also as a base for FacetGroupsWidget.

https://fedorahosted.org/freeipa/ticket/3129
---
 install/ui/src/freeipa/widgets/ListViewWidget.js | 233 +++++++++++++++++++++++
 1 file changed, 233 insertions(+)
 create mode 100644 install/ui/src/freeipa/widgets/ListViewWidget.js

diff --git a/install/ui/src/freeipa/widgets/ListViewWidget.js b/install/ui/src/freeipa/widgets/ListViewWidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..d378e2b1ef293882bfb10d90688d73a8a7a7bffa
--- /dev/null
+++ b/install/ui/src/freeipa/widgets/ListViewWidget.js
@@ -0,0 +1,233 @@
+//
+// Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+//
+
+define([
+    'dojo/_base/declare',
+    'dojo/_base/lang',
+    'dojo/on',
+    'dojo/Evented',
+    'dojo/Stateful',
+    '../jquery',
+    '../ipa',
+    '../reg',
+    '../text',
+    '../util'
+], function(declare, lang, on, Evented, Stateful, $, IPA, reg, text, util) {
+
+var widgets = {};
+
+/**
+ * Widget for rendering a list of groups of items
+ * @class
+ */
+widgets.ListViewWidget = declare([Stateful, Evented], {
+
+    /**
+     * List of groups
+     * @type {Object[]}
+     */
+    groups: null,
+
+    /**
+     * Should be visible
+     * @type {Boolean}
+     */
+    visible: true,
+
+    /**
+     * Selected item
+     * @property {Object}
+     */
+    selected_item: null,
+
+    // behavior
+    select_on_click: true,
+
+    /**
+     * Raised when menu item is clicked
+     * @event item-click
+     */
+
+    // nodes
+    el: null,
+    group_els: null,
+    item_els: null,
+
+    // html and styling
+    css_class: '',
+    group_el_type: '<div/>',
+    group_class: '',
+    group_label_el_type: '<div/>',
+    group_label_class: 'nav-category',
+    group_label_title_el_type: '<h2/>',
+    group_label_title_class: '',
+    item_cont_el_type: '<div/>',
+    item_cont_class: '',
+    item_list_el_type: '<ul/>',
+    item_list_class: 'nav nav-pills nav-stacked',
+    item_el_type: '<li/>',
+    item_class: 't',
+    item_link_class: 'item-link',
+    selected_class: 'active',
+
+    _groupsSetter: function(value) {
+        this.groups = value;
+        this.render_groups();
+    },
+
+    _get_group_items: function(group) {
+        return group.items;
+    },
+
+    render: function() {
+
+        this.el = $('<div/>', { 'class': this.css_class });
+        this.render_groups();
+        return this.el;
+    },
+
+    render_groups: function() {
+        if (!this.el) return;
+
+        this.group_els = {};
+        this.item_els = {};
+
+        this.el.empty();
+        if (!this.groups) return;
+
+        for (var i=0; i<this.groups.length; i++) {
+            var group = this.groups[i];
+            var items = this._get_group_items(group);
+            if (items.length) {
+                var group_el = this.render_group(group);
+                this.el.append(group_el);
+            }
+        }
+    },
+
+    render_group: function(group) {
+
+        var gr = this.group_els[group.name] = { item_els: {}};
+
+        gr.group_el = $(this.group_el_type, {
+            'class': this.group_class,
+            name: group.name
+        });
+
+        gr.label_el = $(this.group_label_el_type, {
+            'class': this.group_label_class
+        }).appendTo(gr.group_el);
+
+        gr.label_title_el = $(this.group_label_title_el_type, {
+            'class': this.group_label_title_class,
+            text: ' '
+        }).appendTo(gr.label_el);
+
+
+        var item_cont = $(this.item_cont_el_type, { 'class': this.item_cont_class });
+        var item_list = $(this.item_list_el_type, { 'class': this.item_list_class });
+        item_list.appendTo(item_cont);
+        var items = this._get_group_items(group);
+        for (var i=0,l=items.length; i<l; i++) {
+            var item = items[i];
+            var item_el = this.item_els[item.name] = this.render_item(item);
+            item_list.append(item_el);
+        }
+        gr.group_el.append(item_cont);
+
+        return gr.group_el;
+    },
+
+    render_item: function(item) {
+        var self = this;
+        var el = $(this.item_el_type, {
+            name: item.name,
+            'class': this.item_class,
+            click: function() {
+                if (item.disabled || el.hasClass('entity-facet-disabled')) {
+                    return false;
+                }
+                self.on_click(item);
+                return false;
+            }
+        });
+
+        $('<a/>', {
+            text: item.label || item.name || '',
+            'class': 'item-link',
+            href: this.create_item_link(item),
+            name: item.name,
+            title: item.title
+        }).appendTo(el);
+
+        return el;
+    },
+
+    create_item_link: function(item) {
+        return '#';
+    },
+
+    on_click: function(item) {
+        this.emit('item-click', { source: this, context: item });
+        if (this.select_on_click) {
+            this.select(item);
+        }
+    },
+
+    update_group: function(group) {
+        if (!this.group_els[group.name]) return;
+        var label_el = this.group_els[group.name].label_title_el;
+        label_el.text(group.label);
+        if (group.title) label_el.attr('title', group.title);
+    },
+
+    update_item: function(item) {
+        var item_el = this.item_els[item.name];
+        var label_el = $('a', item_el);
+        label_el.text(item.label);
+        if (item.title) label_el.attr('title', item.title);
+    },
+
+    select: function(item) {
+        if (!this.el) return;
+        var cls = this.selected_class;
+        var item_el = this.item_els[item.name];
+
+        this.el.find(this.item_class).removeClass(cls);
+        this.el.find(this.item_link_class).removeClass(cls);
+
+        if (!item_el) return; // return if can't select
+
+        item_el.addClass(cls);
+        item_el.find(this.item_link_class).addClass(cls);
+        this.set('selected_item', item);
+        this.emit('select', { source: this, context: item });
+    },
+
+    select_first: function() {
+        if (!this.el) return;
+        this.el.find(this.item_link_class).removeClass(this.selected_class);
+        this.el.find(this.item_class).removeClass(this.selected_class);
+        var first = this.el.find(this.item_link_class + ':first');
+        first.addClass(this.selected_class);
+        first.parent().addClass(this.selected_class);
+    },
+
+    set_visible: function(visible) {
+        this.set('visible', visible);
+        this._apply_visible();
+    },
+
+    _apply_visible: function() {
+        if (!this.el) return;
+        this.el.css('display', this.visible ? '' : 'none');
+    },
+
+    constructor: function(spec) {
+        lang.mixin(this, spec);
+    }
+});
+
+    return widgets.ListViewWidget;
+});
\ No newline at end of file
-- 
2.4.3

From bc63362cd64b4a91bf641b9359bdbf1bcc63aade Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 5 Jun 2015 15:11:54 +0200
Subject: [PATCH] include more information in metadata

added to commands: doc, args, output_params, cli_name, NO_CLI

added to options: alwaysask, autofill, cli_name, cli_short_name

https://fedorahosted.org/freeipa/ticket/3129
---
 ipalib/frontend.py         | 32 +++++++++++++++++++++++---------
 ipalib/parameters.py       |  3 +--
 ipalib/plugins/baseldap.py | 33 ---------------------------------
 3 files changed, 24 insertions(+), 44 deletions(-)

diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index 19190c37878fc50f70113e10b4af0bdd7183f2a1..fd9b3afef250afd1baa9db0633bae712de87e5fe 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -35,6 +35,7 @@ from errors import (ZeroArgumentError, MaxArgumentError, OverlapError,
     VersionError, OptionError, InvocationError,
     ValidationError, ConversionError)
 from ipalib import messages
+from ipalib.util import json_serialize
 from textwrap import wrap
 
 
@@ -1050,7 +1051,7 @@ class Command(HasParam):
 
     # list of attributes we want exported to JSON
     json_friendly_attributes = (
-        'name', 'takes_args',
+        'name', 'doc', 'NO_CLI'
     )
 
     # list of options we want only to mention their presence and not to write
@@ -1059,22 +1060,21 @@ class Command(HasParam):
         'all', 'raw', 'attrs', 'addattr', 'delattr', 'setattr', 'version',
     )
 
-    def get_json_options(self):
+    def get_json_output_params(self):
         """
-        Get only options we want exported to JSON
+        Get only names of output params
         """
-        for option in self.get_options():
-            if option.name not in self.json_only_presence_options:
-                yield option
-            else:
-                yield { 'name': option.name }
+        for param in self.get_output_params():
+            yield param.name
 
     def __json__(self):
         json_dict = dict(
             (a, getattr(self, a)) for a in self.json_friendly_attributes
         )
 
-        json_dict['takes_options'] = list(self.get_json_options())
+        json_dict['takes_args'] = list(self.get_args())
+        json_dict['takes_options'] = list(self.get_options())
+        json_dict['output_params'] = list(self.get_json_output_params())
 
         return json_dict
 
@@ -1211,6 +1211,20 @@ class Object(HasParam):
                 return 1
             return 2
 
+    json_friendly_attributes = (
+        'name', 'takes_params',
+    )
+
+    def __json__(self):
+        json_dict = dict(
+            (a, json_serialize(getattr(self, a)))
+            for a in self.json_friendly_attributes
+        )
+        if self.primary_key:
+            json_dict['primary_key'] = self.primary_key.name
+        json_dict['methods'] = [m for m in self.methods]
+        return json_dict
+
 
 class Attribute(Plugin):
     """
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index 7fa55fd6a6854ffa97da211ca5ef04b7ad974dc4..6d9d03f695c50888734c03861012513361993f1f 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -918,8 +918,7 @@ class Param(ReadOnly):
         return value
 
     json_exclude_attrs = (
-        'alwaysask', 'autofill', 'cli_name', 'cli_short_name', 'csv',
-        'sortorder', 'falsehoods', 'truths', 'version',
+        'csv', 'sortorder', 'falsehoods', 'truths',
     )
 
     def __json__(self):
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 2eab69f3decb3359e82d30a0a3a595e81a6d9bc3..a2dbbbfc28a8f281ae88d4cdc573eccb5d7307c0 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -1299,17 +1299,6 @@ class LDAPCreate(BaseLDAPCommand, crud.Create):
     def interactive_prompt_callback(self, kw):
         return
 
-    # list of attributes we want exported to JSON
-    json_friendly_attributes = (
-        'takes_args',
-    )
-
-    def __json__(self):
-        json_dict = dict(
-            (a, getattr(self, a)) for a in self.json_friendly_attributes
-        )
-        json_dict['takes_options'] = list(self.get_json_options())
-        return json_dict
 
 class LDAPQuery(BaseLDAPCommand, crud.PKQuery):
     """
@@ -1321,17 +1310,6 @@ class LDAPQuery(BaseLDAPCommand, crud.PKQuery):
         for arg in super(LDAPQuery, self).get_args():
             yield arg
 
-    # list of attributes we want exported to JSON
-    json_friendly_attributes = (
-        'takes_args',
-    )
-
-    def __json__(self):
-        json_dict = dict(
-            (a, getattr(self, a)) for a in self.json_friendly_attributes
-        )
-        json_dict['takes_options'] = list(self.get_json_options())
-        return json_dict
 
 class LDAPMultiQuery(LDAPQuery):
     """
@@ -2131,17 +2109,6 @@ class LDAPSearch(BaseLDAPCommand, crud.Search):
     def interactive_prompt_callback(self, kw):
         return
 
-    # list of attributes we want exported to JSON
-    json_friendly_attributes = (
-        'takes_args',
-    )
-
-    def __json__(self):
-        json_dict = dict(
-            (a, getattr(self, a)) for a in self.json_friendly_attributes
-        )
-        json_dict['takes_options'] = list(self.get_json_options())
-        return json_dict
 
 class LDAPModReverseMember(LDAPQuery):
     """
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to