Blob Blame History Raw
From 0a96fde9b1d691268948091442c2f0075e81ab95 Mon Sep 17 00:00:00 2001
From: Ondrej Mular <omular@redhat.com>
Date: Thu, 28 Jul 2016 15:21:18 +0200
Subject: [PATCH] web UI: add possibility to change order of resources in group

---
 pcsd/pcs.rb                   |   1 +
 pcsd/public/css/style.css     |  10 +++
 pcsd/public/js/nodes-ember.js | 167 ++++++++++++++++++++++++++++++++++++++----
 pcsd/public/js/pcsd.js        | 117 +++++++++++++++++------------
 pcsd/remote.rb                |  47 ++++++++----
 pcsd/views/_dialogs.erb       |  21 ++++++
 pcsd/views/_resource.erb      |   6 --
 pcsd/views/main.erb           |  51 +++++++++++--
 8 files changed, 334 insertions(+), 86 deletions(-)

diff --git a/pcsd/pcs.rb b/pcsd/pcs.rb
index ad54a75..1eb9e9e 100644
--- a/pcsd/pcs.rb
+++ b/pcsd/pcs.rb
@@ -1702,6 +1702,7 @@ def get_node_status(auth_user, cib_dom)
         'constraint_colocation_set',
         'sbd',
         'ticket_constraints',
+        'moving_resource_in_group',
       ]
   }
 
diff --git a/pcsd/public/css/style.css b/pcsd/public/css/style.css
index d41b164..0d744d5 100644
--- a/pcsd/public/css/style.css
+++ b/pcsd/public/css/style.css
@@ -848,3 +848,13 @@ table.args-table td.reg {
 .constraint-ticket-add-attribute {
   vertical-align: top;
 }
+
+.cursor-move {
+  cursor: move;
+}
+
+.sortable-table td {
+  height: 1.5em;
+  line-height: 1.2em;
+  background: black;
+}
diff --git a/pcsd/public/js/nodes-ember.js b/pcsd/public/js/nodes-ember.js
index efc0192..3d4fe79 100644
--- a/pcsd/public/js/nodes-ember.js
+++ b/pcsd/public/js/nodes-ember.js
@@ -52,6 +52,11 @@ Pcs = Ember.Application.createWithMixins({
       this.get("available_features").indexOf("constraint_colocation_set") != -1
     );
   }.property("available_features"),
+  is_supported_moving_resource_in_group: function() {
+    return (
+      this.get("available_features").indexOf("moving_resource_in_group") != -1
+    );
+  }.property("available_features"),
   is_sbd_running: false,
   is_sbd_enabled: false,
   is_sbd_enabled_or_running: function() {
@@ -245,6 +250,154 @@ Pcs = Ember.Application.createWithMixins({
   }
 });
 
+Pcs.GroupSelectorComponent = Ember.Component.extend({
+  resource_id: null,
+  resource: function() {
+    var id = this.get("resource_id");
+    if (id) {
+      var resource = Pcs.resourcesContainer.get_resource_by_id(id);
+      if (resource) {
+        return resource;
+      }
+    }
+    return null;
+  }.property("resource_id"),
+  resource_change: function() {
+    this._refresh_fn();
+    this._update_resource_select_content();
+    this._update_resource_select_value();
+  }.observes("resource", "resource_id"),
+  group_list: [],
+  group_select_content: function() {
+    var list = [];
+    $.each(this.get("group_list"), function(_, group) {
+      list.push({
+        name: group,
+        value: group
+      });
+    });
+    return list;
+  }.property("group_list"),
+  group_select_value: null,
+  group: function() {
+    var id = this.get("group_select_value");
+    if (id) {
+      var group = Pcs.resourcesContainer.get_resource_by_id(id);
+      if (group) {
+        return group;
+      }
+    }
+    return null;
+  }.property("group_select_value"),
+  position_select_content: [
+    {
+      name: "before",
+      value: "before"
+    },
+    {
+      name: "after",
+      value: "after"
+    }
+  ],
+  position_select_value: null,
+  position_select_value_changed: function() {
+  }.observes("position_select_value"),
+  resource_select_content: [],
+  resource_select_value: null,
+  group_select_value_changed: function () {
+    this._update_resource_select_content();
+    this._update_resource_select_value();
+  }.observes("group_select_value"),
+  actions: {
+    refresh: function() {
+      this.set("group_list", Pcs.resourcesContainer.get("group_list"));
+      this._refresh_fn();
+      this._update_resource_select_content();
+      this._update_resource_select_value();
+    }
+  },
+  _refresh_fn: function() {
+    var id = this.get("resource_id");
+    if (id) {
+      var resource = Pcs.resourcesContainer.get_resource_by_id(id);
+      if (resource) {
+        var parent = resource.get("parent");
+        if (parent && parent.get("is_group")) {
+          this.set("group_select_value", parent.get("id"));
+          return;
+        }
+      }
+    }
+    this.set("group_select_value", null);
+  },
+  _update_resource_select_content: function() {
+    var self = this;
+    var group = self.get("group");
+    if (!group) {
+      self.set("resource_select_content", []);
+      return;
+    }
+    var list = [];
+    var resource_id;
+    $.each(group.get("members"), function(_, resource) {
+      resource_id = resource.get("id");
+      if (resource_id != self.get("resource_id")) {
+        list.push({
+          name: resource_id,
+          value: resource_id
+        });
+      }
+    });
+    self.set("resource_select_content", list);
+  },
+  _update_resource_select_value: function() {
+    var self = this;
+    var group = self.get("group");
+    var resource = self.get("resource");
+    if (!group) {
+      self.set("resource_select_value", null);
+      return;
+    }
+    var resource_list = group.get("members");
+    if (
+      !resource ||
+      !resource.get("parent") ||
+      resource.get("parent").get("id") != group.get("id")
+    ) {
+      self.set("position_select_value", "after");
+      self.set("resource_select_value", resource_list.slice(-1)[0].get("id"));
+    } else {
+      var index = resource_list.findIndex(function(item) {
+        return item.get("id") == resource.get("id");
+      });
+      if (index == 0) {
+        self.set("position_select_value", "before");
+        self.set(
+          "resource_select_value",
+          (resource_list[1]) ? resource_list[1].get("id") : null // second
+        );
+      } else if (index == -1) {
+        self.set("position_select_value", "after");
+        self.set("resource_select_value", resource_list.slice(-1)[0].get("id"));
+      } else {
+        self.set("position_select_value", "after");
+        self.set("resource_select_value", resource_list[index-1].get("id"));
+      }
+    }
+  },
+  group_input_name: "group_id",
+  classNames: "group-selector",
+  init: function() {
+    this._super();
+    if (this.get("resource_id")) {
+      this.set("group_list", Pcs.resourcesContainer.get("group_list"));
+    }
+    this._refresh_fn();
+    this._update_resource_select_content();
+    this._update_resource_select_value();
+  }
+});
+
 Pcs.ValueSelectorComponent = Ember.Component.extend({
   tagName: 'select',
   attributeBindings: ['name'],
@@ -682,20 +835,6 @@ Pcs.ResourceObj = Ember.Object.extend({
     }
     return null;
   }.property('parent'),
-  group_selector: function() {
-    var self = this;
-    var cur_group = self.get('get_group_id');
-    var html = '<select>\n<option value="">None</option>\n';
-    $.each(self.get('group_list'), function(_, group) {
-      html += '<option value="' + group + '"';
-      if (cur_group === group) {
-        html += 'selected';
-      }
-      html += '>' + group + '</option>\n';
-    });
-    html += '</select><input type="button" value="Change group" onclick="resource_change_group(curResource(), $(this).prev().prop(\'value\'));">';
-    return html;
-  }.property('group_list', 'get_group_id'),
   status: "unknown",
   class_type: null, // property to determine type of the resource
   resource_type: function() { // this property is just for displaying resource type in GUI
diff --git a/pcsd/public/js/pcsd.js b/pcsd/public/js/pcsd.js
index a646bed..82187ef 100644
--- a/pcsd/public/js/pcsd.js
+++ b/pcsd/public/js/pcsd.js
@@ -96,50 +96,77 @@ function select_menu(menu, item, initial) {
 }
 
 function create_group() {
-  var num_nodes = 0;
-  var node_names = "";
-  $("#resource_list :checked").parent().parent().each(function (index,element) {
-    if (element.getAttribute("nodeID")) {
-      num_nodes++;
-      node_names += element.getAttribute("nodeID") + " "
-    }
-  });
-
-  if (num_nodes == 0) {
+  var resource_list = get_checked_ids_from_nodelist("resource_list");
+  if (resource_list.length == 0) {
     alert("You must select at least one resource to add to a group");
     return;
   }
-
-  $("#resources_to_add_to_group").val(node_names);
+  var not_primitives = resource_list.filter(function(resource_id) {
+    return !Pcs.resourcesContainer.get_resource_by_id(resource_id).get(
+      "is_primitive"
+    );
+  });
+  if (not_primitives.length != 0) {
+    alert("Members of group have to be primitive resources. These resources" +
+      " are not primitives: " + not_primitives.join(", "));
+    return;
+  }
+  var order_el = $("#new_group_resource_list tbody");
+  order_el.empty();
+  order_el.append(resource_list.map(function (item) {
+    return `<tr value="${item}" class="cursor-move"><td>${item}</td></tr>`;
+  }));
+  var order_obj = order_el.sortable();
+  order_el.disableSelection();
   $("#add_group").dialog({
     title: 'Create Group',
+    width: 'auto',
     modal: true,
     resizable: false,
-    buttons: {
-      Cancel: function() {
-        $(this).dialog("close");
+    buttons: [
+      {
+        text: "Cancel",
+        click: function() {
+          $(this).dialog("close");
+        }
       },
-      "Create Group": function() {
-        var data = $('#add_group > form').serialize();
-        var url = get_cluster_remote_url() + "add_group";
-        ajax_wrapper({
-          type: "POST",
-          url: url,
-          data: data,
-          success: function() {
-            Pcs.update();
-            $("#add_group").dialog("close");
-          },
-          error: function (xhr, status, error) {
-            alert(
-              "Error creating group "
-              + ajax_simple_error(xhr, status, error)
-            );
-            $("#add_group").dialog("close");
-          }
-        });
+      {
+        text: "Create Group",
+        id: "add_group_submit_btn",
+        click: function() {
+          var dialog_obj = $(this);
+          var submit_btn_obj = dialog_obj.parent().find(
+            "#add_group_submit_btn"
+          );
+          submit_btn_obj.button("option", "disabled", true);
+
+          ajax_wrapper({
+            type: "POST",
+            url: get_cluster_remote_url() + "add_group",
+            data: {
+              resource_group: $(
+                '#add_group:visible input[name=resource_group]'
+              ).val(),
+              resources: order_obj.sortable(
+                "toArray", {attribute: "value"}
+              ).join(" ")
+            },
+            success: function() {
+              submit_btn_obj.button("option", "disabled", false);
+              Pcs.update();
+              dialog_obj.dialog("close");
+            },
+            error: function (xhr, status, error) {
+              alert(
+                "Error creating group "
+                + ajax_simple_error(xhr, status, error)
+              );
+              submit_btn_obj.button("option", "disabled", false);
+            }
+          });
+        }
       }
-    }
+    ]
   });
 }
 
@@ -2257,24 +2284,24 @@ function resource_ungroup(group_id) {
   });
 }
 
-function resource_change_group(resource_id, group_id) {
+function resource_change_group(resource_id, form) {
   if (resource_id == null) {
     return;
   }
   show_loading_screen();
   var resource_obj = Pcs.resourcesContainer.get_resource_by_id(resource_id);
   var data = {
-    resource_id: resource_id,
-    group_id: group_id
+    resource_id: resource_id
   };
+  $.each($(form).serializeArray(), function(_, item) {
+    data[item.name] = item.value;
+  });
 
-  if (resource_obj.get('parent')) {
-    if (resource_obj.get('parent').get('id') == group_id) {
-      return;
-    }
-    if (resource_obj.get('parent').get('class_type') == 'group') {
-      data['old_group_id'] = resource_obj.get('parent').get('id');
-    }
+  if (
+    resource_obj.get('parent') &&
+    resource_obj.get('parent').get('class_type') == 'group'
+  ) {
+    data['old_group_id'] = resource_obj.get('parent').get('id');
   }
 
   ajax_wrapper({
diff --git a/pcsd/remote.rb b/pcsd/remote.rb
index 05a6d03..4844adf 100644
--- a/pcsd/remote.rb
+++ b/pcsd/remote.rb
@@ -1415,21 +1415,23 @@ def update_resource (params, request, auth_user)
 
   param_line = getParamList(params)
   if not params[:resource_id]
-    out, stderr, retval = run_cmd(
-      auth_user,
-      PCS, "resource", "create", params[:name], params[:resource_type],
-      *param_line
-    )
-    if retval != 0
-      return JSON.generate({"error" => "true", "stderr" => stderr, "stdout" => out})
-    end
+    cmd = [PCS, "resource", "create", params[:name], params[:resource_type]]
+    cmd += param_line
     if params[:resource_group] and params[:resource_group] != ""
-      run_cmd(
-        auth_user,
-        PCS, "resource","group", "add", params[:resource_group], params[:name]
+      cmd += ['--group', params[:resource_group]]
+      if (
+        ['before', 'after'].include?(params[:in_group_position]) and
+        params[:in_group_reference_resource_id]
       )
+        cmd << "--#{params[:in_group_position]}"
+        cmd << params[:in_group_reference_resource_id]
+      end
       resource_group = params[:resource_group]
     end
+    out, stderr, retval = run_cmd(auth_user, *cmd)
+    if retval != 0
+      return JSON.generate({"error" => "true", "stderr" => stderr, "stdout" => out})
+    end
 
     if params[:resource_clone] and params[:resource_clone] != ""
       name = resource_group ? resource_group : params[:name]
@@ -1461,10 +1463,18 @@ def update_resource (params, request, auth_user)
         )
       end
     else
-      run_cmd(
-        auth_user, PCS, "resource", "group", "add", params[:resource_group],
+      cmd = [
+        PCS, "resource", "group", "add", params[:resource_group],
         params[:resource_id]
+      ]
+      if (
+        ['before', 'after'].include?(params[:in_group_position]) and
+        params[:in_group_reference_resource_id]
       )
+        cmd << "--#{params[:in_group_position]}"
+        cmd << params[:in_group_reference_resource_id]
+      end
+      run_cmd(auth_user, *cmd)
     end
   end
 
@@ -2098,10 +2108,17 @@ def resource_change_group(params, request, auth_user)
     end
     return 200
   end
-  _, stderr, retval = run_cmd(
-    auth_user,
+  cmd = [
     PCS, 'resource', 'group', 'add', params[:group_id], params[:resource_id]
+  ]
+  if (
+  ['before', 'after'].include?(params[:in_group_position]) and
+    params[:in_group_reference_resource_id]
   )
+    cmd << "--#{params[:in_group_position]}"
+    cmd << params[:in_group_reference_resource_id]
+  end
+  _, stderr, retval = run_cmd(auth_user, *cmd)
   if retval != 0
     return [400, "Unable to add resource '#{params[:resource_id]}' to " +
       "group '#{params[:group_id]}': #{stderr.join('')}"
diff --git a/pcsd/views/_dialogs.erb b/pcsd/views/_dialogs.erb
index 46e7fdb..d18ac71 100644
--- a/pcsd/views/_dialogs.erb
+++ b/pcsd/views/_dialogs.erb
@@ -215,3 +215,24 @@
   </table>
   {{/if}}
 </div>
+
+<div id="add_group" style="display: none;">
+  <form method=POST onkeypress="if (event.keyCode == 13) {$(this).parent().parent().find('.ui-dialog-buttonpane button:eq(1)').trigger('click');return false;} " action="/resource_group_add">
+    <table>
+      <tr>
+        <td>Group Name:</td>
+        <td>
+          <input name="resource_group" type="text" />
+        </td>
+      </tr>
+      <tr>
+        <td style="vertical-align: top;">Change order of resources:</td>
+        <td>
+          <table id="new_group_resource_list" class="sortable-table">
+            <tbody></tbody>
+          </table>
+        </td>
+      </tr>
+    </table>
+  </form>
+</div>
diff --git a/pcsd/views/_resource.erb b/pcsd/views/_resource.erb
index a337160..ad2251c 100644
--- a/pcsd/views/_resource.erb
+++ b/pcsd/views/_resource.erb
@@ -116,10 +116,4 @@
           table_id_suffix="_new"
       }}
     </div>
-    <div id="add_group" style="display: none;">
-      <form method=POST onkeypress="if (event.keyCode == 13) {$(this).parent().parent().find('.ui-dialog-buttonpane button:eq(1)').trigger('click');return false;} " action="/resource_group_add">
-	<p style="font-size:12px;">Group Name:</p><input name="resource_group" type=text>
-	<input id="resources_to_add_to_group"  type=hidden name="resources" value="">
-      </form>
-    </div>
     <% end %>
diff --git a/pcsd/views/main.erb b/pcsd/views/main.erb
index 52c1900..1b21f92 100644
--- a/pcsd/views/main.erb
+++ b/pcsd/views/main.erb
@@ -237,7 +237,7 @@
             <tr>
               <td class="bold" nowrap>Group:</td>
               <td id="cur_res_loc" class="reg">
-                {{{resource.group_selector}}}
+                {{group-selector resource_id=resource._id}}
               </td>
             </tr>
           {{else}}
@@ -245,7 +245,7 @@
             <tr>
               <td class="bold" nowrap>Group:</td>
               <td id="cur_res_loc" class="reg">
-                {{{resource.group_selector}}}
+                {{group-selector resource_id=resource._id}}
               </td>
             </tr>
             {{/if}}
@@ -909,10 +909,9 @@ Use the 'Add' button to submit the form.">
                     </div>
                   </td>
                   <td>
-                    {{value-selector
-                        prompt="None"
-                        content=groups
-                        name="resource_group"
+                    {{group-selector
+                        group_list=Pcs.resourcesContainer.group_list
+                        group_input_name="resource_group"
                     }}
                   </td>
                 </tr>
@@ -1095,6 +1094,46 @@ Use the 'Add' button to submit the form.">
     </td>
   </script>
 
+  <script type="text/x-handlebars" data-template-name="components/group-selector">
+    {{value-selector
+        name=group_input_name
+        content=group_select_content
+        value=group_select_value
+        prompt="None"
+    }}
+    {{#if Pcs.is_supported_moving_resource_in_group}}
+    {{#if group_select_value}}
+    {{#if resource_select_content}}
+      {{value-selector
+          name="in_group_position"
+          content=position_select_content
+          value=position_select_value
+          prompt=""
+      }}
+      {{value-selector
+          name="in_group_reference_resource_id"
+          content=resource_select_content
+          value=resource_select_value
+          prompt=""
+      }}
+    {{/if}}
+    {{/if}}
+    {{/if}}
+    {{#if resource_id}}
+      <br/>
+      <button
+        onclick="
+          resource_change_group(curResource(), $(this).parent().find('select'));
+          return false;
+        "
+      >
+        Update group
+      </button>
+      <button {{action refresh}}>Refresh</button>
+    {{/if}}
+
+  </script>
+
   <script type="text/x-handlebars">
 <div id="wrapper">
 
-- 
1.8.3.1