On 06/23/2016 03:17 PM, Petr Vobornik wrote:
comments inline

On 06/20/2016 02:37 PM, Pavel Vomacka wrote:

On 06/14/2016 09:41 PM, Pavel Vomacka wrote:

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.

It calls ,render on custom actions object. But this method is not part
of the interface:
+            custom_actions.render();

IMHO it should be called internally in the widget as a result of
set_items or disable_item, enable_item call.
Fixed.
Patch 0019:

1. Shouldn't disable_item or enable_item automatically rerender the items?
Same for set and get_items. As mentioned above.

The render code in disable/enable_item should be executed only if
this.ul_node exists.

Fixed.
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 0020-2: ACK

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?
OK, if addressed later then ACK for 0021-2 which is the same as 0021

Patch 0022:

1. Leftover line:
    var sn = record[i].serial_number;
0022-2: ACK

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?
1. table mixin has prepared space for doc text but nothing is there
2. while at it(otherwise don't bother), remove empty line at the end of
create_cell, create_row, create_header_cell

3. When I see a content of create_content on certificate.js:316,
following is repeated many times, it can be changed into a reusable
function.

         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'));

    create_cert_row(header, value).appendTo(table_layout);

But I don't insist on this change.
Changed.

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.
1. buttons.restore should not be removed, it is still used on
stageuser.js:309: label: '@i18n:buttons.restore',
The label is returned back.

Patch 0025: ACK
Why the new revision newly doesn't remove IPA.cert.view_action  and
IPA.cert.get_action implementation ?
I accidentally removed it. Returned back in new revision.

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.
1. why is there a custom certs_field with:
    spec.adapter = spec.field_adapter || {};

field_adapter was supposed to be used only in section. In field IMHO,
you can use 'adapter' spec property directly. We can use
    f.register('certs', field.field);

Or am I missing something?
No, you are not. Changed from field_adapter to adapter.

0027-4: ACK

0028-3: ACK
Because of change above there is a new revision of patch 0028.


Patch 0029-3 and 0030-3:

If 0028 makes IPA.host.has_password_evaluator reusable, then it might be
good to rename it/move so that it is not called "host".

And then IPA.service.has_keytab_evaluator needs to be removed.

I guess that you meant IPA.host.has_keytab_evaluator in first sentence too. So I hope that this is fixed.


I moved big part of certs_widget code to more general
custom_command_multivalued_widget. This change will be useful in future.

Updated patches attached. It would be nice to apply patch 57 between patches 25
and 26, but it should be possible to apply it on the top.
Patch 52-2: ACK

Patch 57:  ACK

1. I wonder if `entity: that.facet.entity.name` in create_add_command
will be future-proof, but that can be improved later.
I'm not sure why it is not future-proof. We can discuss it later.


In general: I don't like the fact that cert-find commands takes at least
1.2s which makes loading of user,host,service details page 6x slower
than before. From access log, it seems it(or dogtag) does too many
unnecessary things.
Yes that's true, I'm adding jcholast to CC, I think that he should know about it.
Updated patches attached.

--
Pavel^3 Vomacka

From 12237d2ab2faf6cce62980f1aea65bb9cc66e2b7 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/16] 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 | 82 ++++++++++++++++++++++++++++++++--------
 1 file changed, 66 insertions(+), 16 deletions(-)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index 0f3e7f27bdd3f7366764c688711762cfc5f68eaf..9084ac584f4a9ab9efd604b56778afcb9dd27097 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,41 @@ 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);
         }
 
         if (row.is_new) {
@@ -1202,10 +1241,21 @@ 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');
+            }
+            else {
+                row.remove_link.show();
+            }
         } else {
-            row.remove_link.hide();
+            if (that.custom_actions) {
+                row.widget.get_custom_actions().disable_item('remove');
+            }
+            else {
+                row.remove_link.hide();
+            }
         }
+
         if (row.widget.update_input_group_state) {
             row.widget.update_input_group_state();
         }
-- 
2.5.5

From ab06e594eead750347254a3489b331983d4adaa1 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/16] 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 | 36 ++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/install/ui/src/freeipa/widgets/DropdownWidget.js b/install/ui/src/freeipa/widgets/DropdownWidget.js
index a81960545a6503240153906908cbc554e473cccb..f952f058981694305b8e1ffaaaa56c2e964c5839 100644
--- a/install/ui/src/freeipa/widgets/DropdownWidget.js
+++ b/install/ui/src/freeipa/widgets/DropdownWidget.js
@@ -142,6 +142,42 @@ define(['dojo/_base/declare',
             return this.dom_node;
         },
 
+        get_items: function() {
+            return this.items;
+        },
+
+        set_items: function(items) {
+            this.items = items;
+            if (this.ul_node) this.render();
+        },
+
+        disable_item: function(item_name) {
+            var item = this._find_item(item_name);
+            if (item && this.ul_node) {
+                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 && this.ul_node) {
+                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 7731dbab5657d38e8f58bb0e218b2f47f6d2ca7b 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/16] 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 | 181 +++++++++++++---------------------
 install/ui/src/freeipa/dialog.js      | 100 +++++++++++++++++++
 install/ui/test/data/ipa_init.json    |   1 +
 ipaserver/plugins/internal.py         |   1 +
 5 files changed, 192 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 7e7a3baba64d9f342651e5083e112c7d9482f6f9..65c3da7232456745ceff2fc5db484c0e57cd768b 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,64 @@ 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);
+        var new_row = function(title, value) {
+            var row = that.create_row();
+            row.append(that
+                .create_header_cell(title, ':'));
+            row.append(that.create_cell(value, '', 'break-words'));
+
+            return row;
+        };
+
+        that.create_title('@i18n:objects.cert.issued_to')
+            .appendTo(that.container);
+
+        var table_layout = that.create_layout().appendTo(that.container);
+
+        new_row('@i18n:objects.cert.common_name', that.subject.cn)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.organization', that.subject.o)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.organizational_unit', that.subject.ou)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.serial_number',
+            that.serial_number.toString()).appendTo(table_layout);
+        new_row('@i18n:objects.cert.serial_number_hex', that.serial_number_hex)
+            .appendTo(table_layout);
+
+        that.create_title('@i18n:objects.cert.issued_by')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        new_row('@i18n:objects.cert.common_name', that.issuer.cn)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.organization', that.issuer.o)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.organizational_unit', that.issuer.ou)
+            .appendTo(table_layout);
+
+        that.create_title('@i18n:objects.cert.validity')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        new_row('@i18n:objects.cert.issued_on', that.issued_on)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.expires_on', that.expires_on)
+            .appendTo(table_layout);
+
+        that.create_title('@i18n:objects.cert.fingerprints')
+            .appendTo(that.container);
+
+        table_layout = that.create_layout().appendTo(that.container);
+
+        new_row('@i18n:objects.cert.md5_fingerprint', that.md5_fingerprint)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.sha1_fingerprint', that.sha1_fingerprint)
+            .appendTo(table_layout);
+        new_row('@i18n:objects.cert.sha256_fingerprint', that.sha256_fingerprint)
+            .appendTo(table_layout);
     };
 
     return that;
diff --git a/install/ui/src/freeipa/dialog.js b/install/ui/src/freeipa/dialog.js
index 3d156ce10356d5f4f9ab506dfc924a01fd354ea3..1a0c9d22c85ad6820970e65c4b8fc53617960b4a 100644
--- a/install/ui/src/freeipa/dialog.js
+++ b/install/ui/src/freeipa/dialog.js
@@ -1440,6 +1440,106 @@ 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 1d7f5d883dd923dc5ad749b592e55f23e7d4771b..19fb1008e6eb6d75101fde42999614bda8bc4800 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 c0360567cbca883dda018a7a2f7f9f9536c3d118..826ecfa1129816323b08552a2fe40265e2b712e5 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 2c2ecbac04e5423093a7967a9db02af1da65b1d9 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/16] 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 ++++++++++++++++++++++++-----------
 1 file changed, 40 insertions(+), 17 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index 65c3da7232456745ceff2fc5db484c0e57cd768b..28b7756c516830a6db6c59fe9f311f1007889bb2 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -787,6 +787,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 || {};
@@ -822,21 +841,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;
@@ -881,23 +896,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';
-- 
2.5.5

From 31c273bf8c42578c9632b11e4845a069e83383b1 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/16] 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 | 68 -----------------------------------
 1 file changed, 68 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index 28b7756c516830a6db6c59fe9f311f1007889bb2..1ea4c1bac2043cc92ef01c16c70b2a4940decfa5 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -588,72 +588,6 @@ IPA.cert.is_enabled = function() {
     return !!IPA.ca_enabled;
 };
 
-IPA.cert.view_action = function(spec) {
-
-    spec = spec || {};
-    spec.name = spec.name || 'view_cert';
-    spec.label = spec.label || '@i18n:objects.cert.view_certificate_btn';
-    spec.enable_cond = spec.enable_cond || ['has_certificate'];
-
-    var that = IPA.action(spec);
-    that.entity_label = spec.entity_label;
-
-    that.execute_action = function(facet) {
-
-        var certificate = facet.certificate;
-        if (!certificate) that.facet.refresh();
-
-        var entity_label = that.entity_label || facet.entity.metadata.label_singular;
-        var entity_name = certificate.entity_info.name;
-
-        var title = text.get('@i18n:objects.cert.view_certificate');
-        title = title.replace('${entity}', entity_label);
-        title = title.replace('${primary_key}', entity_name);
-
-        var dialog = IPA.cert.view_dialog({
-            title: title,
-            certificate: certificate
-        });
-
-        dialog.open();
-    };
-
-    return that;
-};
-
-IPA.cert.get_action = function(spec) {
-
-    spec = spec || {};
-    spec.name = spec.name || 'get_cert';
-    spec.label = spec.label || '@i18n:objects.cert.get_certificate';
-    spec.enable_cond = spec.enable_cond || ['has_certificate'];
-
-    var that = IPA.action(spec);
-    that.entity_label = spec.entity_label;
-
-    that.execute_action = function(facet) {
-
-        var certificate = facet.certificate;
-        if (!certificate) that.facet.refresh();
-
-        var entity_label = that.entity_label || facet.entity.metadata.label_singular;
-        var entity_name = certificate.entity_info.name;
-
-        var title = text.get('@i18n:objects.cert.view_certificate');
-        title = title.replace('${entity}', entity_label);
-        title = title.replace('${primary_key}', entity_name);
-
-        var dialog = IPA.cert.download_dialog({
-            title: title,
-            certificate: certificate.certificate
-        });
-
-        dialog.open();
-    };
-
-    return that;
-};
-
 IPA.cert.create_data_uri = function(certificate) {
     if (typeof certificate !== 'string') return '';
 
@@ -1587,8 +1521,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 81775154ae5322271bf6e58828f076a13320054f Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Tue, 26 Apr 2016 08:44:03 +0200
Subject: [PATCH 10/16] 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 | 190 +++++++++++++++++++++-------------
 install/ui/src/freeipa/field.js       |   1 +
 install/ui/test/data/ipa_init.json    |   1 +
 ipaserver/plugins/internal.py         |   1 +
 4 files changed, 120 insertions(+), 73 deletions(-)

diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js
index 1ea4c1bac2043cc92ef01c16c70b2a4940decfa5..0aa270039800a12e17493456c739b8b458409f04 100755
--- a/install/ui/src/freeipa/certificate.js
+++ b/install/ui/src/freeipa/certificate.js
@@ -113,6 +113,25 @@ 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) {
+        match = match[2].replace(/\s*/g, '');
+        return $.trim(match);
+    }
+
+    text = text.replace(/\s*/g, '');
+    return $.trim(text);
+};
+
 IPA.cert.pem_format_base64 = function(text) {
     /*
      * Input is assumed to be base64 possibly with embedded whitespace.
@@ -547,31 +566,6 @@ IPA.cert.load_policy = function(spec) {
         //store cert directly to facet. FIXME: introduce concept of models
         that.container.certificate = certificate;
         that.notify_loaded();
-
-        // initialize another load of certificate because current entity
-        // show commands don't contain revocation_reason so previous data
-        // might be slightly incorrect
-        if (!that.has_reason && certificate && certificate.certificate &&
-                IPA.cert.is_enabled()) {
-            that.load_revocation_reason(certificate.serial_number);
-        }
-    };
-
-    that.load_revocation_reason = function(serial_number) {
-        if (serial_number === null || serial_number === undefined) return;
-
-        rpc.command({
-            entity: 'cert',
-            method: 'show',
-            args: [serial_number],
-            on_success: function(data, text_status, xhr) {
-                // copy it so consumers can notice the difference
-                that.container.certificate = lang.clone(that.container.certificate);
-                var cert = that.container.certificate;
-                cert.revocation_reason = data.result.result.revocation_reason;
-                that.notify_loaded();
-            }
-        }).execute();
     };
 
     that.notify_loaded = function() {
@@ -1011,57 +1005,108 @@ 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';
-
-    var that = IPA.input_widget(spec);
-    that.certs_visible = false;
-
-    that.create = function(container) {
-
-        that.widget_create(container);
-        that.content_el = $('<div>').appendTo(container);
+    spec.child_spec = spec.child_spec || {
+        $factory: IPA.cert.cert_widget,
+        css_class: 'certificate-widget',
+        facet: spec.facet
     };
 
-    that.create_status = function(name, text, icon) {
-
-        var status = $('<label/>', {
-            'class': 'certificate-status'
-        });
-
-        $('<i/>', {
-            'class': icon
-        }).appendTo(status);
-
-        status.append(" " + text);
-
-        return status;
-    };
-
-    that.create_certs = function() {
-
-        that.content_el.empty();
-        var l = that.certificates.length;
-
-        if (l && that.certs_visible) {
-            for (var i=0; i<l; i++) {
-                $('<div/>', {
-                    'class': 'certificate',
-                    text: that.certificates[i]
-                }).appendTo(that.content_el);
-            }
-            $('<div/>').append(
-                IPA.button({
-                    name: 'hide',
-                    label: '@i18n:buttons.hide',
-                    click: function() {
-                        that.certs_visible = false;
-                        that.create_certs();
+    spec.item_name = 'cert';
+
+    spec.custom_actions = spec.custom_actions === undefined ? true :
+        spec.custom_actions;
+
+    spec.adder_dialog_spec = {
+        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
                     }
-                })).
-            appendTo(that.content_el);
+                ],
+                layout:
+                {
+                    $factory: widget_mod.fluid_layout,
+                    widget_cls: 'col-sm-12',
+                    label_cls: 'col-sm-6 control-label'
+                }
+            }
+        ]
+    };
+
+    var that = IPA.custom_command_multivalued_widget(spec);
+
+    that.create_remove_options = function(row) {
+        var blob = row.widget.save();
+        var options = {
+            usercertificate: blob
+        };
+
+        return options;
+    };
+
+    /**
+     * Called on success of remove command. Override point.
+     */
+    that.on_success_remove = function(data, text_status, xhr) {
+        that.facet.refresh();
+        that.facet.certificate_updated.notify();
+        IPA.notify_success(data.result.summary);
+    };
+
+    that.create_add_options = function() {
+        var blob = that.adder_dialog.get_field('new_cert').get_value()[0];
+        blob = IPA.cert.get_base64(blob);
+        var options = {
+            usercertificate: blob
+        };
+
+        return options;
+    };
+
+    that.on_success_add = function(data, text_status, xhr) {
+        that.facet.refresh();
+        that.facet.certificate_updated.notify();
+        IPA.notify_success(data.result.summary);
+        that.adder_dialog.close();
+    };
+
+    that.create_remove_dialog_title = function(row) {
+        var title = row.widget.compose_dialog_title();
+
+        return title;
+    };
+
+    that.create_remove_dialog_message = function(row) {
+        var sn = row.widget.certificate.serial_number;
+        var message = text.get('@i18n:actions.delete_confirm');
+        message = message.replace('${object}',
+            text.get('@i18n:objects.cert.delete_cert_end') + sn);
+
+        return message;
+    };
+
+    return that;
+};
         }
 
         if (!l) {
@@ -1514,10 +1559,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..d8b957f5ab28b5ee4bc4ebce2ae6f454083bc4fd 100644
--- a/install/ui/src/freeipa/field.js
+++ b/install/ui/src/freeipa/field.js
@@ -1553,6 +1553,7 @@ field.register = function() {
     var v = reg.validator;
     var l = reg.adapter;
 
+    f.register('certs', field.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 19fb1008e6eb6d75101fde42999614bda8bc4800..cdb7bdbefdfca6467e6f5111c508e044a2371b40 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -242,6 +242,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 826ecfa1129816323b08552a2fe40265e2b712e5..0f61dc4887299b32e11cb14ce8e9f8742323dba6 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -379,6 +379,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 24b6146b24814e41f7a755d0ee1d520b54f2b48e Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:57:39 +0200
Subject: [PATCH 12/16] 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..d8d22ffbc67adec39a75009cd4de0ebc6500b03c 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',
+                            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 8de1ff20a77d0c0563a74d3bb87c1c4713d5befc Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:58:22 +0200
Subject: [PATCH 13/16] 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 | 37 ++++++++++++++++++----
 install/ui/src/freeipa/host.js    | 65 +++++++++++++++++++++++++++++----------
 2 files changed, 79 insertions(+), 23 deletions(-)

diff --git a/install/ui/src/freeipa/details.js b/install/ui/src/freeipa/details.js
index 080df1e7d1606053e23dd852506af063858cca26..e274e6faaaa40b36397dba30b2ddbd3a4fd347b5 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());
         }
 
@@ -1739,6 +1742,28 @@ exp.value_state_evaluator = IPA.value_state_evaluator = function(spec) {
     return that;
 };
 
+
+/**
+ * has_keytab evaluator
+ *
+ * @class details.has_keytab_evaluator
+ * @alternateClassName IPA.has_keytab_evaluator
+ * @extends facet.value_state_evaluator
+ */
+exp.has_keytab_evaluator = IPA.has_keytab_evaluator = function(spec) {
+
+    spec.name = spec.name || 'has_keytab_evaluator';
+    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;
+};
+
 /**
  * Object class evaluator
  *
diff --git a/install/ui/src/freeipa/host.js b/install/ui/src/freeipa/host.js
index 80e3db65844759cb195c56fe0b48a95bba78bd00..677da96f2c8e6840dc826a49a162c1c63d4a4f9e 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',
+                            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.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();
     };
@@ -919,17 +958,6 @@ IPA.host.krbprincipalkey_acl_evaluator = function(spec) {
     return that;
 };
 
-IPA.host.has_keytab_evaluator = function(spec) {
-
-    spec.name = spec.name || 'has_keytab_evaluator';
-    spec.attribute = spec.attribute || 'has_keytab';
-    spec.value = spec.value || [true];
-    spec.representation = spec.representation || 'has_keytab';
-
-    var that = IPA.value_state_evaluator(spec);
-    return that;
-};
-
 IPA.host_password_widget = function(spec) {
 
     spec = spec || {};
@@ -1007,8 +1035,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 3cdc402d709234a9476dc08c6805de6dde68fca1 Mon Sep 17 00:00:00 2001
From: Pavel Vomacka <pvoma...@redhat.com>
Date: Fri, 22 Apr 2016 12:59:30 +0200
Subject: [PATCH 14/16] 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 | 53 ++++++++++++++++++++++++++++-----------
 1 file changed, 38 insertions(+), 15 deletions(-)

diff --git a/install/ui/src/freeipa/service.js b/install/ui/src/freeipa/service.js
index 6f8e4efbd7f07574e924cbdeac970b70eb0a9b0e..4ef233cfe123f19f3bd526735e2d44b424c214b1 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',
+                            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.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;
@@ -600,17 +634,6 @@ IPA.service.krbprincipalkey_acl_evaluator = function(spec) {
     return that;
 };
 
-IPA.service.has_keytab_evaluator = function(spec) {
-
-    spec.name = spec.name || 'has_keytab_evaluator';
-    spec.attribute = spec.attribute || 'has_keytab';
-    spec.value = spec.value || [true];
-    spec.representation = spec.representation || 'has_keytab';
-
-    var that = IPA.value_state_evaluator(spec);
-    return that;
-};
-
 IPA.service.certificate_policy = function(spec) {
 
     spec = spec || {};
-- 
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