Hello,

attached patches implements Web UI part of ID Views. Backend is currently on review as well - thread "[PATCHES 247-259] ID views - management part".

https://fedorahosted.org/freeipa/ticket/4535

I expect that backed can change and that the UI might influence it as well. Therefore no UI integration tests nor static data files are included in this patchset. They will follow when backend is stable.

First 5 patches extends UI framework to support the main patch (#754).


== [PATCH] 749 webui: improve breadcrumb navigation ==

Fixes issue when:
- user navigates to a nested facet
- refreshes browser
- uses breadcrumb navigation to go to parent entity page which requires a pkey. E.g. from automount keys to maps.

The old code relies on the facet, that user visited the parent facet before and therefore the facet has pkey stored. It fails after the browser reload.

Allows to specify a containing_facet. It allows breadcrumb navigation to return to a different facet than the 'default'.

== [PATCH] 750 webui: treat value as pkey in link widget ==

Current default mechanism of a link widget assumes that pkeys of a current facet are pkeys for the link. It works for the only usage - in password policy. It's rather inflexible since it can't be used if the keys are in other attribute. This behavior is also bad in nested entities - creates a link to itself which is pointless.

This patch changes the default behavior to assume that the supplied value are the pkeys and that the last pkey is the value to display.

It also keeps the old method of overriding `other_pkeys` method so if the last and only pkey is the actual value to display then the method can tranform it into the pkeys which keeps compatibility with descendant widgets (`host_dnsrecord_entity_link_widget`, `dnsrecord_host_link_widget`).

== [PATCH] 751 webui: do not show internal facet name to user ==

== [PATCH] 752 webui: allow to skip link widget link validation ==

== [PATCH] 753 webui: add simple link column suppor ==

Usual link columns are link with primary key of current entity.

This patch allows to create a link to arbitrary non-nested entity.

== [PATCH] 754 webui: new ID views section ==
--
Petr Vobornik
From 5568329ac43966a04c46661a47a0efc197aa2c03 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Wed, 3 Sep 2014 14:45:05 +0200
Subject: [PATCH] webui: new ID views section

https://fedorahosted.org/freeipa/ticket/4535
---
 install/ui/doc/categories.json                     |   1 +
 install/ui/src/freeipa/_base/Singleton_registry.js |  17 +
 install/ui/src/freeipa/app.js                      |   3 +-
 install/ui/src/freeipa/association.js              |  17 +-
 install/ui/src/freeipa/dialog.js                   |   4 +-
 install/ui/src/freeipa/idviews.js                  | 673 +++++++++++++++++++++
 install/ui/src/freeipa/navigation/menu_spec.js     |   1 +
 install/ui/test/data/ipa_init.json                 |  24 +
 ipalib/plugins/internal.py                         |  24 +
 9 files changed, 759 insertions(+), 5 deletions(-)
 create mode 100644 install/ui/src/freeipa/idviews.js

diff --git a/install/ui/doc/categories.json b/install/ui/doc/categories.json
index 153c366c43a9e51b61e941acbe54da62a6c7e35a..e9507795b9557880cfb4ce34c0808b6bd2d2ab2c 100644
--- a/install/ui/doc/categories.json
+++ b/install/ui/doc/categories.json
@@ -241,6 +241,7 @@
                 "name": "Plugins",
                 "classes": [
                     "aci",
+                    "idviews",
                     "otptoken",
                     "radiusproxy",
                     "user",
diff --git a/install/ui/src/freeipa/_base/Singleton_registry.js b/install/ui/src/freeipa/_base/Singleton_registry.js
index e0c55ca14af2b2c738ff51112630966e27977d73..6aa10545630da9b0dc95c165f19c2b12ad63832b 100644
--- a/install/ui/src/freeipa/_base/Singleton_registry.js
+++ b/install/ui/src/freeipa/_base/Singleton_registry.js
@@ -116,6 +116,23 @@ define(['dojo/_base/declare',
             this.builder.registry.register(type, func, default_spec);
         },
 
+        /**
+         * Makes a copy of construct specification of original type. Extends
+         * it with values in supplied construct specification.
+         *
+         * @param {string} org_type Original type
+         * @param {string} new_type New type
+         * @param {Object} construct_spec Construction specification
+         */
+        copy: function(org_type, new_type, construct_spec) {
+            if (!lang.exists('builder.registry', this)) {
+                throw {
+                    error: 'Object Initialized Exception: builder not initalized',
+                    context: this
+                };
+            }
+            this.builder.registry.copy(org_type, new_type, construct_spec);
+        },
 
         constructor: function(spec) {
 
diff --git a/install/ui/src/freeipa/app.js b/install/ui/src/freeipa/app.js
index 2297b866dde99fe5964f8e0c3516ed458d5ba0dc..46752fa09e47be9e14e5fa37ce1bd1cbd0b0afdf 100644
--- a/install/ui/src/freeipa/app.js
+++ b/install/ui/src/freeipa/app.js
@@ -35,6 +35,7 @@ define([
     './hostgroup',
     './host',
     './idrange',
+    './idviews',
     './netgroup',
     './otptoken',
     './policy',
@@ -50,4 +51,4 @@ define([
     'dojo/domReady!'
 ],function(app_container) {
     return app_container;
-});
\ No newline at end of file
+});
diff --git a/install/ui/src/freeipa/association.js b/install/ui/src/freeipa/association.js
index 8fe612e913c3fc8bb8c2fa86423eb4b46f224ddc..89edeeb1a97359009b38b4eeae5b5a4fc3144015 100644
--- a/install/ui/src/freeipa/association.js
+++ b/install/ui/src/freeipa/association.js
@@ -133,6 +133,7 @@ IPA.bulk_associator = function(spec) {
     spec = spec || {};
 
     var that = IPA.associator(spec);
+    that.options = spec.options || { 'all': true };
 
     that.execute = function() {
 
@@ -145,7 +146,7 @@ IPA.bulk_associator = function(spec) {
             entity: that.entity.name,
             method: that.method,
             args: [that.pkey],
-            options: { 'all': true },
+            options: that.options,
             on_success: that.on_success,
             on_error: that.on_error
         });
@@ -1407,6 +1408,15 @@ exp.attribute_facet = IPA.attribute_facet = function(spec, no_init) {
 
     that.refresh = function() {
 
+        var command = that.get_refresh_command();
+        command.execute();
+    };
+
+    /**
+     * Create refresh command
+     */
+    that.get_refresh_command = function() {
+
         var pkey = that.get_pkeys();
 
         var command = rpc.command({
@@ -1431,8 +1441,7 @@ exp.attribute_facet = IPA.attribute_facet = function(spec, no_init) {
             that.redirect_error(error_thrown);
             that.report_error(error_thrown);
         };
-
-        command.execute();
+        return command;
     };
 
     that.clear = function() {
@@ -1525,6 +1534,8 @@ exp.attribute_facet = IPA.attribute_facet = function(spec, no_init) {
 
     if (!no_init) that.init_attribute_facet();
 
+    that.attribute_get_refresh_command = that.get_refresh_command;
+
     return that;
 };
 
diff --git a/install/ui/src/freeipa/dialog.js b/install/ui/src/freeipa/dialog.js
index f430e5604e0d0b58e48db3be026208322eb32d56..def29cfca73f6a2874514ad7cd649190f53b00ab 100644
--- a/install/ui/src/freeipa/dialog.js
+++ b/install/ui/src/freeipa/dialog.js
@@ -796,6 +796,8 @@ IPA.adder_dialog = function(spec) {
     /** @property {number} height=300 Height */
     that.height = spec.height || 360;
 
+    that.add_button_label = spec.add_button_label || '@i18n:buttons.add';
+
     if (!that.entity) {
         var except = {
             expected: false,
@@ -1045,7 +1047,7 @@ IPA.adder_dialog = function(spec) {
 
         var add_button = that.create_button({
             name: 'add',
-            label: '@i18n:buttons.add',
+            label: that.add_button_label,
             click: function() {
                 if (!add_button.is_enabled()) return;
                 that.execute();
diff --git a/install/ui/src/freeipa/idviews.js b/install/ui/src/freeipa/idviews.js
new file mode 100644
index 0000000000000000000000000000000000000000..3312b29f9f9f736e316f7cbc46bcb9155100745d
--- /dev/null
+++ b/install/ui/src/freeipa/idviews.js
@@ -0,0 +1,673 @@
+/*
+ *  Authors:
+ *    Petr Vobornik <pvobo...@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([
+        './ipa',
+        './jquery',
+        './menu',
+        './phases',
+        './reg',
+        './rpc',
+        './text',
+        './details',
+        './facet',
+        './search',
+        './entity'],
+            function(IPA, $, menu, phases, reg, rpc, text, mod_details, mod_facet) {
+/**
+ * ID Views module
+ * @class
+ * @singleton
+ */
+var idviews = IPA.idviews = {};
+
+var make_spec = function() {
+return {
+    name: 'idview',
+    enable_test: function() {
+        return true;
+    },
+    facet_groups: ['appliedto', 'overrides', 'settings'],
+    facets: [
+        {
+            $type: 'search',
+            columns: [
+                'cn',
+                'description'
+            ],
+            actions: [
+                'idview_unapply_host',
+                'idview_unapply_hostgroups'
+            ],
+            control_buttons: [
+                {
+                    name: 'idview_unapply_host',
+                    label: '@i18n:objects.idview.unapply_hosts_all',
+                    icon: 'fa-trash-o'
+                },
+                {
+                    name: 'idview_unapply_hostgroups',
+                    label: '@i18n:objects.idview.unapply_hostgroups',
+                    icon: 'fa-trash-o'
+                }
+            ]
+        },
+        {
+            $type: 'details',
+            actions: [
+                'delete'
+            ],
+            header_actions: ['delete'],
+            state: {
+            },
+            sections: [
+                {
+                    name: 'details',
+                    fields: [
+                        'cn',
+                        {
+                            $type: 'textarea',
+                            name: 'description'
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            $type: 'nested_search',
+            facet_group: 'overrides',
+            nested_entity: 'idoverrideuser',
+            search_all_entries: true,
+            label: '@mo:idoverrideuser.label',
+            tab_label: '@mo:idoverrideuser.label',
+            name: 'idoverrideuser',
+            columns: [
+                {
+                    name: 'ipaanchoruuid',
+                    label: '@i18n:objects.idoverrideuser.anchor_label'
+                },
+                'uid',
+                'uidnumber',
+                'homedirectory',
+                'description'
+            ]
+        },
+        {
+            $type: 'nested_search',
+            facet_group: 'overrides',
+            nested_entity: 'idoverridegroup',
+            search_all_entries: true,
+            label: '@mo:idoverridegroup.label',
+            tab_label: '@mo:idoverridegroup.label',
+            name: 'idoverridegroup',
+            columns: [
+                {
+                    name: 'ipaanchoruuid',
+                    label: '@i18n:objects.idoverridegroup.anchor_label'
+                },
+                'cn',
+                'gidnumber',
+                'description'
+            ]
+        },
+        {
+            $type: 'idview_appliedtohosts',
+            name: 'appliedtohosts',
+            attribute: 'appliedtohosts',
+            tab_label: '@i18n:objects.idview.appliedtohosts',
+            facet_group: 'appliedto',
+            actions: [
+                'idview_apply',
+                'idview_apply_hostgroups',
+                'idview_unapply',
+                'idview_unapply_hostgroups'
+            ],
+            control_buttons: [
+                {
+                    name: 'idview_unapply',
+                    label: '@i18n:objects.idview.unapply_hosts',
+                    icon: 'fa-trash-o'
+                },
+                {
+                    name: 'idview_unapply_hostgroups',
+                    label: '@i18n:objects.idview.unapply_hostgroups',
+                    icon: 'fa-trash-o'
+                },
+                {
+                    name: 'idview_apply',
+                    label: '@i18n:objects.idview.apply_hosts',
+                    icon: 'fa-plus'
+                },
+                {
+                    name: 'idview_apply_hostgroups',
+                    label: '@i18n:objects.idview.apply_hostgroups',
+                    icon: 'fa-plus'
+                }
+            ],
+            columns: [
+                {
+                    name: 'appliedtohosts',
+                    label: '@mo:host.label_singular',
+                    link: true,
+                    target_entity: 'host',
+                    target_facet: 'details'
+                }
+            ]
+        }
+    ],
+
+    adder_dialog: {
+        fields: [
+            'cn',
+            {
+                $type: 'textarea',
+                name: 'description'
+            }
+        ]
+    }
+};};
+
+var make_idoverrideuser_spec = function() {
+return {
+    name: 'idoverrideuser',
+    enable_test: function() {
+        return true;
+    },
+    policies:[
+        {
+            $factory: IPA.facet_update_policy,
+            source_facet: 'details',
+            dest_entity: 'idview',
+            dest_facet: 'idoverrideuser'
+        }
+    ],
+    containing_entity: 'idview',
+    facets: [
+        {
+            $type: 'details',
+            disable_breadcrumb: false,
+            containing_facet: 'idoverrideuser',
+            actions: [
+                'delete'
+            ],
+            header_actions: ['delete'],
+            state: {
+            },
+            sections: [
+                {
+                    name: 'details',
+                    fields: [
+                        {
+                            $type: 'link',
+                            name: 'ipaanchoruuid',
+                            label: '@i18n:objects.idoverrideuser.anchor_label',
+                            other_entity: 'user',
+                            no_check: true,
+                            is_link: true
+                        },
+                        {
+                            $type: 'textarea',
+                            name: 'description'
+                        },
+                        'uid',
+                        'uidnumber',
+                        'homedirectory'
+                    ]
+                }
+            ]
+        }
+    ],
+
+    adder_dialog: {
+        fields: [
+            {
+                $type: 'entity_select',
+                label: '@i18n:objects.idoverrideuser.anchor_label',
+                name: 'ipaanchoruuid',
+                other_entity: 'user',
+                other_field: 'uid'
+            },
+            'uid',
+            'uidnumber',
+            'homedirectory',
+            {
+                $type: 'textarea',
+                name: 'description'
+            }
+        ]
+    }
+};};
+
+var make_idoverridegroup_spec = function() {
+return {
+    name: 'idoverridegroup',
+    enable_test: function() {
+        return true;
+    },
+    policies:[
+        {
+            $factory: IPA.facet_update_policy,
+            source_facet: 'details',
+            dest_entity: 'idview',
+            dest_facet: 'idoverridegroup'
+        }
+    ],
+    containing_entity: 'idview',
+    facets: [
+        {
+            $type: 'details',
+            disable_breadcrumb: false,
+            containing_facet: 'idoverridegroup',
+            actions: [
+                'delete'
+            ],
+            header_actions: ['delete'],
+            state: {
+            },
+            sections: [
+                {
+                    name: 'details',
+                    fields: [
+                        {
+                            $type: 'link',
+                            name: 'ipaanchoruuid',
+                            label: '@i18n:objects.idoverridegroup.anchor_label',
+                            other_entity: 'group',
+                            no_check: true,
+                            is_link: true
+                        },
+                        {
+                            $type: 'textarea',
+                            name: 'description'
+                        },
+                        'cn',
+                        'gidnumber'
+                    ]
+                }
+            ]
+        }
+    ],
+
+    adder_dialog: {
+        fields: [
+             {
+                $type: 'entity_select',
+                label: '@i18n:objects.idoverridegroup.anchor_label',
+                name: 'ipaanchoruuid',
+                other_entity: 'group',
+                other_field: 'cn'
+            },
+            'cn',
+            'gidnumber',
+            {
+                $type: 'textarea',
+                name: 'description'
+            }
+        ]
+    }
+};};
+
+
+/**
+ * Facet for hosts which have current id view applied on
+ *
+ * @class idviews.appliedtohosts_facet
+ * @extends IPA.attribute_facet
+ */
+idviews.appliedtohosts_facet = function(spec, no_init) {
+
+    spec = spec || {};
+
+    var that = IPA.attribute_facet(spec, no_init);
+
+    /**
+     * @inheritDoc
+     */
+    that.get_refresh_command = function() {
+        var command = that.attribute_get_refresh_command();
+        command.set_option('show_hosts', true);
+        return command;
+    };
+
+    return that;
+};
+
+/**
+ * Apply Id view on hosts on hostgroup action base class
+ *
+ * @class idviews.apply_action
+ * @extends IPA.action
+ */
+idviews.apply_action = function(spec) {
+
+    spec = spec || {};
+    spec.name = spec.name || 'idview_apply';
+    spec.label = spec.label || '@i18n:objects.idview.apply_hosts';
+
+    var that = IPA.action(spec);
+
+    /**
+     * Confirm button label
+     * @property {string}
+     */
+    that.confirm_button_label = spec.confirm_button_label || '@i18n:buttons.apply';
+
+    /**
+     * Entity to apply
+     * @property {entity.entity|string}
+     */
+    that.other_entity = spec.other_entity || 'host';
+
+    /**
+     * Dialog title
+     * @property {string}
+     */
+    that.dialog_title = spec.dialog_title || '@i18n:objects.idview.apply_hosts_title';
+
+    /**
+     * Method
+     * @property {string}
+     */
+    that.method = spec.method || 'apply';
+
+    /**
+     * Success message
+     * @property {string}
+     */
+    that.success_msg = spec.success_msg || '@i18n:association.added';
+
+    /**
+     * @inheritDoc
+     */
+    that.execute_action = function(facet, on_success, on_error) {
+
+        that.show_dialog(facet);
+    };
+
+    /**
+     * Create and open dialog
+     */
+    that.show_dialog = function(facet, current_pkeys) {
+
+        var pkey = facet.get_pkey();
+        var other_entity = reg.entity.get(that.other_entity);
+        var other_entity_label = other_entity.metadata.label;
+        var title = text.get(that.dialog_title);
+        title = title.replace('${entity}', other_entity_label);
+        title = title.replace('${primary_key}', pkey);
+
+        var dialog = IPA.association_adder_dialog({
+            title: title,
+            entity: facet.entity,
+            pkey: pkey,
+            other_entity: other_entity,
+            attribute_member: that.attribute_member,
+            exclude: current_pkeys || [],
+            add_button_label: that.confirm_button_label
+        });
+
+        dialog.execute = function() {
+            var values = dialog.get_selected_values();
+            var command = that.get_command(
+                facet,
+                values,
+                function(data) {
+                    that.notify_change(facet);
+                    dialog.close();
+                    var succeeded = IPA.get_succeeded(data);
+                    var msg = text.get(that.success_msg).replace('${count}', succeeded);
+                    IPA.notify_success(msg);
+                },
+                function() {
+                    that.notify_change(facet);
+                    dialog.close();
+                });
+            command.execute();
+            return command;
+        };
+
+        dialog.open();
+    };
+
+    /**
+     * Construct action command
+     */
+    that.get_command = function(facet, values, on_success, on_error) {
+        var other_entity = reg.entity.get(that.other_entity);
+        var pkey = facet.get_pkey();
+        var args = pkey ? [pkey] : [];
+        var command = rpc.command({
+                entity: 'idview',
+                method: that.method,
+                args: args,
+                options: {},
+                on_success: on_success,
+                on_error: on_error
+            });
+
+        command.set_option(other_entity.name, values);
+        return command;
+    };
+
+    /**
+     * Notify idview.appliedtohosts facet that there were possible changes
+     * and a refresh is needed.
+     */
+    that.notify_change = function(current_facet) {
+
+        if (current_facet && current_facet.name === 'appliedtohosts') {
+            current_facet.refresh();
+        } else {
+            reg.entity.get('idview').
+                get_facet('appliedtohosts').
+                set_expired_flag();
+        }
+    };
+
+    that.apply_action_get_command = that.get_command;
+
+    return that;
+};
+
+
+/**
+ * Apply Id view on hosts of a hostgroup
+ *
+ * @class idviews.apply_hostgroup_action
+ * @extends idviews.apply_action
+ */
+idviews.apply_hostgroups_action = function(spec) {
+
+    spec = spec || {};
+    spec.name = spec.name || 'idview_apply_hostgroups';
+    spec.label = spec.label || '@i18n:objects.idview.apply_hostgroups';
+    spec.other_entity = spec.other_entity || 'hostgroup';
+    spec.dialog_title = spec.dialog_title || '@i18n:objects.idview.apply_hostgroups_title';
+
+    var that = idviews.apply_action(spec);
+    return that;
+};
+
+/**
+ * Unapply Id view from hosts
+ *
+ * @class idviews.unapply_host_action
+ * @extends idviews.apply_action
+ */
+idviews.unapply_host_action = function(spec) {
+
+    spec = spec || {};
+    spec.name = spec.name || 'idview_unapply_host';
+    spec.label = spec.label || '@i18n:objects.idview.unapply_hosts_all';
+    spec.other_entity = spec.other_entity || 'host';
+    spec.method = spec.method || 'unapply';
+    spec.dialog_title = spec.dialog_title || '@i18n:objects.idview.unapply_hosts_all_title';
+    spec.confirm_button_label = spec.confirm_button_label || '@i18n:buttons.unapply';
+    spec.success_msg = spec.success_msg || '@i18n:association.removed';
+
+    var that = idviews.apply_action(spec);
+
+    /**
+     * @inheritDoc
+     */
+    that.get_command = function(facet, values, on_success, on_error) {
+        var command = that.apply_action_get_command(facet, values, on_success, on_error);
+        // idview_unapply doesn't support primary keys to narrow down idviews
+        // to un-apply yet
+        command.args = [];
+        return command;
+    };
+
+    return that;
+};
+
+/**
+ * Unapply Id view from all hosts of a hostgroup
+ *
+ * @class idviews.unapply_hostgroups_action
+ * @extends idviews.unapply_host_action
+ */
+idviews.unapply_hostgroups_action = function(spec) {
+
+    spec = spec || {};
+    spec.name = spec.name || 'idview_unapply_hostgroups';
+    spec.label = spec.label || '@i18n:objects.idview.unapply_hostgroups';
+    spec.other_entity = spec.other_entity || 'hostgroup';
+    spec.dialog_title = spec.dialog_title || '@i18n:objects.idview.unapply_hostgroups_all_title';
+
+    var that = idviews.unapply_host_action(spec);
+    return that;
+};
+
+/**
+ * Unapply Id view from selected hosts
+ *
+ * @class idviews.unapply_action
+ * @extends idviews.unapply_host_action
+ */
+idviews.unapply_action = function(spec) {
+
+    spec = spec || {};
+    spec.name = spec.name || 'idview_unapply';
+    spec.label = spec.label || '@i18n:objects.idview.unapply_hosts';
+    spec.enable_cond = spec.enable_cond || ['item-selected'];
+    spec.enabled = spec.enabled === undefined ? false : spec.enabled;
+    spec.confirm_button_label = spec.confirm_button_label || '@i18n:buttons.unapply';
+    spec.method = spec.method || 'unapply';
+    spec.dialog_title = spec.dialog_title || '@i18n:objects.idview.unapply_hosts_title';
+
+    var that = idviews.unapply_host_action(spec);
+
+    /**
+     * @inheritDoc
+     */
+    that.show_dialog = function(facet, current_pkeys) {
+
+        var selected_values = facet.get_selected_values();
+
+        if (!selected_values.length) {
+            var message = text.get('@i18n:dialogs.remove_empty');
+            IPA.notify(message, 'error');
+            return;
+        }
+
+        var pkey = facet.get_pkey();
+        var other_entity = reg.entity.get('host');
+        var title = text.get(that.dialog_title);
+        title = title.replace('${primary_key}', pkey);
+
+        var dialog = IPA.association_deleter_dialog({
+            title: title,
+            entity: facet.entity,
+            pkey: pkey,
+            other_entity: other_entity,
+            values: selected_values,
+            method: that.method,
+            ok_label: that.confirm_button_label,
+            message: '@i18n:objects.idview.unapply_hosts_confirm'
+        });
+
+        dialog.execute = function() {
+            var command = that.get_command(
+                facet,
+                selected_values,
+                function(data) {
+                    that.notify_change(facet);
+                    var succeeded = IPA.get_succeeded(data);
+                    var msg = text.get('@i18n:association.removed').replace('${count}', succeeded);
+                    IPA.notify_success(msg);
+                },
+                function() {
+                    that.notify_change(facet);
+                }
+            );
+            command.execute();
+        };
+
+        dialog.open();
+    };
+
+    return that;
+};
+
+/**
+ * ID View entity specification object
+ * @member idviews
+ */
+idviews.spec = make_spec();
+
+/**
+ * ID user override entity specification object
+ * @member idviews
+ */
+idviews.idoverrideuser_spec = make_idoverrideuser_spec();
+
+/**
+ * ID group override entity specification object
+ * @member idviews
+ */
+idviews.idoverridegroup_spec = make_idoverridegroup_spec();
+
+/**
+ * Register entity
+ * @member idviews
+ */
+idviews.register = function() {
+    var e = reg.entity;
+    var f = reg.facet;
+    var a = reg.action;
+    e.register({type: 'idview', spec: idviews.spec});
+    e.register({type: 'idoverrideuser', spec: idviews.idoverrideuser_spec});
+    e.register({type: 'idoverridegroup', spec: idviews.idoverridegroup_spec});
+    f.copy('attribute', 'idview_appliedtohosts', {
+        factory: idviews.appliedtohosts_facet
+    });
+    a.register('idview_apply', idviews.apply_action);
+    a.register('idview_apply_hostgroups', idviews.apply_hostgroups_action);
+    a.register('idview_unapply', idviews.unapply_action);
+    a.register('idview_unapply_host', idviews.unapply_host_action);
+    a.register('idview_unapply_hostgroups', idviews.unapply_hostgroups_action);
+};
+
+phases.on('registration', idviews.register);
+
+return idviews;
+});
diff --git a/install/ui/src/freeipa/navigation/menu_spec.js b/install/ui/src/freeipa/navigation/menu_spec.js
index 9182d11bf56c73e1fce724d438fe2211105b75ad..ca1a290f479fd0cc6a399e6bc93bd3e8ed1fca40 100644
--- a/install/ui/src/freeipa/navigation/menu_spec.js
+++ b/install/ui/src/freeipa/navigation/menu_spec.js
@@ -159,6 +159,7 @@ var nav = {};
                     ]
                 },
                 { entity: 'idrange' },
+                { entity: 'idview' },
                 { entity: 'realmdomains' },
                 {
                     name: 'trusts',
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 2f42b3613c86894f98a41427c96849e5e6822550..85f274621cc7d7e5e14be903df151f15ec01b2b3 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -62,6 +62,7 @@
                         "add_and_close": "Add and Close",
                         "add_and_edit": "Add and Edit",
                         "add_many": "Add Many",
+                        "apply": "Apply",
                         "back": "Back",
                         "cancel": "Cancel",
                         "close": "Close",
@@ -81,6 +82,7 @@
                         "retry": "Retry",
                         "revoke": "Revoke",
                         "set": "Set",
+                        "unapply": "Un-apply",
                         "update": "Update",
                         "view": "View"
                     },
@@ -358,6 +360,28 @@
                         "hostgroup": {
                             "identity": "Host Group Settings"
                         },
+                        "idoverrideuser": {
+                            "anchor_label": "User to override"
+                        },
+                        "idoverridegroup": {
+                            "anchor_label": "Group to override"
+                        },
+                        "idview": {
+                            "appliedtohosts": "Applied to hosts",
+                            "appliedtohosts_title": "Applied to hosts",
+                            "apply_hostgroups": "Apply to host groups",
+                            "apply_hostgroups_title": "Apply ID View ${primary_key} on hosts of ${entity}",
+                            "apply_hosts": "Apply to hosts",
+                            "apply_hosts_title": "Apply ID view ${primary_key} on ${entity}",
+                            "unapply_hostgroups": "Un-apply from host groups",
+                            "unapply_hostgroups_all_title": "Un-apply ID Views from hosts of hostgroups",
+                            "unapply_hostgroups_title": "Un-apply ID View ${primary_key} from hosts of ${entity}",
+                            "unapply_hosts": "Un-apply",
+                            "unapply_hosts_all": "Un-apply from hosts",
+                            "unapply_hosts_all_title": "Un-apply ID Views from hosts",
+                            "unapply_hosts_confirm": "Are you sure you want to un-apply ID view from selected entries?",
+                            "unapply_hosts_title": "Un-apply ID View ${primary_key} from hosts"
+                        },
                         "krbtpolicy": {
                             "identity": "Kerberos Ticket Policy"
                         },
diff --git a/ipalib/plugins/internal.py b/ipalib/plugins/internal.py
index af51cab63aa748456e0442c7a2a2d857f39ed82f..0bfce0edfbffb8d9d24b8adbc5f8b3fccac1d5c7 100644
--- a/ipalib/plugins/internal.py
+++ b/ipalib/plugins/internal.py
@@ -204,6 +204,7 @@ class i18n_messages(Command):
             "add_and_close": _("Add and Close"),
             "add_and_edit": _("Add and Edit"),
             "add_many": _("Add Many"),
+            "apply": _("Apply"),
             "back": _("Back"),
             "cancel": _("Cancel"),
             "close": _("Close"),
@@ -223,6 +224,7 @@ class i18n_messages(Command):
             "retry": _("Retry"),
             "revoke": _("Revoke"),
             "set": _("Set"),
+            "unapply": ("Un-apply"),
             "update": _("Update"),
             "view": _("View"),
         },
@@ -502,6 +504,28 @@ class i18n_messages(Command):
             "hostgroup": {
                 "identity": _("Host Group Settings"),
             },
+            "idoverrideuser": {
+                "anchor_label": _("User to override"),
+            },
+            "idoverridegroup": {
+                "anchor_label": _("Group to override"),
+            },
+            "idview": {
+                "appliedtohosts": _("Applied to hosts"),
+                "appliedtohosts_title": _("Applied to hosts"),
+                "apply_hostgroups": _("Apply to host groups"),
+                "apply_hostgroups_title": _("Apply ID View ${primary_key} on hosts of ${entity}"),
+                "apply_hosts": _("Apply to hosts"),
+                "apply_hosts_title": _("Apply ID view ${primary_key} on ${entity}"),
+                "unapply_hostgroups": _("Un-apply from host groups"),
+                "unapply_hostgroups_all_title": _("Un-apply ID Views from hosts of hostgroups"),
+                "unapply_hostgroups_title": _("Un-apply ID View ${primary_key} from hosts of ${entity}"),
+                "unapply_hosts": _("Un-apply"),
+                "unapply_hosts_all": _("Un-apply from hosts"),
+                "unapply_hosts_all_title": _("Un-apply ID Views from hosts"),
+                "unapply_hosts_confirm": _("Are you sure you want to un-apply ID view from selected entries?"),
+                "unapply_hosts_title": _("Un-apply ID View ${primary_key} from hosts"),
+            },
             "krbtpolicy": {
                 "identity": _("Kerberos Ticket Policy"),
             },
-- 
1.9.3

From 977bd2a53589b91bf59d6cb3efe64741dcdeda45 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 18 Sep 2014 17:28:41 +0200
Subject: [PATCH] webui: add simple link column support

Usual link columns are link with primary key of current entity.

This patch allows to create a link to arbitrary non-nested entity.
---
 install/ui/src/freeipa/facet.js  | 4 +++-
 install/ui/src/freeipa/widget.js | 6 ++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index c26df4e73cc86815d816934d5bc733fcedcab62c..c06df00a87bb2ce0961a5187a10d68e4ebb5ad44 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1965,7 +1965,9 @@ exp.table_facet = IPA.table_facet = function(spec, no_init) {
 
             var metadata = IPA.get_entity_param(entity.name, column.name);
             column.primary_key = metadata && metadata.primary_key;
-            column.link = (column.link === undefined ? true : column.link) && column.primary_key;
+            if (column.primary_key) {
+                column.link = column.link === undefined ? true : column.link;
+            }
 
             if (column.link && column.primary_key) {
                 column.link_handler = function(value) {
diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index f447103847089ba02f76f01bc8626bf2f757f3ea..c7a082b187e06221846487fbb137811b9edb5e55 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -2489,6 +2489,8 @@ IPA.column = function (spec) {
     that.link = spec.link;
     that.adapter = builder.build('adapter', spec.adapter || 'adapter', { context: that });
     that.formatter = builder.build('formatter', spec.formatter);
+    that.target_entity = spec.target_entity;
+    that.target_facet = spec.target_facet;
 
     if (!that.entity) {
         throw {
@@ -2585,6 +2587,10 @@ IPA.column = function (spec) {
      * Intended to be overridden.
      */
     that.link_handler = function(value) {
+
+        // very simple implementation which doesn't handle navigation to
+        // nested entities
+        navigation.show_entity(that.target_entity, that.target_facet, [value]);
         return false;
     };
 
-- 
1.9.3

From 74545332620726fe43a696920b3dc6e5b5da5644 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 12 Sep 2014 18:27:20 +0200
Subject: [PATCH] webui: allow to skip link widget link validation

---
 install/ui/src/freeipa/widget.js | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 10715aa49c0164bcb59b608d8f430043fbac8f41..f447103847089ba02f76f01bc8626bf2f757f3ea 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -4096,8 +4096,21 @@ IPA.link_widget = function(spec) {
      */
     that.other_pkeys = spec.other_pkeys || other_pkeys;
 
+    /**
+     * Indicates if it's a valid link
+     * @property {boolean}
+     */
     that.is_link = spec.is_link || false;
 
+    /**
+     * Whether to skip entity validation step
+     *
+     * Value of `is_link` won't be changed.
+     *
+     * @property {boolean}
+     */
+    that.no_check = spec.no_check;
+
     that.value = '';
     that.values = [];
 
@@ -4131,7 +4144,7 @@ IPA.link_widget = function(spec) {
         that.value = that.values.slice(-1)[0] || '';
         that.link.html(that.value);
         that.nonlink.html(that.value);
-
+        that.update_link();
         that.check_entity_link();
         that.on_value_changed(values);
     };
@@ -4177,6 +4190,8 @@ IPA.link_widget = function(spec) {
             return;
         }
 
+        if (that.no_check) return;
+
         rpc.command({
             entity: that.other_entity.name,
             method: 'show',
@@ -4192,8 +4207,6 @@ IPA.link_widget = function(spec) {
                 that.update_link();
             }
         }).execute();
-
-        that.update_link();
     };
 
     /** @inheritDoc */
-- 
1.9.3

From 08bffc4e8241d92fcbea8801be1d6a2826487c31 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 12 Sep 2014 18:13:59 +0200
Subject: [PATCH] webui: do not show internal facet name to user

---
 install/ui/src/freeipa/facet.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index 07b43e032178392f956b84c9c2df67fb24037b55..c26df4e73cc86815d816934d5bc733fcedcab62c 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -1228,7 +1228,6 @@ exp.facet_header = IPA.facet_header = function(spec) {
 
         var li = $('<li/>', {
             name: other_facet.name,
-            title: other_facet.name,
             click: function() {
                 if (li.hasClass('entity-facet-disabled')) {
                     return false;
-- 
1.9.3

From 2849129c23ef975116c7e84624d6d84f32ab2af3 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 12 Sep 2014 17:15:39 +0200
Subject: [PATCH] webui: treat value as pkey in link widget

Current default mechanism of a link widget assumes that pkeys of a current facet are pkeys for the link. It works for the only usage - in password policy. It's rather inflexible since it can't be used if the keys are in other attribute. This behavior is also bad in nested entities - creates a link to itself which is pointless.

This patch changes the default behavior to assume that the supplied value are the pkeys and that the last pkey is the value to display.

It also keeps the old method of overriding `other_pkeys` method so if the last and only pkey is the actual value to display then the method can tranform it into the pkeys which keeps compatibility with descendant widgets (`host_dnsrecord_entity_link_widget`, `dnsrecord_host_link_widget`).
---
 install/ui/src/freeipa/widget.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 252fe02088fd63e819693b38937443732db8dc0f..10715aa49c0164bcb59b608d8f430043fbac8f41 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -4098,10 +4098,11 @@ IPA.link_widget = function(spec) {
 
     that.is_link = spec.is_link || false;
 
-    that.value = [];
+    that.value = '';
+    that.values = [];
 
     function other_pkeys () {
-        return that.facet.get_pkeys();
+        return that.values;
     }
 
     /** @inheritDoc */
@@ -4126,7 +4127,8 @@ IPA.link_widget = function(spec) {
     /** @inheritDoc */
     that.update = function(values) {
 
-        that.value = util.normalize_value(values)[0] || '';
+        that.values = util.normalize_value(values);
+        that.value = that.values.slice(-1)[0] || '';
         that.link.html(that.value);
         that.nonlink.html(that.value);
 
-- 
1.9.3

From 080f9e4ff8a2585afb284583a949f0ec56437e3d Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 12 Sep 2014 15:26:28 +0200
Subject: [PATCH] webui: improve breadcrumb navigation

Fixes issue when:
- user navigates to a nested facet
- refreshes browser
- uses breadcrumb navigation to go to parent entity page which requires a pkey. E.g. from automount keys to maps.

The old code relies on the facet, that user visited the parent facet before and therefore the facet has pkey stored. It fails after the browser reload.

Allows to specify a containing_facet. It allows breadcrumb navigation to return to a different facet than the 'default'.
---
 install/ui/src/freeipa/facet.js | 44 +++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 15 deletions(-)

diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js
index 594f9af5c1c7092bc2cd13485db45e204bc4fe65..07b43e032178392f956b84c9c2df67fb24037b55 100644
--- a/install/ui/src/freeipa/facet.js
+++ b/install/ui/src/freeipa/facet.js
@@ -298,6 +298,15 @@ exp.facet = IPA.facet = function(spec, no_init) {
      */
     that.redirect_info = spec.redirect_info;
 
+    /**
+     * Name of containing facet of containing entity
+     *
+     * A guide for breadcrumb navigation
+     *
+     * @property {string}
+     */
+    that.containing_facet = spec.containing_facet;
+
 
     /**
      * Facet requires authenticated user
@@ -1130,25 +1139,30 @@ exp.facet_header = IPA.facet_header = function(spec) {
         if (!that.breadcrumb) return;
 
         var items = [];
-        var item, i, l;
+        var item, i, l, keys, target_facet, target_facet_keys, containing_entity;
 
         // all pkeys should be available in facet
-        var keys = that.facet.get_pkeys();
-        var entity = that.facet.entity.get_containing_entity();
-        i = keys.length - 2; //set pointer to first containing entity
-        while (entity) {
+        keys = that.facet.get_pkeys();
+
+        target_facet_keys = keys;
+        containing_entity = that.facet.entity.get_containing_entity();
+        target_facet = that.facet;
+
+        while (containing_entity) {
+            target_facet = containing_entity.get_facet(
+                target_facet.containing_facet || 'default');
+            target_facet_keys = target_facet_keys.slice(0, -1);
             items.unshift({
-                text: keys[i],
-                title: entity.metadata.label_singular,
-                handler: function(entity) {
+                text: target_facet_keys.slice(-1),
+                title: containing_entity.metadata.label_singular,
+                handler: function(facet, keys) {
                     return function() {
-                        navigation.show_entity(entity.name, 'default');
+                        navigation.show(facet, keys);
                         return false;
                     };
-                }(entity)
+                }(target_facet, target_facet_keys)
             });
-            entity = entity.get_containing_entity();
-            i--;
+            containing_entity = containing_entity.get_containing_entity();
         }
 
         //calculation of breadcrumb keys length
@@ -1167,12 +1181,12 @@ exp.facet_header = IPA.facet_header = function(spec) {
         }
         max_key_l = ((max_bc_l - bc_l) / to_limit) - 4;
 
+        target_facet = target_facet.get_redirect_facet();
         // main level item
-        var redirect_facet = that.facet.get_redirect_facet();
         items.unshift({
-            text: redirect_facet.label,
+            text: target_facet.label,
             handler: function() {
-                that.facet.redirect();
+                navigation.show(target_facet);
                 return false;
             }
         });
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to