diff options
author | Alex Rudyy <orudyy@apache.org> | 2014-02-17 09:55:06 +0000 |
---|---|---|
committer | Alex Rudyy <orudyy@apache.org> | 2014-02-17 09:55:06 +0000 |
commit | cb14ed57053dde009d289e6a230496ca8f00ab59 (patch) | |
tree | fda13f0dce5434a9c94a215efe19f313d91aed4f | |
parent | 1f14596bfab437c64dcb5ccebb285263a66b6b5a (diff) | |
download | qpid-python-cb14ed57053dde009d289e6a230496ca8f00ab59.tar.gz |
QPID-5409: Add UI for editing node attributes
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/java-broker-bdb-ha@1568923 13f79535-47bb-0310-9956-ffa450edef68
6 files changed, 477 insertions, 15 deletions
diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java index d36f9539dd..a137e38baf 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java @@ -28,6 +28,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.server.store.StoreFuture; import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; import com.sleepycat.je.Transaction; public class CoalescingCommiter implements Committer @@ -226,7 +227,11 @@ public class CoalescingCommiter implements Committer startTime = System.currentTimeMillis(); } - _environmentFacade.getEnvironment().flushLog(true); + Environment environment = _environmentFacade.getEnvironment(); + if (environment != null && environment.isValid()) + { + environment.flushLog(true); + } if(LOGGER.isDebugEnabled()) { diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java index f87949065a..721acf758a 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.model.ConfiguredObject; @@ -50,6 +51,7 @@ import com.sleepycat.je.rep.ReplicatedEnvironment; public class LocalReplicationNode extends AbstractAdapter implements ReplicationNode, ReplicatedEnvironmentConfiguration { + private static final Logger LOGGER = Logger.getLogger(LocalReplicationNode.class); private static final Durability DEFAULT_DURABILITY = new Durability(SyncPolicy.NO_SYNC, SyncPolicy.NO_SYNC, ReplicaAckPolicy.SIMPLE_MAJORITY); @@ -266,10 +268,12 @@ public class LocalReplicationNode extends AbstractAdapter implements Replication catch(IllegalStateException e) { // ignore, as attribute value will be returned from actual/default attribute maps if present + LOGGER.debug("Exception on retrieving attribute from environment", e); } catch(DatabaseException e) { // ignore, as attribute value will be returned from actual/default attribute maps if present + LOGGER.debug("Exception on retrieving attribute from environment", e); } } return super.getAttribute(attributeName); diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java index 123a157513..722a0bca31 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java @@ -419,6 +419,10 @@ public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChan public String getNodeState() { + if (_state.get() != State.OPEN) + { + return ReplicatedEnvironment.State.UNKNOWN.name(); + } ReplicatedEnvironment.State state = _environment.getState(); return state.toString(); } @@ -430,6 +434,10 @@ public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChan public boolean isDesignatedPrimary() { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not opened"); + } return _environment.getRepMutableConfig().getDesignatedPrimary(); } @@ -472,6 +480,10 @@ public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChan int getPriority() { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not opened"); + } ReplicationMutableConfig repConfig = _environment.getRepMutableConfig(); return repConfig.getNodePriority(); } @@ -515,6 +527,10 @@ public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChan int getElectableGroupSizeOverride() { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not opened"); + } ReplicationMutableConfig repConfig = _environment.getRepMutableConfig(); return repConfig.getElectableGroupSizeOverride(); } @@ -943,6 +959,10 @@ public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChan // For testing only int getNumberOfElectableGroupMembers() { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not opened"); + } return _environment.getGroup().getElectableNodes().size(); } diff --git a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/edit.js b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/edit.js new file mode 100644 index 0000000000..038cdda4d6 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/edit.js @@ -0,0 +1,210 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/_base/array", + "dojo/_base/event", + "dojo/_base/lang", + "dojo/_base/window", + "dojo/dom", + "dojo/dom-construct", + "dijit/registry", + "dojo/parser", + 'dojo/json', + "dojo/query", + "dojo/text!virtualhost/bdb_ha/edit.html", + "dijit/Dialog", + "dijit/form/CheckBox", + "dijit/form/FilteringSelect", + "dijit/form/ValidationTextBox", + "dijit/form/Button", + "dijit/form/Form", + "dojox/validate/us", + "dojox/validate/web", + "dojo/domReady!"], + function (xhr, array, event, lang, win, dom, domConstruct, registry, parser, json, query, template) + { + var widgets = [{ + name: "storePath", + defaultValue: "", + editable: false + }, + { + name: "name", + defaultValue: "", + editable: false + }, + { + name: "groupName", + defaultValue: "", + editable: false + }, + { + name: "hostPort", + defaultValue: "", + editable: false + }, + { + name: "helperHostPort", + defaultValue: "", + editable: false + }, + { + name: "durability", + defaultValue: "", + editable: false + }, + { + name: "coalescingSync", + defaultValue: true, + defaultChecked: true, + editable: false + }, + { + name: "designatedPrimary", + defaultValue: true, + defaultChecked: true, + editable: true + }, + { + name: "priority", + defaultValue: 1, + editable: true + }, + { + name: "quorumOverride", + defaultValue: 0, + editable: true + }]; + + var bdbHaNodeEditor = + { + _init: function() + { + this.containerNode = domConstruct.create("div", {innerHTML: template}); + parser.parse(this.containerNode); + this.dialog = registry.byId("editNodeDialog") + this.saveButton = registry.byId("editNode.saveButton"); + this.cancelButton = registry.byId("editNode.cancelButton"); + this.cancelButton.on("click", function(e){bdbHaNodeEditor._cancel(e);}); + this.saveButton.on("click", function(e){bdbHaNodeEditor._save(e);}); + for(var i = 0; i < widgets.length; i++) + { + var widgetItem = widgets[i]; + this[widgetItem.name] = registry.byNode(query(".editNode-" + widgetItem.name, this.dialog.containerNode)[0]); + } + this.form = registry.byId("editNodeForm"); + }, + show: function(virtualHost, nodeName) + { + this.query = "rest/replicationnode/" + encodeURIComponent(virtualHost) + "/" + encodeURIComponent(nodeName); + if (virtualHost && nodeName) + { + xhr.get({url: this.query, + sync: true, + handleAs: "json", + load: function(data) { + var node = data[0]; + for(var i = 0; i < widgets.length; i++) + { + var widgetItem = widgets[i]; + bdbHaNodeEditor[widgetItem.name].set("value", node[widgetItem.name]); + bdbHaNodeEditor[widgetItem.name].set("disabled", !widgetItem.editable); + } + }}); + } + else + { + for(var i = 0; i < nonEditableWidgetNumber; i++) + { + var widgetItem = widgets[i]; + bdbHaNodeEditor[widgetItem.name].set("value", widgetItem.defaultValue); + bdbHaNodeEditor[widgetItem.name].set("disabled", false); + if (widgetItem.hasOwnProperty("defaultChecked")) + { + bdbHaNodeEditor[widgetName].set("checked", widgetItem.defaultChecked); + } + } + } + this.dialog.show(); + }, + destroy: function() + { + if (this.dialog) + { + this.dialog.destroyRecursive(); + this.dialog = null; + } + + if (this.containerNode) + { + domConstruct.destroy(this.containerNode); + this.containerNode = null; + } + }, + _cancel: function(e) + { + this.dialog.hide(); + }, + _save: function(e) + { + event.stop(e); + if(this.form.validate()) + { + var data = {}; + for(var i = 0; i < widgets.length; i++) + { + var widgetItem = widgets[i]; + var widget = this[widgetItem.name]; + if (!widget.get("disabled")) + { + data[widgetItem.name] = widgetItem.hasOwnProperty("defaultChecked")? widget.get("checked"):widget.get("value"); + } + } + xhr.put({ + url: this.query, + sync: true, + handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify(data), + load: function(x) {bdbHaNodeEditor.success = true; }, + error: function(error) {bdbHaNodeEditor.success = false; bdbHaNodeEditor.failureReason = error;} + }); + + if(this.success === true) + { + this.dialog.hide(); + } + else + { + alert("Error:" + this.failureReason); + } + } + else + { + alert('Form contains invalid data. Please correct first'); + } + } + }; + + bdbHaNodeEditor._init(); + + return bdbHaNodeEditor; + });
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js index 72d36d57d2..c27cdcfc90 100644 --- a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js +++ b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js @@ -19,16 +19,20 @@ * */ define(["dojo/_base/xhr", + "dojo/_base/lang", + "dojo/_base/connect", "dojo/parser", "dojo/string", "dojox/html/entities", "dojo/query", + "dojo/json", "dijit/registry", "dojox/grid/EnhancedGrid", "qpid/common/UpdatableStore", "qpid/management/UserPreferences", + "qpid/management/virtualhost/bdb_ha/edit", "dojo/domReady!"], - function (xhr, parser, json, entities, query, registry, EnhancedGrid, UpdatableStore, UserPreferences) { + function (xhr, lang, connect, parser, json, entities, query, json, registry, EnhancedGrid, UpdatableStore, UserPreferences, nodeEditor) { var hostFields = ["desiredState", "quiesceOnMasterChange"]; @@ -36,8 +40,7 @@ define(["dojo/_base/xhr", "coalescingSync", "designatedPrimary", "durability", "priority", "quorumOverride"]; var buttons = ["activateVirtualHostButton", "quiesceVirtualHostButton", "stopVirtualHostButton", - "editVirtualHostButton", "activateNodeButton", "stopNodeButton","editNodeButton", - "removeNodeButton", "transferMasterButton"]; + "editVirtualHostButton", "removeNodeButton"]; function findNode(nodeClass, containerNode) { @@ -73,6 +76,7 @@ define(["dojo/_base/xhr", BDBHA.prototype.update=function(data) { + this.data = data for(var i = 0; i < hostFields.length; i++) { var name = hostFields[i]; @@ -82,6 +86,8 @@ define(["dojo/_base/xhr", if (nodes && nodes.length>0) { var localNode = nodes[0]; + this.stopNodeButton.set("disabled", localNode.state == "STOPPED"); + this.activateNodeButton.set("disabled", localNode.state == "ACTIVE"); for(var i = 0; i < nodeFields.length; i++) { var name = nodeFields[i]; @@ -115,6 +121,7 @@ define(["dojo/_base/xhr", this._initFields(hostFields, containerNode); this._initFields(nodeFields, containerNode); + var that = this; for(var i = 0; i < buttons.length; i++) { var buttonName = buttons[i]; @@ -124,22 +131,63 @@ define(["dojo/_base/xhr", var buttonWidget = registry.byNode(buttonNode); if (buttonWidget) { - this[buttonName] = buttonWidget; - var handler = this["_onClick_" + buttonName]; - if (handler) - { - buttonWidget.on("click", function(evt){ - handler(evt); - }); - } - else - { buttonWidget.set("disabled", true); - } } } } + this.editNodeButton = registry.byNode(findNode("editNodeButton", containerNode)); + this.editNodeButton.on("click", function(e){ + var nodeName = null; + if (that.data.replicationnodes && that.data.replicationnodes.length > 0) + { + nodeName = that.data.replicationnodes[0].name; + } + nodeEditor.show(that.data.name, nodeName); + }); + + function changeNodeAttributes(nodeName, data) + { + var success = false; + var failureReason = ""; + xhr.put({ + url: "rest/replicationnode/" + encodeURIComponent(that.data.name) + "/" + encodeURIComponent(nodeName), + sync: true, + handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify(data), + load: function(x) {success = true; }, + error: function(error) {success = false; failureReason = error;} + }); + if (!success) + { + alert("Error:" + failureReason); + } + } + + function changeStatus(newState) + { + if (confirm("Are you sure you would like to change node state to '" + newState + "'?")) + { + changeNodeAttributes(that.data.replicationnodes[0].name, {desiredState: newState}); + } + } + + this.stopNodeButton = registry.byNode(findNode("stopNodeButton", containerNode)); + this.stopNodeButton.on("click", function(e){changeStatus("STOPPED");}); + this.activateNodeButton = registry.byNode(findNode("activateNodeButton", containerNode)); + this.activateNodeButton.on("click", function(e){changeStatus("ACTIVE");}); + this.transferMasterButton = registry.byNode(findNode("transferMasterButton", containerNode)); + this.transferMasterButton.on("click", function(e){ + var data = that.membersGrid.grid.selection.getSelected(); + if (data.length == 1 && confirm("Are you sure you would like to transfer mastership to node '" + data[0].name + "'?")) + { + changeNodeAttributes(data[0].name, {role: "MASTER"}); + that.membersGrid.grid.selection.clear(); + } + }); + this.transferMasterButton.set("disabled", true); + this.membersGrid = new UpdatableStore([], findNode("cluserNodes", containerNode), [ @@ -158,6 +206,14 @@ define(["dojo/_base/xhr", }, EnhancedGrid, true ); + var transferMasterToggler = function(rowIndex){ + var data = that.membersGrid.grid.selection.getSelected(); + var isButtonDisabled = data.length != 1 || data[0].role != "REPLICA"; + that.transferMasterButton.set("disabled", isButtonDisabled); + }; + connect.connect(this.membersGrid.grid.selection, 'onSelected', transferMasterToggler); + connect.connect(this.membersGrid.grid.selection, 'onDeselected', transferMasterToggler); + this.parametersGrid = new UpdatableStore([], findNode("parameters", containerNode), [ diff --git a/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/edit.html b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/edit.html new file mode 100644 index 0000000000..f7cdac81fa --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/edit.html @@ -0,0 +1,167 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit/Dialog" data-dojo-props="title:'Edit Node'" id="editNodeDialog"> + <form id="editNodeForm" method="post" data-dojo-type="dijit/form/Form"> + <table class="tableContainer-table tableContainer-table-horiz"> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Path to store location*: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-storePath" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'storePath', + placeHolder: '/path/to/message/store', + required: true, + missingMessage: 'A store path must be supplied', + title: 'Enter path to the store folder'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Node Name*: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-name" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'nodeName', + placeHolder: 'Node Name', + required: true, + missingMessage: 'A name must be supplied', + title: 'Enter node name', + pattern: '^[\x20-\x2e\x30-\x7F]{1,255}$'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Replication Group*: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-groupName" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'groupName', + placeHolder: 'Group Name', + required: true, + missingMessage: 'A group name must be supplied', + title: 'Enter group name', + pattern: '^[\x20-\x2e\x30-\x7F]{1,255}$'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Node Address*: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-hostPort" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'hostPort', + placeHolder: 'host:port', + required: true, + missingMessage: 'A Host and Port must be supplied', + invalidMessage: 'Must be of the form host:port', + title: 'Enter Host and Port name', + pattern: '^([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}$'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Helper Address*: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-helperHostPort" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'helperHostPort', + placeHolder: 'host:port', + required: true, + missingMessage: 'A Host and Port must be supplied', + invalidMessage: 'Must be of the form host:port', + title: 'Enter Helper Host and Port name', + pattern: '^([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}$'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Durability: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-durability" + data-dojo-type="dijit/form/ValidationTextBox" + data-dojo-props=" + name: 'durability', + placeHolder: 'NO_SYNC,NO_SYNC,SIMPLE_MAJORITY', + required: false, + missingMessage: 'A Host and Port must be supplied', + title: 'Enter Helper Host and Port name', + pattern: '^(SYN|NO_SYNC|WRITE_NO_SYNC),(SYN|NO_SYNC|WRITE_NO_SYNC),(SIMPLE_MAJORITY|ALL|NONE)*$'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Coalesce local sync: </strong></td> + <td class="tableContainer-valueCell"> + <input type="checkbox" class="editNode-coalescingSync" checked="checked" + data-dojo-type="dijit/form/CheckBox" + data-dojo-props=" + name: 'coalescingSync', + required: false, + title: 'Enable coalescing sync mode for a better performance: when many transactions are synced to disk at the same time'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Designated Primary: </strong></td> + <td class="tableContainer-valueCell"> + <input type="checkbox" class="editNode-designatedPrimary" checked="checked" + data-dojo-type="dijit/form/CheckBox" + data-dojo-props=" + name: 'designatedPrimary', + required: false, + title: 'Designate node as primary. It is applicable only to 2-nodes cluster'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Priority of electing as a Master: </strong></td> + <td class="tableContainer-valueCell"> + <div data-dojo-type="dojo/store/Memory" data-dojo-id="priorityStore" + data-dojo-props="data: [ + {id: '0', name: 'Never'}, + {id: '1', name: 'Default', selected: '1'}, + {id: '2', name: 'Middle'}, + {id: '3', name: 'High'} + ]"></div> + <input class="editNode-priority" data-dojo-type="dijit/form/FilteringSelect" value="1" + data-dojo-props=" + name: 'priority', + required: false, + title: 'Select node priority for election as a Master', + store: priorityStore, + searchAttr: 'name'" /> + </td> + </tr> + <tr> + <td class="tableContainer-labelCell" style="width: 200px;"><strong>Quorum override: </strong></td> + <td class="tableContainer-valueCell"> + <input type="text" class="editNode-quorumOverride" + data-dojo-type="dijit/form/NumberSpinner" value="0" + data-dojo-props=" + name: 'quorumOverride', + required: false, + title: 'Enter quorum override. 0 signifies simple majority', + smallDelta:1, + constraints:{min:0,max:10,places:0}" /> + </td> + </tr> + </table> + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit/form/Button" id="editNode.saveButton" data-dojo-props="label: 'Save'" type="submit">Save</button> + <button data-dojo-type="dijit/form/Button" id="editNode.cancelButton" data-dojo-props="label: 'Cancel'" ></button> + </div> + </form> + </div> +</div> |