On 05/13/2016 06:56 PM, Petr Vobornik wrote:
On 04/26/2016 04:23 PM, Pavel Vomacka wrote:
Self-NACK for patches 0027, 28, 29, 30 - used incorrect policy. I also attach
all patches which were not changed - it is easier to get the whole patchset.

On 04/26/2016 02:02 PM, Pavel Vomacka wrote:
I forgot to mention that my patches requires patches from :
https://www.redhat.com/archives/freeipa-devel/2016-April/msg00209.html


On 04/26/2016 01:33 PM, Pavel Vomacka wrote:
Hello,

the attached patches add support for more certificates and ability to add and
remove certificates. Fixes these two tickets:
https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381

These patches add ability to view, get, download, revoke, restore and delete
each certificate directly from user/host/service details page. There is also
button for adding new certificates.

There is one known issue, that after page save action is performed some data
disappear (includes certificates). This issue has a ticket already:
https://fedorahosted.org/freeipa/ticket/5776

--
Pavel^3 Vomacka

Great stuff, couple comments below.

We can discuss some items in person. Not everything needs to be done.

I didn't run it, just reading the code.

Patch 0018:

1. Nit pick: When a value should be boolean, then following method won't
make sure that dropdown_menu won't be e.g. an object.
  +    that.dropdown_menu = spec.dropdown_menu || false;

I would prefer:
  +    that.dropdown_menu = !!spec.dropdown_menu;

Which retypes it to boolean. If default should be true (not this case) then:
   that.dropdown_menu = spec.dropdown_menu !== undefined ?
!!spec.dropdown_menu : true;

Also the interface is very specific. It says that the child widget will
have dropdown menu. What if the actions won't be in dropdown menu but,
e.g., some overlay menu.

Imho the interface should be:

  that.custom_actions = !!spec.custom_actions;

Than the child object would have define,e.g., :

    action_object get_custom_actions()

Interface of action_object would be e.g.:
    get_items()
    set_items(items)
    enable_item(name)
    disable_item(name)

Dropdown menu would have to define these methods.

Patch 0019:

1. Shouldn't disable_item or enable_item automatically rerender the items?

2. The rerender, used in later patches. Imo it should do only:

   if (this.ul_node) {
      construct.empty(this.ul_node);
      this._render_items(this.items);
   }

Or just re-render the one item.
   $( "li[data-name=" + item.name +"]", this.ul_node ).replaceWith(
this._render_item(item));

3. in future for loops write:
   for (var i=0, l=this.items.length; i<l; i++) {
instead of
   for (var i=0; i<this.items.length; i++) {

It's defensive style when you don't know if computing length is O(1) or
O(n) or how big is n. In this instance it doesn't probably matter.

Patch 0020:

1. Working widgets inherits from IPA.widget so why do you redefine
'name', 'facet', 'entity'

2. why to you introduce 'base_cls' and 'other_cls' when there are
'base_css_class' and 'css_class'?

3. typo in comment 'opaciti'

4. We can ignore IE < 9, I'm not sure if you avoid using some features
because of IE9.

5. _normalize_bg_color_css could be moved to util.js But I wonder if we
need it. rgba is supported in 95% of browsers and definitely in all
supported by IPA (chrome, firefox).

Patch 0021:

1. The original code is not great - RPC code should be agnostic to
display layer. Ideally it would not call methods like
IPA.hide_activity_icon(); and IPA.display_activity_icon();

I would rather avoid adding more of such things there and would refactor
now, when it is easy.

IMO the rpc class could use Evented mixin and have:
  4 local events: start, end, success, error
  2 global topics: rpc-start, rpc-end

Local events will be new - will replace the notify_activity_start and
notify_activity_end. For convenience, spec object could accept a
handlers in similar fashion as now. The handlers would be registered to
the events.

Global topics would replace current IPA.hide_activity_icon(); and
IPA.display_activity_icon();

App class would subscribe to the global events and to stuff (hide|show)
accordingly. Example of such subscription:
    topic.subscribe('phase-error', lang.hitch(this, this.on_phase_error));

Then hide_activity_icon could be something like notify_global with
default to true which can be suppressed by setting it to false.

What do you think?

Patch 0022:

1. Leftover line:
   var sn = record[i].serial_number;

Patch 0023:

1. move the css code from ipa.css to layout.less?

2. Did you mean "display: table"?
   display: cert-table;

3. Why are create_layout, create_row and create_cell private methods of
create_content? It would make sense to create a table dialog mixin
(similar as confirm mixin) and use the same code in all cert dialogs(if
applicable).

4. 'table-head' class is used in every cell. Why is it call 'head' when
it is in fact in all cells?

Patch 0024:

Will require changes described in patch 21(rpc).

1. should the methods have rather params: serial_number and
revocation_reason, instead of certificate(maybe ok) and dialog(not ok)?
That way it would be more general/reusable.

Patch 0025: ACK

Patch 0026:

1. Add link use exactly the same code as in multivalued_widget. The code
should be moved to method: create_add_link(container) and then reused.
new_row would be overridedn and it would call open_addcert_dialog or
open_addcert_dialog renamed to new_row.

2. it can have a spec default:
   spec.dropdown_menu = spec.dropdown_menu !== undefined ? true :
spec.dropdown_menu;

And then it doesn't have to be defined in patches 28, 29, 30.


Patch 0027:

1. Why is download action name a 'that.data_url' ?

2. the dropdown_menu -> custom_actions change

3. "that.certificate &&" is not required it will be always true
         that.certificate = $.extend({}, certificate);

         if (that.certificate && that.certificate.revoked !== 'undefined') {
             that.get_cert_revoke_status();
         }

4. update_link is weird name for a method which updates data in a table

5. "that.update_link = function(r)", "r" shouldn't be there

6. Following doesn't look future proof:
"""
     that.get_cert_revoke_status = function() {
         // Double check whether the certificate is issued by our CA.
         var issuer = 'CN=' + that.ipa_issuer_commonname + ',' +
             that.ipa_issuer_subjbase;
         if (that.certificate.issuer !== issuer) return;
"""
Would check it with Fraser

7. in handle_revocation_reason, useless call:
   var dd_menu = that.get_dropdown_menu();

8. in compose_dialog_title, there is a check for `cert.subject` if true
it sets `cn`, `o`. But if false then the values are `undefined` which
are then used in the r.replace calls. Is it intended?

Patch 0028:
Looks OK, "dropdown_menu: true" doesn't have to be there as mentioned
earlier

Will test later.

Patch 0029:

1. What about extending value_state_evaluator with adapter and param
property. So that it would use standard adapter by default and every
child could use it's own? IMO other parts of UI could use it as well.
The reason why it doesn't use adapter already is that adapters were
introduced later.

It would simplify both definitions + the definitions in patch 30.

Patch 0030:

Same as patch 0029.
Thank you for the review. I fixed everthing, only rpc code isn't changed. I will do it later in separate patch.

These patches requires jcholast's patches 551.2, 552.2, 623, 624 (which require ftweedal's Sub-CA patches) and WebUI patches for Sub-CAs (because of download functionality).

Edited and rebased patches are attached.

--
Pavel^3 Vomacka
From 43b21f9f0d5db03de1ff7859d246b08c8d54f829 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:22:30 +0200
Subject: [PATCH 01/14] Add support for custom menu in multivalued widget

Every single widget which is in multivalued widget can now have custom action menu
and the delete button is included in this custom action menu.

Part of this ticket:
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/widget.js | 85 ++++++++++++++++++++++++++++++++--------
 1 file changed, 69 insertions(+), 16 deletions(-)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 0f3e7f27bdd3f7366764c688711762cfc5f68eaf..c9596a0593443a959a2142a85520efea09d9d962 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -988,6 +988,27 @@ IPA.multivalued_widget = function(spec) {
     that.initialized = true;
     that.updating = false;
 
+    /**
+     *
+     * CUSTOM ACTION MENU HELP:
+     *
+     * Custom actions variable sets whether each row will use custom menu
+     * for showing remove button or not. Default is false - button will be
+     * displayed classicaly as any other button. True means custom menu is
+     * present..
+     *
+     * In case that the variable is set to true, the widget of each row
+     * has to offer following method:
+     *      action_object get_custom_actions();
+     *
+     * Then the action_object has to have following interface:
+     *      Array[item] get_items();
+     *      set_items(Array[item]);
+     *      enable_item(name);
+     *      disable_item(name);
+     */
+    that.custom_actions = !!spec.custom_actions;
+
     that.rows = [];
 
     that.base_css_class = that.base_css_class + ' multivalued-widget';
@@ -1165,23 +1186,42 @@ IPA.multivalued_widget = function(spec) {
             that.emit('error-show', { source: that });
         });
 
+        var remove_row = function() {
+            that.remove_row(row);
+        };
+
         var remove_link_visible = !(row.is_new || !that.is_writable());
-        row.remove_link = $('<button/>', {
-            name: 'remove',
-            'class': 'btn btn-default',
-            title: text.get('@i18n:buttons.remove'),
-            html: text.get('@i18n:buttons.remove'),
-            click: function () {
-                that.remove_row(row);
-                return false;
+
+        if (!that.custom_actions) {
+            row.remove_link = $('<button/>', {
+                name: 'remove',
+                'class': 'btn btn-default',
+                title: text.get('@i18n:buttons.remove'),
+                html: text.get('@i18n:buttons.remove'),
+                click: function () {
+                    remove_row();
+                    return false;
+                }
+            });
+
+            if (row.widget.input_group_btn) {
+                // A little hack to make delete button part of row widget
+                row.remove_link.appendTo(row.widget.input_group_btn);
+            } else {
+                row.remove_link.appendTo(row.container);
             }
-        });
-
-        if (row.widget.input_group_btn) {
-            // A little hack to make delete button part of row widget
-            row.remove_link.appendTo(row.widget.input_group_btn);
         } else {
-            row.remove_link.appendTo(row.container);
+            row.remove_link = {
+                name: 'remove',
+                label: text.get('@i18n:buttons.remove'),
+                handler: remove_row
+            };
+
+            var custom_actions = row.widget.get_custom_actions();
+            var items = custom_actions.get_items();
+            items.push(row.remove_link);
+            custom_actions.set_items(items);
+            custom_actions.render();
         }
 
         if (row.is_new) {
@@ -1202,10 +1242,23 @@ IPA.multivalued_widget = function(spec) {
 
     that.toggle_remove_link = function(row, show) {
         if (show) {
-            row.remove_link.show();
+            if (that.custom_actions) {
+                row.widget.get_custom_actions().enable_item('remove');
+                row.widget.get_custom_actions().render();
+            }
+            else {
+                row.remove_link.show();
+            }
         } else {
-            row.remove_link.hide();
+            if (that.custom_actions) {
+                row.widget.get_custom_actions().disable_item('remove');
+                row.widget.get_custom_actions().render();
+            }
+            else {
+                row.remove_link.hide();
+            }
         }
+
         if (row.widget.update_input_group_state) {
             row.widget.update_input_group_state();
         }
-- 
2.5.5

From 21b62cc857e37a4ae5b005e4c94b41ad7f63c599 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:27:10 +0200
Subject: [PATCH 02/14] Extends functionality of DropdownWidget

Adds methods which are able to enable and disable options according to the name of option
and methods which set or get whole item list.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/widgets/DropdownWidget.js | 35 ++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/install/ui/src/freeipa/widgets/DropdownWidget.js b/install/ui/src/freeipa/widgets/DropdownWidget.js
index a81960545a6503240153906908cbc554e473cccb..699438b7e3493a9535eb337fe3c6b4000d42936a 100644
--- a/install/ui/src/freeipa/widgets/DropdownWidget.js
+++ b/install/ui/src/freeipa/widgets/DropdownWidget.js
@@ -142,6 +142,41 @@ define(['dojo/_base/declare',
             return this.dom_node;
         },
 
+        get_items: function() {
+            return this.items;
+        },
+
+        set_items: function(items) {
+            this.items = items;
+        },
+
+        disable_item: function(item_name) {
+            var item = this._find_item(item_name);
+            if (item) {
+                item.disabled = true;
+                $("li[data-name=" + item.name +"]", this.ul_node ).replaceWith(
+                    this._render_item(item));
+            }
+        },
+
+        enable_item: function(item_name) {
+            var item = this._find_item(item_name);
+            if (item) {
+                item.disabled = false;
+                $("li[data-name=" + item.name +"]", this.ul_node ).replaceWith(
+                    this._render_item(item));
+            }
+        },
+
+        _find_item: function(item_name) {
+            for (var i=0, l=this.items.length; i<l; i++) {
+                if (this.items[i].name && this.items[i].name == item_name) {
+                    return this.items[i];
+                }
+            }
+            return null;
+        },
+
         _render_toggle: function(container) {
 
             this.toggle_node = construct.create('a', {
-- 
2.5.5

From 6c85a9afc6f44afaa318cb4daa3e70544ec1adbb Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:31:07 +0200
Subject: [PATCH 03/14] Add working widget

This widget can be used as notification that some other widget is working.
It shows spinner and cover the other widget by specified color.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/less/widgets.less     | 14 +++++++++
 install/ui/src/freeipa/widget.js | 63 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 56a3104624b489c3a1b7f829d7eadb6c44e90a3f..b68161b65ef856d731778ef97044b6cdfeaca4ca 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -143,6 +143,20 @@
     }
 }
 
+// Working widget
+
+.working-widget {
+    position: absolute;
+    display: none;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    .spinner {
+        align-self: center;
+    }
+}
+
+
 // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=409254
 tbody:empty { display: none; }
 
diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index c9596a0593443a959a2142a85520efea09d9d962..157806cd0d5679c3102d256fbb72d40999eed0a4 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -325,6 +325,69 @@ IPA.widget = function(spec) {
 };
 
 /**
+ * Working widget which contains spinner and can be used while some other
+ * widget is working.
+ *
+ * @class
+ * @param {Object} spec
+ */
+IPA.working_widget = function(spec) {
+
+    spec = spec || {};
+    spec.base_css_class = spec.base_css_class || 'working-widget';
+
+    var that = IPA.widget(spec);
+
+    /**
+     * Patternfly class name which defines size of spinner. Possible values:
+     * ''|'spinner-lg'|'spinner-sm'|'spinner-xs'
+     *
+     * @property {string} class_name
+     */
+    that.spinner_size_cls = spec.spinner_size_cls || 'spinner-sm';
+
+    /**
+     * The variable defines the background color of working widget.
+     *
+     * @property {string} color definition
+     */
+    that.bg_color = spec.bg_color || 'rgba(255,255,255,0.7)';
+
+    /**
+     * Z-index of this widget.
+     *
+     * @property {number}
+     */
+    that.z_index = spec.z_index || 99;
+
+    that.create = function(container) {
+
+        that.spin_div = $('<div />', {
+            'class': that.base_css_class,
+            style: 'background-color: ' + that.bg_color + ';'
+                    + 'z-index: ' + that.z_index + ';'
+        });
+        that.spinner = $('<div />', {
+            'class': 'spinner ' + that.spinner_size_cls
+        }).appendTo(that.spin_div);
+
+        that.spin_div.appendTo(container);
+
+        that.on('hide-spinner', function() {
+            that.spin_div.fadeOut();
+        });
+
+        that.on('display-spinner', function() {
+            that.spin_div.fadeIn();
+            that.spin_div.css('display', 'flex');
+            that.spin_div.css('display', '-webkit-flex');
+        });
+    };
+
+    return that;
+};
+
+/**
  * Base class for input gathering widgets.
  * @class
  * @extends IPA.widget
-- 
2.5.5

From 8815b7417ff56761ca1a35d1ea9387cbfa90cfff Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:34:06 +0200
Subject: [PATCH 04/14] Add ability to turn off activity icon

By specifying correct attribute when creating command it turn off showing activity icon
when webui waits for response from the server.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/rpc.js | 46 +++++++++++++++++++++++++++++++++++++++----
 1 file changed, 42 insertions(+), 4 deletions(-)

diff --git a/install/ui/src/freeipa/rpc.js b/install/ui/src/freeipa/rpc.js
index 9e684a7daccfc4cd327d256844be6dbf1efe6533..a185585f4176658e299e7e92434522c936cc36b4 100644
--- a/install/ui/src/freeipa/rpc.js
+++ b/install/ui/src/freeipa/rpc.js
@@ -96,6 +96,29 @@ rpc.command = function(spec) {
      */
     that.retry = typeof spec.retry == 'undefined' ? true : spec.retry;
 
+    /**
+     * Allow turning off the activity icon.
+     *
+     * @property {Boolean} show=true
+     */
+    that.hide_activity_icon = spec.hide_activity_icon || false;
+
+    /**
+     * Allow set function which will be called when the activity of the command
+     * starts. Works only when 'activity_icon' property is set to false
+     *
+     * @property {Function}
+     */
+    that.notify_activity_start = spec.notify_activity_start || null;
+
+    /**
+     * Allow set function which will be called when the activity of the command
+     * ends. Works only when 'activity_icon' property is set to false
+     *
+     * @property {Function}
+     */
+    that.notify_activity_end = spec.notify_activity_end || null;
+
     /** @property {string} error_message Default error message */
     that.error_message = text.get(spec.error_message || '@i18n:dialogs.batch_error_message', 'Some operations failed.');
 
@@ -198,6 +221,15 @@ rpc.command = function(spec) {
         }
     };
 
+    that.handle_notify_execution_end = function() {
+        if (that.hide_activity_icon) {
+            if (that.notify_activity_end) that.notify_activity_end();
+        }
+        else {
+            IPA.hide_activity_icon();
+        }
+    };
+
     /**
      * Execute the command.
      *
@@ -280,7 +312,7 @@ rpc.command = function(spec) {
          */
         function error_handler(xhr, text_status, error_thrown) {
 
-            IPA.hide_activity_icon();
+            that.handle_notify_execution_end();
 
             if (xhr.status === 401) {
                 error_handler_auth(xhr, text_status, error_thrown);
@@ -360,7 +392,7 @@ rpc.command = function(spec) {
                 });
 
             } else {
-                IPA.hide_activity_icon();
+                that.handle_notify_execution_end();
 
                 var ajax = this;
                 var failed = that.get_failed(that, data.result, text_status, xhr);
@@ -413,7 +445,13 @@ rpc.command = function(spec) {
             error: error_handler_login
         };
 
-        IPA.display_activity_icon();
+        if (that.hide_activity_icon) {
+            if (that.notify_activity_start) that.notify_activity_start();
+        }
+        else {
+            IPA.display_activity_icon();
+        }
+
         $.ajax(that.request);
         return deferred.promise;
     };
@@ -1005,4 +1043,4 @@ rpc.extract_objects = function(values) {
 };
 
 return rpc;
-});
\ No newline at end of file
+});
-- 
2.5.5

From 6ad92f3c5603be294689d61f3f21fa278c1c4c02 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:45:49 +0200
Subject: [PATCH 05/14] Add Object adapter

Object adapter changes data to more useful format. Single value is reachable
as single value, property with more values is transformed to array.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/field.js | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/install/ui/src/freeipa/field.js b/install/ui/src/freeipa/field.js
index 12ef7f45585802c4099e469c9fb5188b3a3587ab..3f7e1d1b48efdbf60c6472458f6b4622cbea5232 100644
--- a/install/ui/src/freeipa/field.js
+++ b/install/ui/src/freeipa/field.js
@@ -1266,6 +1266,45 @@ field.SshKeysAdapter = declare([field.Adapter], {
     }
 });
 
+
+/**
+ * ObjectAdapter is basic adapter which converts object to more usable format.
+ * All properties which have only one value are tranformed this way:
+ *  property1: {"__base64__": "value1"} => property1: "value1",
+ *  property2: {"value2"} => property2: "value2",
+ * Works for __base64__ as well as for __datetime__ and __dns_name__
+ *
+ * In case that the property has more values, then they are returned as array.
+ *
+ * @class
+ * @extends field.Adapter
+ */
+field.ObjectAdapter = declare([field.Adapter], {
+
+    normalize_object: function(obj) {
+        for (var property in obj) {
+            if (obj.hasOwnProperty(property)) {
+                obj[property] = rpc.extract_objects([obj[property]]);
+                if (obj[property].length == 1) {
+                    obj[property] = obj[property][0];
+                }
+            }
+        }
+    },
+
+    load: function(data) {
+
+        var record = this.get_record(data);
+
+        for (var i=0; i<record.length; i++) {
+            this.normalize_object(record[i]);
+        }
+
+        return record;
+    }
+});
+
+
 /**
  * Field for enabling/disabling entity
  *
@@ -1536,6 +1575,7 @@ field.register = function() {
     v.register('same_password', field.same_password_validator);
 
     l.register('adapter', field.Adapter);
+    l.register('object_adapter', field.ObjectAdapter);
 };
 phases.on('registration', field.register);
 
-- 
2.5.5

From 77b30a67926db4255190b68cb3ea925c61831f40 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 10:50:38 +0200
Subject: [PATCH 06/14] Refactored certificate view and remove hold dialog

Removed old layout created using html tables. Now table layout is made by div
and modern css styling.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/less/layout.less           |  21 +++-
 install/ui/src/freeipa/certificate.js | 208 ++++++++++++++++------------------
 install/ui/src/freeipa/dialog.js      | 104 +++++++++++++++++
 install/ui/test/data/ipa_init.json    |   1 +
 ipaserver/plugins/internal.py         |   1 +
 5 files changed, 223 insertions(+), 112 deletions(-)

diff --git a/install/ui/less/layout.less b/install/ui/less/layout.less
index bf425d4e7a6d39bd327add711781fa7f51e0ff47..1b7e0479c98c328066471f18e894f52b1a69d39c 100644
--- a/install/ui/less/layout.less
+++ b/install/ui/less/layout.less
@@ -4,4 +4,23 @@
 
 #container {
     background-color: white;
-}
\ No newline at end of file
+}
+
+/* --- Table layout created by CSS --- */
+.table-layout {
+    display: table;
+}
+.table-row {
+    display: table-row;
+    .table-cell {
+        display: table-cell;
+        vertical-align: middle;
+    }
+    .table-head {
+        padding: 0 5px 0 0;
+    }
+}
+
+.break-words {
+    word-break: break-all;
+}
diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index 79f2582f8c38effae91ec440f2c7e58f27444f44..e804f5126372604c47cee329a40ffb040b4efd35 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -229,6 +229,7 @@ IPA.cert.revoke_dialog = function(spec) {
     spec.ok_label = spec.ok_label || '@i18n:buttons.revoke';
 
     var that = IPA.confirm_dialog(spec);
+    IPA.table_mixin().apply(that);
 
     that.get_reason = function() {
         return that.select.val();
@@ -236,22 +237,17 @@ IPA.cert.revoke_dialog = function(spec) {
 
     that.create_content = function() {
 
-        var table = $('<table/>').appendTo(that.container);
+        var table = that.create_layout().appendTo(that.container);
 
-        var tr = $('<tr/>').appendTo(table);
+        var tr = that.create_row().appendTo(table);
+        var td = that.create_cell('@i18n:objects.cert.note', ':').appendTo(tr);
+        td = that.create_cell('@i18n:objects.cert.revoke_confirmation')
+            .appendTo(tr);
 
-        var td = $('<td/>').appendTo(tr);
-        td.append(text.get('@i18n:objects.cert.note')+':');
-
-        td = $('<td/>').appendTo(tr);
-        td.append(text.get('@i18n:objects.cert.revoke_confirmation'));
-
-        tr = $('<tr/>').appendTo(table);
-
-        td = $('<td/>').appendTo(tr);
-        td.append(text.get('@i18n:objects.cert.reason')+':');
-
-        td = $('<td/>').appendTo(tr);
+        tr = that.create_row().appendTo(table);
+        td = that.create_header_cell('@i18n:objects.cert.reason', ':')
+            .appendTo(tr);
+        td = that.create_cell().appendTo(tr);
 
         that.select = $('<select/>').appendTo(td);
         for (var i=0; i<IPA.cert.CRL_REASON.length; i++) {
@@ -272,6 +268,7 @@ IPA.cert.view_dialog = function(spec) {
     spec = spec || {};
 
     var that = IPA.dialog(spec);
+    IPA.table_mixin().apply(that);
 
     that.width = spec.width || 600;
     that.height = spec.height || 500;
@@ -284,6 +281,7 @@ IPA.cert.view_dialog = function(spec) {
     that.expires_on = spec.certificate.valid_not_after || '';
     that.md5_fingerprint = spec.certificate.md5_fingerprint || '';
     that.sha1_fingerprint = spec.certificate.sha1_fingerprint || '';
+    that.sha256_fingerprint = spec.certificate.sha256_fingerprint || '';
 
     that.create_button({
         name: 'close',
@@ -295,103 +293,91 @@ IPA.cert.view_dialog = function(spec) {
 
     that.create_content = function() {
 
-        var table = $('<table/>').appendTo(that.container);
-
-        var tr = $('<tr/>').appendTo(table);
-        $('<td/>', {
-            'colspan': 2,
-            'html': '<h3>'+text.get('@i18n:objects.cert.issued_to')+'</h3>'
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.common_name')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.subject.cn
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.organization')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.subject.o
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.organizational_unit')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.subject.ou
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.serial_number')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.serial_number
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.serial_number_hex')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.serial_number_hex
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td/>', {
-            'colspan': 2,
-            'html': '<h3>'+text.get('@i18n:objects.cert.issued_by')+'</h3>'
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.common_name')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.issuer.cn
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.organization')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.issuer.o
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.organizational_unit')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.issuer.ou
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td/>', {
-            'colspan': 2,
-            'html': '<h3>'+text.get('@i18n:objects.cert.validity')+'</h3>'
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.issued_on')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.issued_on
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.expires_on')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.expires_on
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td/>', {
-            'colspan': 2,
-            'html': '<h3>'+text.get('@i18n:objects.cert.fingerprints')+'</h3>'
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.sha1_fingerprint')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.sha1_fingerprint
-        }).appendTo(tr);
-
-        tr = $('<tr/>').appendTo(table);
-        $('<td>'+text.get('@i18n:objects.cert.md5_fingerprint')+':</td>').appendTo(tr);
-        $('<td/>', {
-            text: that.md5_fingerprint
-        }).appendTo(tr);
+        that.create_title('@i18n:objects.cert.issued_to')
+            .appendTo(that.container);
+
+        var table_layout = that.create_layout().appendTo(that.container);
+
+        var row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.common_name', ':'));
+        row.append(that.create_cell(that.subject.cn, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.organization', ':'));
+        row.append(that.create_cell(that.subject.o, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.organizational_unit', ':'));
+        row.append(that.create_cell(that.subject.ou, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.serial_number', ':'));
+        row.append(that
+            .create_cell(that.serial_number.toString(), '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.serial_number_hex', ':'));
+        row.append(that.create_cell(that.serial_number_hex, '', 'break-words'));
+
+        that.create_title('@i18n:objects.cert.issued_by')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.common_name', ':'));
+        row.append(that.create_cell(that.issuer.cn, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.organization', ':'));
+        row.append(that.create_cell(that.issuer.o, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.organizational_unit', ':'));
+        row.append(that.create_cell(that.issuer.ou, '', 'break-words'));
+
+        that.create_title('@i18n:objects.cert.validity')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.issued_on', ':'));
+        row.append(that.create_cell(that.issued_on, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.expires_on', ':'));
+        row.append(that.create_cell(that.expires_on, '', 'break-words'));
+
+        that.create_title('@i18n:objects.cert.fingerprints')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.md5_fingerprint', ':'));
+        row.append(that.create_cell(that.md5_fingerprint, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.sha1_fingerprint', ':'));
+        row.append(that.create_cell(that.sha1_fingerprint, '', 'break-words'));
+
+        row = that.create_row().appendTo(table_layout);
+        row.append(that
+            .create_header_cell('@i18n:objects.cert.sha256_fingerprint', ':'));
+        row.append(that.create_cell(that.sha256_fingerprint, '', 'break-words'));
     };
 
     return that;
diff --git a/install/ui/src/freeipa/dialog.js b/install/ui/src/freeipa/dialog.js
index 3d156ce10356d5f4f9ab506dfc924a01fd354ea3..2988ac388858cc65444c4db0a260cf002fcb65dd 100644
--- a/install/ui/src/freeipa/dialog.js
+++ b/install/ui/src/freeipa/dialog.js
@@ -1440,6 +1440,110 @@ IPA.confirm_dialog = function(spec) {
     return that;
 };
 
+
+/**
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+IPA.table_mixin = function() {
+
+    return {
+        mixin: {
+            /**
+             * Create title above a table.
+             *
+             * @param {string} cls css class which will be added to current title
+             */
+            create_title: function(str, cls) {
+                cls = cls || '';
+
+                return $('<h3 />', {
+                    'class': cls,
+                    text: text.get(str)
+                });
+            },
+
+            /**
+             * Create table layout..
+             *
+             * @param {string} cls css class which will be added to current table layout
+             */
+            create_layout: function(cls) {
+                cls = cls || '';
+
+                return $('<div />', {
+                    'class': 'table-layout ' + cls
+                });
+            },
+
+            /**
+             * Create one row to the table layout.
+             *
+             * @param {string} cls css class which will be added to current row
+             */
+            create_row: function(cls) {
+                cls = cls || '';
+
+                return $('<div />', {
+                    'class': 'table-row ' + cls
+                });
+
+            },
+
+            /**
+             * Create one cell to the table layout.
+             *
+             * @param {string} string, will be parsed using our provider
+             * @param {string} suffix, string which will be concatenated to the end of
+             *                  'str' string. Not parsed using text.get()
+             * @param {string} cls css class which will be added to current cell
+             */
+            create_cell: function(str, suffix, cls) {
+                str = str || '';
+                suffix = suffix || '';
+                cls = cls || '';
+
+                return $('<div />', {
+                    'class': 'table-cell ' + cls,
+                    text: text.get(str) + suffix
+                });
+
+            },
+
+            /**
+             * Create header cell to the table layout.
+             *
+             * @param {string} string, will be parsed using our provider
+             * @param {string} suffix, string which will be concatenated to the end of
+             *                  'str' string. Not parsed using text.get()
+             * @param {string} cls css class which will be added to current cell
+             */
+            create_header_cell: function(str, suffix, cls) {
+                str = str || '';
+                suffix = suffix || '';
+                cls = cls || '';
+
+                return $('<div />', {
+                    'class': 'table-cell table-head' + cls,
+                    text: text.get(str) + suffix
+                });
+
+            }
+
+        },
+
+        apply: function(obj) {
+            $.extend(obj, this.mixin);
+        }
+    };
+};
+
+
+
 /**
  * General form dialog with confirmation feature
  * @class  dialog.form_dialog
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 6fd75b826cdd37cbaecb9a458985e9ea4cfedfce..d4a63313c640796f8654b32378a5591c3799382f 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -289,6 +289,7 @@
                             "serial_number": "Serial Number",
                             "serial_number_hex": "Serial Number (hex)",
                             "sha1_fingerprint": "SHA1 Fingerprint",
+                            "sha256_fingerprint": "SHA256 Fingerprint",
                             "status": "Status",
                             "superseded": "Superseded",
                             "unspecified": "Unspecified",
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index 51b220598e24be7f027cb2659d3bedcf2c268203..b6b9920c1310e7b9d8d2c8df08d1aeece885059f 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -426,6 +426,7 @@ class i18n_messages(Command):
                 "serial_number": _("Serial Number"),
                 "serial_number_hex": _("Serial Number (hex)"),
                 "sha1_fingerprint": _("SHA1 Fingerprint"),
+                "sha256_fingerprint": _("SHA256 Fingerprint"),
                 "status": _("Status"),
                 "superseded": _("Superseded"),
                 "unspecified": _("Unspecified"),
-- 
2.5.5

From cd7329174adfe66766c0b8198cb475df1625dfcf Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 11:13:06 +0200
Subject: [PATCH 07/14] Changed the way how to handle remove hold and revoke
 actions

Method calling in actions is moved to another function - these calls may be used
by another functions, not only by actions.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/certificate.js | 57 ++++++++++++++++++++++++-----------
 install/ui/test/data/ipa_init.json    |  1 -
 ipaserver/plugins/internal.py         |  1 -
 3 files changed, 40 insertions(+), 19 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index e804f5126372604c47cee329a40ffb040b4efd35..bd0143b87b811dcfafbf9602965f672b90f9046c 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -811,6 +811,25 @@ IPA.cert.request_action = function(spec) {
     return that;
 };
 
+IPA.cert.perform_revoke = function(spec, sn, revocation_reason) {
+
+    spec.hide_activity_icon = spec.hide_activity_icon || false;
+
+    rpc.command({
+        entity: 'cert',
+        method: 'revoke',
+        hide_activity_icon: spec.hide_activity_icon,
+        args: [ sn ],
+        options: {
+            'revocation_reason': revocation_reason
+        },
+        notify_activity_start: spec.notify_activity_start,
+        notify_activity_end: spec.notify_activity_end,
+        on_success: spec.on_success,
+        on_error: spec.on_error
+    }).execute();
+};
+
 IPA.cert.revoke_action = function(spec) {
 
     spec = spec || {};
@@ -846,21 +865,17 @@ IPA.cert.revoke_action = function(spec) {
 
     that.execute_action = function(facet) {
 
-        var certificate = facet.certificate;
-
-        rpc.command({
-            entity: 'cert',
-            method: 'revoke',
-            args: [certificate.serial_number],
-            options: {
-                'revocation_reason': that.dialog.get_reason()
-            },
+        var spec = {
             on_success: function(data, text_status, xhr) {
                 facet.refresh();
                 IPA.notify_success('@i18n:objects.cert.revoked');
                 facet.certificate_updated.notify([], that.facet);
             }
-        }).execute();
+        };
+
+        var sn = facet.certificate.serial_number;
+        var revocation_reason = that.dialog.get_reason();
+        IPA.cert.perform_revoke(spec, sn, revocation_reason);
     };
 
     return that;
@@ -905,23 +920,31 @@ IPA.cert.remove_hold_action = function(spec) {
 
     that.execute_action = function(facet) {
 
-        var certificate = facet.certificate;
-
-        rpc.command({
-            entity: 'cert',
-            method: 'remove_hold',
-            args: [certificate.serial_number],
+        var spec = {
             on_success: function(data, text_status, xhr) {
                 facet.refresh();
                 IPA.notify_success('@i18n:objects.cert.hold_removed');
                 facet.certificate_updated.notify([], that.facet);
             }
-        }).execute();
+        };
+
+        IPA.cert.perform_remove_hold(spec, facet.certificate.serial_number);
+
     };
 
     return that;
 };
 
+IPA.cert.perform_remove_hold = function(spec, sn) {
+
+    rpc.command({
+        entity: 'cert',
+        method: 'remove_hold',
+        args: [sn],
+        on_success: spec.on_success
+    }).execute();
+};
+
 IPA.cert.certificate_evaluator = function(spec) {
 
     spec.name = spec.name || 'has_certificate_evaluator';
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index d4a63313c640796f8654b32378a5591c3799382f..8a2e2010a23e3d70dfdae97393a2212c8e9c7b7b 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -83,7 +83,6 @@
                         "remove_hold": "Remove hold",
                         "reset": "Reset",
                         "reset_password_and_login": "Reset Password and Login",
-                        "restore": "Restore",
                         "retry": "Retry",
                         "revert": "Revert",
                         "revert_title": "Undo all unsaved changes.",
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index b6b9920c1310e7b9d8d2c8df08d1aeece885059f..d5fdd080f0e29dba2b7089fc04914349a53ec133 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -219,7 +219,6 @@ class i18n_messages(Command):
             "remove_hold": _("Remove hold"),
             "reset": _("Reset"),
             "reset_password_and_login": _("Reset Password and Login"),
-            "restore": _("Restore"),
             "retry": _("Retry"),
             "revert": _("Revert"),
             "revert_title": ("Undo all unsaved changes."),
-- 
2.5.5

From eb59b49b238f61ee3f586bc54eda0f394efcef52 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:12:01 +0200
Subject: [PATCH 08/14] Remove old useless actions - get and view

These two actions are not available any more. So that code is never called.

https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/certificate.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index bd0143b87b811dcfafbf9602965f672b90f9046c..16a6b4bdf58ca0ea6d133775b1dae6e41f5cf4b0 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -1611,8 +1611,6 @@ exp.register = function() {
     f.register('revocation_reason', IPA.revocation_reason_field);
     w.register('revocation_reason', IPA.text_widget);
 
-    a.register('cert_view', IPA.cert.view_action);
-    a.register('cert_get', IPA.cert.get_action);
     a.register('cert_request', IPA.cert.request_action);
     a.register('download_cert', IPA.cert.download_action);
     a.register('cert_revoke', IPA.cert.revoke_action);
-- 
2.5.5

From a333517a20aa8caf5fa5cdeebbeff314e0834895 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 26 Apr 2016 08:44:03 +0200
Subject: [PATCH 09/14] Add widget for showing multiple certificates

Certs widget is based on multivalued widget and adds ability to add new certificate
and delete it. Each line is cert_widget.

https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/certificate.js | 227 ++++++++++++++++++++++++++++------
 install/ui/src/freeipa/field.js       |  19 +++
 install/ui/test/data/ipa_init.json    |   1 +
 ipaserver/plugins/internal.py         |   1 +
 4 files changed, 212 insertions(+), 36 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index 16a6b4bdf58ca0ea6d133775b1dae6e41f5cf4b0..c8db3f4a2569679f42012506b7fa023093e17b34 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -113,6 +113,22 @@ IPA.cert.parse_dn = function(dn) {
     return result;
 };
 
+IPA.cert.get_base64 = function(text) {
+    /*
+     * Input is assumed to be base64 or PEM formatted certificate.
+     * The function just cuts the '-----BEGIN CERTIFICATE----' and
+     * '-----END CERTIFICATE-----' strings if they are present.
+     * Returns only base64 blob.
+     */
+
+    var match = IPA.cert.PEM_CERT_REGEXP.exec(text);
+
+    if (match) {
+        return $.trim(match[2]);
+    }
+    return $.trim(text);
+};
+
 IPA.cert.pem_format_base64 = function(text) {
     /*
      * Input is assumed to be base64 possibly with embedded whitespace.
@@ -1101,57 +1117,197 @@ IPA.cert.status_field = function(spec) {
     return that;
 };
 
-IPA.cert.cert_widget = function(spec) {
+
+/**
+ * Certificates widget
+ *
+ * Multivalued widget with certificate widget instead of text widget.
+ *
+ * @class
+ * @extends IPA.multivalued_widget
+ */
+IPA.cert.certs_widget = function(spec) {
 
     spec = spec || {};
-    spec.css_class = spec.css_class || 'certificate-widget';
+    spec.child_spec = {
+        $factory: IPA.cert.cert_widget,
+        css_class: 'certificate-widget',
+        facet: spec.facet
+    };
 
-    var that = IPA.input_widget(spec);
-    that.certs_visible = false;
+    spec.custom_actions = spec.custom_actions === undefined ? true :
+        spec.custom_actions;
 
-    that.create = function(container) {
+    var that = IPA.multivalued_widget(spec);
 
-        that.widget_create(container);
-        that.content_el = $('<div>').appendTo(container);
+    that.new_row = function() {
+        that.open_addcert_dialog();
     };
 
-    that.create_status = function(name, text, icon) {
+    that.remove_row = function(row) {
 
-        var status = $('<label/>', {
-            'class': 'certificate-status'
-        });
+        var perform_remove = function() {
+            that.perform_remove(row);
+        };
 
-        $('<i/>', {
-            'class': icon
-        }).appendTo(status);
+        var sn = row.widget.certificate.serial_number;
+        var title = row.widget.compose_dialog_title();
+        var message = text.get('@i18n:actions.delete_confirm');
+        message = message.replace('${object}',
+            text.get('@i18n:objects.cert.delete_cert_end') + sn);
 
-        status.append(" " + text);
+        var spec = {
+            title: title,
+            message: message,
+            on_ok: perform_remove
+        };
 
-        return status;
+        that.create_confirmation_dialog(spec);
     };
 
-    that.create_certs = function() {
+    that.create_confirmation_dialog = function(spec) {
+        spec = spec || {};
+        var confirm_dialog = IPA.confirm_dialog(spec);
+
+        if (that.facet.is_dirty()) {
+            var dialog = IPA.dirty_dialog({
+                facet: that.facet
+            });
+
+            dialog.callback = function() {
+                confirm_dialog.open();
+            };
+            dialog.open();
+
+        } else {
+            confirm_dialog.open();
+        }
+    };
 
-        that.content_el.empty();
-        var l = that.certificates.length;
+    that.perform_remove = function(row) {
+        var facet = that.facet;
+        var pkey = facet.get_pkey();
+        var blob = row.widget.save();
 
-        if (l && that.certs_visible) {
-            for (var i=0; i<l; i++) {
-                $('<div/>', {
-                    'class': 'certificate',
-                    text: that.certificates[i]
-                }).appendTo(that.content_el);
+        var command = rpc.command({
+            entity: facet.entity.name,
+            method: 'remove_cert',
+            args: [pkey],
+            options: {
+                usercertificate: blob
+            },
+            on_success: function(data) {
+                facet.refresh();
+                facet.certificate_updated.notify();
+                IPA.notify_success(data.result.summary);
+            },
+            on_error: function(data) {
+                IPA.notify(data.result.summary, 'error');
             }
-            $('<div/>').append(
-                IPA.button({
-                    name: 'hide',
-                    label: '@i18n:buttons.hide',
-                    click: function() {
-                        that.certs_visible = false;
-                        that.create_certs();
+        });
+
+        command.execute();
+    };
+
+    that.add = function(dialog) {
+        var value = dialog.get_field('new_cert').get_value();
+        value = IPA.cert.get_base64(value);
+
+        var facet = that.facet;
+        var pkey = facet.get_pkey();
+
+        var command = rpc.command({
+            entity: facet.entity.name,
+            method: 'add_cert',
+            args: [pkey],
+            options: {
+                usercertificate: value
+            },
+            on_success: function(data) {
+                facet.refresh();
+                facet.certificate_updated.notify();
+                IPA.notify_success(data.result.summary);
+                dialog.close();
+            },
+            on_error: function(data) {
+                dialog.focus_first_element();
+            }
+        });
+
+        command.execute();
+    };
+
+    that.open_addcert_dialog = function() {
+        var add_dialog = that.create_addcert_dialog();
+
+        if (that.facet.is_dirty()) {
+            var dialog = IPA.dirty_dialog({
+                facet: that.facet
+            });
+
+            dialog.callback = function() {
+                add_dialog.open();
+            };
+            dialog.open();
+
+        } else {
+            add_dialog.open();
+        }
+
+    };
+
+    that.create_addcert_dialog = function() {
+        var dialog = IPA.dialog({
+            name: 'cert-add-dialog',
+            title: '@i18n:objects.cert.new_certificate',
+            sections: [
+                {
+                    show_header: false,
+                    fields: [
+                        {
+                            $type: 'textarea',
+                            name: 'new_cert',
+                            label: '@i18n:objects.cert.new_cert_format',
+                            required: true,
+                            rows: 15
+                        }
+                    ],
+                    layout:
+                    {
+                        $factory: widget_mod.fluid_layout,
+                        widget_cls: 'col-sm-12',
+                        label_cls: 'col-sm-6 control-label'
                     }
-                })).
-            appendTo(that.content_el);
+                }
+            ]
+        });
+
+        dialog.create_button({
+            name: 'add',
+            label: '@i18n:buttons.add',
+            click: function() {
+                if (!dialog.validate()) {
+                    widget_mod.focus_invalid(dialog);
+                }
+                else {
+                    that.add(dialog);
+                }
+            }
+        });
+
+        dialog.create_button({
+            name: 'cancel',
+            label: '@i18n:buttons.cancel',
+            click: function() {
+                dialog.close();
+            }
+        });
+
+        return dialog;
+    };
+
+    return that;
+};
         }
 
         if (!l) {
@@ -1604,10 +1760,9 @@ exp.register = function() {
     var f = reg.field;
     var a = reg.action;
 
-    w.register('certificate', IPA.cert.cert_widget);
+    w.register('certs', IPA.cert.certs_widget);
     w.register('certificate_status', IPA.cert.status_widget);
     f.register('certificate_status', IPA.cert.status_field);
-
     f.register('revocation_reason', IPA.revocation_reason_field);
     w.register('revocation_reason', IPA.text_widget);
 
diff --git a/install/ui/src/freeipa/field.js b/install/ui/src/freeipa/field.js
index 3f7e1d1b48efdbf60c6472458f6b4622cbea5232..f94bcc30699201793d80f0347c7f960438db46c4 100644
--- a/install/ui/src/freeipa/field.js
+++ b/install/ui/src/freeipa/field.js
@@ -1268,6 +1268,24 @@ field.SshKeysAdapter = declare([field.Adapter], {
 
 
 /**
+ * Used along with cert widget
+ *
+ * @class
+ * @alternateClassName IPA.certs_field
+ * @extends IPA.field
+ */
+field.certs_field = IPA.certs_field = function(spec) {
+
+    spec = spec || {};
+
+    spec.adapter = spec.field_adapter || {};
+
+    var that = IPA.field(spec);
+
+    return that;
+};
+
+/**
  * ObjectAdapter is basic adapter which converts object to more usable format.
  * All properties which have only one value are tranformed this way:
  *  property1: {"__base64__": "value1"} => property1: "value1",
@@ -1553,6 +1571,7 @@ field.register = function() {
     var v = reg.validator;
     var l = reg.adapter;
 
+    f.register('certs', field.certs_field);
     f.register('checkbox', field.checkbox_field);
     f.register('checkboxes', field.field);
     f.register('combobox', field.field);
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 8a2e2010a23e3d70dfdae97393a2212c8e9c7b7b..95cfe3e5e7ebabd3d62b62b06a7bb22004313a54 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -241,6 +241,7 @@
                             "cessation_of_operation": "Cessation of Operation",
                             "common_name": "Common Name",
                             "download": "Download",
+                            "delete_cert_end": "the certificate with serial number ",
                             "expires_on": "Expires On",
                             "fingerprints": "Fingerprints",
                             "find_issuedon_from": "Issued on from",
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index d5fdd080f0e29dba2b7089fc04914349a53ec133..a60b853e957cfb9629edad68e82d60f495e6872c 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -378,6 +378,7 @@ class i18n_messages(Command):
                 "cessation_of_operation": _("Cessation of Operation"),
                 "common_name": _("Common Name"),
                 "download": _("Download"),
+                "delete_cert_end": _("the certificate with serial number "),
                 "expires_on": _("Expires On"),
                 "find_issuedon_from": _("Issued on from"),
                 "find_issuedon_to": _("Issued on to"),
-- 
2.5.5

From ddd41ccbcfef41a25abb795e25fd55de732f69cf Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 26 Apr 2016 12:28:45 +0200
Subject: [PATCH 10/14] Add certificate widget

The certificate widget is used for each certificate in certs_widget. It allows to
view, get, download, revoke and restore certificate.

https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/less/widgets.less          |  18 ++
 install/ui/src/freeipa/certificate.js | 317 ++++++++++++++++++++++++++++++----
 install/ui/test/data/ipa_init.json    |   6 +
 ipaserver/plugins/internal.py         |   6 +
 4 files changed, 318 insertions(+), 29 deletions(-)

diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index b68161b65ef856d731778ef97044b6cdfeaca4ca..7345ebc96050413d120dd6c2d138089a8d7e90ed 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -134,13 +134,31 @@
 // Certificate Widget
 
 .certificate-widget {
+    padding: 5px 5px 5px 5px;
+    border: 1px dashed #DDD;
     label {
         padding-right: 10px;
     }
+    .cert-value {
+        padding-left: 5px;
+    }
     .certificate {
         word-wrap: break-word;
         padding-bottom: 10px;
     }
+    .dropdown {
+        float:right;
+    }
+    ul.dropdown-menu {
+        min-width: 100px;
+    }
+    .watermark {
+        position: absolute;
+        bottom: 0;
+        right: 0;
+        opacity:0.5;
+        font-size: 150%;
+    }
 }
 
 // Working widget
diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index c8db3f4a2569679f42012506b7fa023093e17b34..b89900c1a05834cc7804f6df422a9f2f1c96c20e 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -21,7 +21,9 @@
 
 define([
     'dojo/_base/lang',
+    'dojo/on',
     './builder',
+    './datetime',
     './metadata',
     './ipa',
     './jquery',
@@ -31,10 +33,11 @@ define([
     './rpc',
     './text',
     './widget',
+    './widgets/DropdownWidget',
     './dialog'],
     function(
-        lang, builder, metadata_provider, IPA, $, menu,
-        phases, reg, rpc, text, widget_mod) {
+        lang, on, builder, datetime, metadata_provider, IPA, $, menu,
+        phases, reg, rpc, text, widget_mod, DropdownWidget) {
 
 var exp = IPA.cert = {};
 
@@ -1308,40 +1311,296 @@ IPA.cert.certs_widget = function(spec) {
 
     return that;
 };
-        }
-
-        if (!l) {
-            that.content_el.append(that.create_status(
-                'missing',
-                text.get('@i18n:objects.cert.missing'),
-                'fa fa-warning'));
-        }
-
-        if (l && !that.certs_visible) {
-
-            var msg = text.get('@i18n:objects.cert.present');
-            msg = msg.replace('${count}', l);
-            that.content_el.append(
-                that.create_status('present', msg, 'fa fa-check'));
-
-            IPA.button({
-                name: 'show',
-                label: '@i18n:buttons.show',
-                click: function() {
-                    that.certs_visible = true;
-                    that.create_certs();
+
+/**
+ * certificate widget
+ *
+ * @class
+ * @extends IPA.input_widget
+ */
+IPA.cert.cert_widget = function(spec) {
+
+    spec = spec || {};
+
+    var that = IPA.input_widget(spec);
+    IPA.table_mixin().apply(that);
+
+    that.certificate = null;
+    that.ipa_issuer_subjbase = IPA.server_config.ipacertificatesubjectbase[0];
+    that.ipa_issuer_commonname = 'Certificate Authority';
+
+    that.create = function(container) {
+
+        that.widget_create(container);
+
+        that.container = container;
+        that.container.addClass('cert-container col-sm-12');
+
+        var spinner_spec = {
+            name: 'working-notification'
+        };
+
+        that.spinner = IPA.working_widget(spinner_spec);
+        that.spinner.create(that.container);
+
+        that.cert_subject = $('<div />', {
+            style: 'font-weight: bold;',
+            text: ''
+        }).appendTo(that.container);
+
+        that.table_layout = that.create_layout().appendTo(that.container);
+
+        var tr = that.create_row().appendTo(that.table_layout);
+        that.create_header_cell('@i18n:objects.cert.serial_number', ':')
+            .appendTo(tr);
+        that.cert_sn = that.create_cell('', '', 'cert-value').appendTo(tr);
+
+        tr = that.create_row().appendTo(that.table_layout);
+        that.create_header_cell('@i18n:objects.cert.issued_by', ':')
+            .appendTo(tr);
+        that.cert_issuer = that.create_cell('', '', 'cert-value').appendTo(tr);
+
+        tr = that.create_row().appendTo(that.table_layout);
+        that.create_header_cell('@i18n:objects.cert.valid_from', ':')
+            .appendTo(tr);
+        that.cert_valid_from = that.create_cell('', '', 'cert-value')
+            .appendTo(tr);
+
+        tr = that.create_row().appendTo(that.table_layout);
+        that.create_header_cell('@i18n:objects.cert.valid_to', ':')
+            .appendTo(tr);
+        that.cert_valid_to = that.create_cell('', '', 'cert-value')
+            .appendTo(tr);
+
+        that.dropdown = builder.build(null, {
+            $ctor: DropdownWidget,
+            toggle_text: text.get('@i18n:actions.title'),
+            toggle_class: 'btn btn-default dropdown-toggle',
+            toggle_icon: 'caret',
+            right_aligned: true,
+            name: 'cert-actions',
+            'class': 'dropdown cert-actions',
+            items: [
+                {
+                    name: 'view',
+                    label: text.get('@i18n:buttons.view'),
+                    handler: that.open_view_dialog
+                },
+                {
+                    name: 'get',
+                    label: text.get('@i18n:buttons.get'),
+                    handler: that.open_get_dialog
+                },
+                {
+                    name: 'download',
+                    label: text.get('@i18n:buttons.download'),
+                    handler: that.perform_download
+                },
+                {
+                    name: 'revoke',
+                    label: text.get('@i18n:buttons.revoke'),
+                    disabled: true,
+                    handler: that.open_revoke_dialog
+                },
+                {
+                    name: 'remove_hold',
+                    label: text.get('@i18n:buttons.remove_hold'),
+                    disabled: true,
+                    handler: that.perform_remove_hold
                 }
-            }).appendTo(that.content_el);
+            ]
+        });
+
+        on(that.dropdown, 'item-click', function(item) {
+            if (!item.disabled && item.handler) {
+                item.handler();
+            }
+        });
+
+        that.container.append(that.dropdown.render());
+        that.table_layout.appendTo(that.container);
+
+        that.create_error_link(that.container);
+    };
+
+    that.get_custom_actions = function() {
+        return that.dropdown;
+    };
+
+    that.update_displayed_data = function() {
+
+        that.revoke_note = $('<div />', {
+            text: text.get('@i18n:objects.cert.revoked_status'),
+            style: 'display: none',
+            'class': 'watermark'
+        }).appendTo(that.container);
+
+        var cert = that.certificate;
+
+        if (cert) {
+            that.cert_subject.text(IPA.cert.parse_dn(cert.subject).cn);
+            that.cert_sn.text(cert.serial_number);
+            that.cert_issuer.text(IPA.cert.parse_dn(cert.issuer).cn);
+            that.cert_valid_from.text(cert.valid_not_before);
+            that.cert_valid_to.text(cert.valid_not_after);
+        }
+
+        that.handle_revocation_reason(cert.revocation_reason);
+    };
+
+    that.toggle_revoked_note = function(show) {
+        if (show) {
+            that.revoke_note.css('display', 'block');
+        }
+        else {
+            that.revoke_note.css('display', 'none');
+        }
+    };
+
+    that.handle_revocation_reason = function(reason) {
+        // Skip certificates which are not issued by ipa's CA
+        if (that.certificate.revoked === undefined) return;
+
+        var dd_menu = that.get_custom_actions();
+
+        if (reason && reason === 6) {
+            dd_menu.enable_item('remove_hold');
+            dd_menu.disable_item('revoke');
+            that.toggle_revoked_note(true);
+        }
+        else if (reason === null || reason === undefined) {
+            dd_menu.enable_item('revoke');
+            dd_menu.disable_item('remove_hold');
+        }
+        else if (typeof reason === 'number' && reason >= 0 &&
+            reason < IPA.cert.CRL_REASON.length) {
+                dd_menu.disable_item('revoke');
+                that.toggle_revoked_note(true);
         }
     };
 
     that.update = function(values) {
-        that.certificates = values;
-        that.create_certs();
+
+        var certificate = values[0];
+
+        if (!certificate ) certificate = {};
+
+        that.certificate = $.extend({}, certificate);
+
+        that.update_displayed_data();
+    };
+
+    that.save = function() {
+        return that.certificate.certificate;
+    };
+
+    that.compose_dialog_title = function() {
+        var cert = that.certificate;
+        var cn, o;
+
+        if (cert.subject) {
+            cn = IPA.cert.parse_dn(cert.subject).cn;
+            o = IPA.cert.parse_dn(cert.subject).o;
+        }
+        else {
+            cn = o = text.get('@i18n:objects.cert.unspecified');
+        }
+
+        var r = text.get('@i18n:objects.cert.view_certificate');
+        r = r.replace('${entity}', cn);
+        r = r.replace('${primary_key}', o);
+
+        return r;
     };
 
-    that.clear = function() {
-        that.content_el.empty();
+    that.open_view_dialog = function() {
+
+        var spec = {
+            title: that.compose_dialog_title(),
+            certificate: that.certificate
+        };
+
+        var dialog = IPA.cert.view_dialog(spec);
+        dialog.open();
+    };
+
+    that.open_get_dialog = function() {
+        var spec = {
+            title: that.compose_dialog_title(),
+            certificate: that.certificate.certificate
+        };
+
+        var dialog = IPA.cert.download_dialog(spec);
+        dialog.open();
+    };
+
+    that.perform_download = function() {
+        var data_uri = IPA.cert.create_data_uri(that.certificate.certificate);
+        IPA.cert.perform_download(data_uri);
+    };
+
+    that.open_revoke_dialog = function() {
+        var spec = {
+            title: that.compose_dialog_title(),
+            message: '@i18n:objects.cert.revoke_confirmation',
+            ok_label: '@i18n:buttons.revoke',
+            on_ok: function() {
+
+                var command_spec = {
+                    hide_activity_icon: true,
+                    notify_activity_end: function() {
+                        that.spinner.emit('hide-spinner');
+                    },
+                    notify_activity_start: function() {
+                        that.spinner.emit('display-spinner');
+                    },
+                    on_success: function() {
+                        var reason = parseInt(dialog.get_reason(), 10);
+                        that.handle_revocation_reason(reason);
+                        that.facet.certificate_updated.notify();
+                        IPA.notify_success('@i18n:objects.cert.revoked');
+                    }
+                };
+
+                var sn = that.certificate.serial_number;
+                var revocation_reason = dialog.get_reason();
+                IPA.cert.perform_revoke(command_spec, sn, revocation_reason);
+            }
+        };
+
+        var dialog = IPA.cert.revoke_dialog(spec);
+        dialog.open();
+    };
+
+    that.perform_remove_hold = function() {
+        var spec = {
+            title: that.compose_dialog_title(),
+            message: '@i18n:objects.cert.remove_certificate_hold_confirmation',
+            ok_label: '@i18n:buttons.remove_hold',
+            on_ok: function () {
+                var command_spec = {
+                    hide_activity_icon: true,
+                    notify_activity_end: function() {
+                        that.spinner.emit('hide-spinner');
+                    },
+                    notify_activity_start: function() {
+                        that.spinner.emit('display-spinner');
+                    },
+                    on_success: function() {
+                        that.toggle_revoked_note();
+                        that.handle_revocation_reason();
+                        that.facet.certificate_updated.notify();
+                        IPA.notify_success('@i18n:objects.cert.hold_removed');
+                    }
+                };
+
+                var sn =  that.certificate.serial_number;
+                IPA.cert.perform_remove_hold(command_spec, sn);
+            }
+        };
+
+        var dialog = IPA.confirm_dialog(spec);
+        dialog.open();
     };
 
     return that;
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 95cfe3e5e7ebabd3d62b62b06a7bb22004313a54..e1e3512c9e2746f553e3f59e8ad5f98aabee80b8 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -69,6 +69,8 @@
                         "cancel": "Cancel",
                         "close": "Close",
                         "disable": "Disable",
+                        "download": "Download",
+                        "download_title": "Download certificate as PEM formatted file.",
                         "edit": "Edit",
                         "enable": "Enable",
                         "filter": "Filter",
@@ -267,6 +269,7 @@
                             "md5_fingerprint": "MD5 Fingerprint",
                             "missing": "No Valid Certificate",
                             "new_certificate": "New Certificate",
+                            "new_cert_format": "Certificate in base64 or PEM format",
                             "note": "Note",
                             "organization": "Organization",
                             "organizational_unit": "Organizational Unit",
@@ -286,6 +289,7 @@
                             "revoke_certificate_simple": "Revoke Certificate",
                             "revoke_confirmation": "To confirm your intention to revoke this certificate, select a reason from the pull-down list, and click the \"Revoke\" button.",
                             "revoked": "Certificate Revoked",
+                            "revoked_status": "REVOKED",
                             "serial_number": "Serial Number",
                             "serial_number_hex": "Serial Number (hex)",
                             "sha1_fingerprint": "SHA1 Fingerprint",
@@ -294,6 +298,8 @@
                             "superseded": "Superseded",
                             "unspecified": "Unspecified",
                             "valid": "Valid Certificate Present",
+                            "valid_from": "Valid from",
+                            "valid_to": "Valid to",
                             "validity": "Validity",
                             "view_certificate": "Certificate for ${entity} ${primary_key}",
                             "view_certificate_btn": "View Certificate"
diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index a60b853e957cfb9629edad68e82d60f495e6872c..1d69de7ddcb926145c2ddf6f6adda35f269eea8e 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -205,6 +205,8 @@ class i18n_messages(Command):
             "cancel": _("Cancel"),
             "close": _("Close"),
             "disable": _("Disable"),
+            "download": _("Download"),
+            "download_title": _("Download certificate as PEM formatted file."),
             "edit": _("Edit"),
             "enable": _("Enable"),
             "filter": _("Filter"),
@@ -404,6 +406,7 @@ class i18n_messages(Command):
                 "md5_fingerprint": _("MD5 Fingerprint"),
                 "missing": _("No Valid Certificate"),
                 "new_certificate": _("New Certificate"),
+                "new_cert_format": _("Certificate in base64 or PEM format"),
                 "note": _("Note"),
                 "organization": _("Organization"),
                 "organizational_unit": _("Organizational Unit"),
@@ -423,6 +426,7 @@ class i18n_messages(Command):
                 "revoke_certificate_simple": _("Revoke Certificate"),
                 "revoke_confirmation": _("To confirm your intention to revoke this certificate, select a reason from the pull-down list, and click the \"Revoke\" button."),
                 "revoked": _("Certificate Revoked"),
+                "revoked_status": _("REVOKED"),
                 "serial_number": _("Serial Number"),
                 "serial_number_hex": _("Serial Number (hex)"),
                 "sha1_fingerprint": _("SHA1 Fingerprint"),
@@ -431,6 +435,8 @@ class i18n_messages(Command):
                 "superseded": _("Superseded"),
                 "unspecified": _("Unspecified"),
                 "valid": _("Valid Certificate Present"),
+                "valid_from": _("Valid from"),
+                "valid_to": _("Valid to"),
                 "validity": _("Validity"),
                 "view_certificate": _("Certificate for ${entity} ${primary_key}"),
                 "view_certificate_btn": _("View Certificate"),
-- 
2.5.5

From e9810be7e292476772eb2740037171ecb1125396 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:57:39 +0200
Subject: [PATCH 11/14] Add new certificates widget to the user details page

https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/user.js | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
index 49c7ff4d45aab1749f2dcf88d5b97ef28e935857..0f21e73bde13d013efe952358757886d8f8aaddf 100644
--- a/install/ui/src/freeipa/user.js
+++ b/install/ui/src/freeipa/user.js
@@ -73,6 +73,18 @@ return {
             source_facet: 'details',
             dest_entity: 'stageuser',
             dest_facet: 'search'
+        },
+        {
+            $factory: IPA.cert.cert_update_policy,
+            source_facet: 'details',
+            dest_entity: 'cert',
+            dest_facet: 'search'
+        },
+        {
+            $factory: IPA.cert.cert_update_policy,
+            source_facet: 'details',
+            dest_entity: 'cert',
+            dest_facet: 'details'
         }
     ],
     facets: [
@@ -188,8 +200,12 @@ return {
                             label: '@i18n:objects.sshkeystore.keys'
                         },
                         {
-                            $type: 'certificate',
-                            name: 'usercertificate'
+                            $type: 'certs',
+                            field_adapter: {
+                                $type: 'object_adapter',
+                                result_index: 3
+                            },
+                            label: '@i18n:objects.cert.certificates'
                         },
                         {
                             $type: 'checkboxes',
@@ -563,9 +579,22 @@ IPA.user.details_facet = function(spec, no_init) {
 
         batch.add_command(krbtpolicy_command);
 
+        var certificates = rpc.command({
+            entity: 'cert',
+            method: 'find',
+            retry: false,
+            options: {
+                user: [ pkey ],
+                all: true
+            }
+        });
+
+        batch.add_command(certificates);
+
         return batch;
     };
 
+
     if (!no_init) that.init_details_facet();
 
     return that;
-- 
2.5.5

From 5968f8759695429fc44812ac032532079b909f2a Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:58:22 +0200
Subject: [PATCH 12/14] Add new certificates widget to the host details page.
 Also extends evaluator and add support for adapters.

https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/details.js | 15 ++++++-----
 install/ui/src/freeipa/host.js    | 57 ++++++++++++++++++++++++++++++++++-----
 2 files changed, 60 insertions(+), 12 deletions(-)

diff --git a/install/ui/src/freeipa/details.js b/install/ui/src/freeipa/details.js
index 080df1e7d1606053e23dd852506af063858cca26..c631258e1a79622f49e08abd97a6c1a1774be641 100644
--- a/install/ui/src/freeipa/details.js
+++ b/install/ui/src/freeipa/details.js
@@ -1671,22 +1671,25 @@ exp.value_state_evaluator = IPA.value_state_evaluator = function(spec) {
      */
     that.representation = spec.representation;
 
+    that.adapter = builder.build('adapter',
+                    spec.adapter, { context: that });
+
+    that.param = spec.param;
+
     /**
      * @inheritDoc
      */
     that.on_event = function(data) {
 
-        var old_state, record, state, value, loaded_value;
+        var old_state, value, loaded_value;
 
         old_state = that.state;
-        record = data.result.result;
         value = that.normalize_value(that.value);
-        loaded_value = record[that.attribute];
-        loaded_value = that.normalize_value(loaded_value);
-
         that.state = [];
 
-        if (!IPA.array_diff(value, loaded_value)) {
+        loaded_value = that.adapter.load(data, spec.attribute);
+
+        if(!IPA.array_diff(value, loaded_value)) {
             that.state.push(that.get_state_text());
         }
 
diff --git a/install/ui/src/freeipa/host.js b/install/ui/src/freeipa/host.js
index 80e3db65844759cb195c56fe0b48a95bba78bd00..0f7eefa0d11584b83fd9ef414881ea18d5c08a03 100644
--- a/install/ui/src/freeipa/host.js
+++ b/install/ui/src/freeipa/host.js
@@ -19,7 +19,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-define(['./ipa',
+define(['./builder',
+        './ipa',
         './jquery',
         './phases',
         './reg',
@@ -30,7 +31,7 @@ define(['./ipa',
         './association',
         './entity',
         './certificate'],
-    function(IPA, $, phases, reg, rpc, text) {
+    function(builder, IPA, $, phases, reg, rpc, text) {
 
 var exp = IPA.host = {};
 
@@ -140,8 +141,12 @@ return {
                     name: 'certificate',
                     fields: [
                         {
-                            $type: 'certificate',
-                            name: 'usercertificate'
+                            $type: 'certs',
+                            field_adapter: {
+                                $type: 'object_adapter',
+                                result_index: 1
+                            },
+                            label: '@i18n:objects.cert.certificates'
                         }
                     ]
                 },
@@ -334,8 +339,16 @@ return {
                 'request_cert'],
             state: {
                 evaluators: [
-                    IPA.host.has_password_evaluator,
-                    IPA.host.has_keytab_evaluator,
+                    {
+                        $factory: IPA.host.has_password_evaluator,
+                        param: 'has_password',
+                        adapter: { $type: 'batch', result_index: 0 }
+                    },
+                    {
+                        $factory: IPA.host.has_keytab_evaluator,
+                        param: 'has_keytab',
+                        adapter: { $type: 'batch', result_index: 0 }
+                    },
                     IPA.host.userpassword_acl_evaluator,
                     IPA.host.krbprincipalkey_acl_evaluator,
                     IPA.cert.certificate_evaluator
@@ -433,6 +446,32 @@ IPA.host.details_facet = function(spec, no_init) {
     that.certificate_loaded = IPA.observer();
     that.certificate_updated = IPA.observer();
 
+    that.create_refresh_command = function() {
+        var pkey = that.get_pkey();
+
+        var batch = rpc.batch_command({
+            name: 'host_details_refresh'
+        });
+
+        var host_command = that.details_facet_create_refresh_command();
+        batch.add_command(host_command);
+
+        var certificates = rpc.command({
+            entity: 'cert',
+            method: 'find',
+            retry: false,
+            options: {
+                host: [ pkey ],
+                all: true
+            }
+        });
+
+        batch.add_command(certificates);
+
+        return batch;
+
+    };
+
     that.get_refresh_command_name = function() {
         return that.entity.name+'_show_'+that.get_pkey();
     };
@@ -925,8 +964,11 @@ IPA.host.has_keytab_evaluator = function(spec) {
     spec.attribute = spec.attribute || 'has_keytab';
     spec.value = spec.value || [true];
     spec.representation = spec.representation || 'has_keytab';
+    spec.param = spec.param || 'has_keytab';
+    spec.adapter = spec.adapter || { $type: 'adapter' };
 
     var that = IPA.value_state_evaluator(spec);
+
     return that;
 };
 
@@ -1007,8 +1049,11 @@ IPA.host.has_password_evaluator = function(spec) {
     spec.attribute = spec.attribute || 'has_password';
     spec.value = spec.value || [true];
     spec.representation = spec.representation || 'has_password';
+    spec.param = spec.param || 'has_password';
+    spec.adapter = spec.adapter || { $type: 'adapter' };
 
     var that = IPA.value_state_evaluator(spec);
+
     return that;
 };
 
-- 
2.5.5

From 65eeab4a489ecd18f9ebab1a903acd43277193ed Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:59:30 +0200
Subject: [PATCH 13/14] Add new certificates widget to the service details page

https://fedorahosted.org/freeipa/ticket/5108
https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/service.js | 45 +++++++++++++++++++++++++++++++++++----
 1 file changed, 41 insertions(+), 4 deletions(-)

diff --git a/install/ui/src/freeipa/service.js b/install/ui/src/freeipa/service.js
index 6f8e4efbd7f07574e924cbdeac970b70eb0a9b0e..d2a8a02a52d2f736d2be8b598e6cc71a3315132c 100644
--- a/install/ui/src/freeipa/service.js
+++ b/install/ui/src/freeipa/service.js
@@ -21,6 +21,7 @@
 define([
     'dojo/_base/declare',
     './field',
+    './builder',
     './ipa',
     './jquery',
     './phases',
@@ -31,7 +32,7 @@ define([
     './search',
     './association',
     './entity'],
-        function(declare, field_mod, IPA, $, phases, reg, rpc, text) {
+        function(declare, field_mod, builder, IPA, $, phases, reg, rpc, text) {
 
 var exp =IPA.service = {};
 
@@ -154,8 +155,12 @@ return {
                     name: 'certificate',
                     fields: [
                         {
-                            $type: 'certificate',
-                            name: 'usercertificate'
+                            $type: 'certs',
+                            field_adapter: {
+                                $type: 'object_adapter',
+                                result_index: 1
+                            },
+                            label: '@i18n:objects.cert.certificates'
                         }
                     ]
                 },
@@ -318,7 +323,11 @@ return {
             ],
             state: {
                 evaluators: [
-                    IPA.service.has_keytab_evaluator,
+                    {
+                        $factory: IPA.host.has_keytab_evaluator,
+                        param: 'has_keytab',
+                        adapter: { $type: 'batch', result_index: 0 }
+                    },
                     IPA.service.krbprincipalkey_acl_evaluator,
                     IPA.cert.certificate_evaluator
                 ]
@@ -388,6 +397,31 @@ IPA.service.details_facet = function(spec, no_init) {
     that.certificate_loaded = IPA.observer();
     that.certificate_updated = IPA.observer();
 
+    that.create_refresh_command = function() {
+        var pkey = that.get_pkey();
+
+        var batch = rpc.batch_command({
+            name: 'services_details_refresh'
+        });
+
+        var service_command = that.details_facet_create_refresh_command();
+        batch.add_command(service_command);
+
+        var certificates = rpc.command({
+            entity: 'cert',
+            method: 'find',
+            retry: false,
+            options: {
+                service: [ pkey ],
+                all: true
+            }
+        });
+
+        batch.add_command(certificates);
+
+        return batch;
+    };
+
     if (!no_init) that.init_details_facet();
 
     return that;
@@ -606,8 +640,11 @@ IPA.service.has_keytab_evaluator = function(spec) {
     spec.attribute = spec.attribute || 'has_keytab';
     spec.value = spec.value || [true];
     spec.representation = spec.representation || 'has_keytab';
+    spec.param = spec.param || 'has_keytab';
+    spec.adapter = spec.adapter || { $type: 'adapter' };
 
     var that = IPA.value_state_evaluator(spec);
+
     return that;
 };
 
-- 
2.5.5

From 270acd3cedd8c69cbc68f0a5008c058af671d44c Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 14 Jun 2016 19:30:06 +0200
Subject: [PATCH 14/14] Updated certificates table

All certificates which are not issued by IPA CA are grey and not clickable. That's
because these certificates are not maintained by IPA CA.

Part of: https://fedorahosted.org/freeipa/ticket/5381
---
 install/ui/src/freeipa/certificate.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index b89900c1a05834cc7804f6df422a9f2f1c96c20e..2609f33cb656def5d43fdc46d8f0ef0af08bc884 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -1743,6 +1743,7 @@ return {
             disable_facet_tabs: false,
             tabs_in_sidebar: true,
             tab_label: '@i18n:tabs.cert',
+            row_enabled_attribute: 'status',
             facet_groups: [exp.facet_group],
             facet_group: 'certificates',
             pagination: false,
@@ -1932,6 +1933,11 @@ IPA.cert.search_facet = function(spec) {
         return command;
     };
 
+    that.table.setup_column = function(column, div, record) {
+        var supress_link = record.status === undefined;
+        column.setup(div, record, supress_link);
+    };
+
     // parent method only sets expired flag when filter change, it doesn't
     // expect that option can change -> set expire flag for every search
     that.find = function() {
-- 
2.5.5

-- 
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