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