diff options
author | Keith Wall <kwall@apache.org> | 2014-12-19 19:13:50 +0000 |
---|---|---|
committer | Keith Wall <kwall@apache.org> | 2014-12-19 19:13:50 +0000 |
commit | aa516ab4d908927a95b20275fc7daaa7bb9420a9 (patch) | |
tree | 13d7e8e04c45960ee680c69926d3bbf485b9f2bd /qpid/java/broker-plugins/management-http | |
parent | 40e74eaa3f8a345e7bc888e36de79717b7c761d0 (diff) | |
download | qpid-python-aa516ab4d908927a95b20275fc7daaa7bb9420a9.tar.gz |
QPID-6276: [Java Broker] Enhance the virtualhost UI to support upload/download of virtualhost config expressed as JSON.
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1646829 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker-plugins/management-http')
7 files changed, 213 insertions, 47 deletions
diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 0bc0a4514f..52c9b10e59 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -62,13 +62,20 @@ public class RestServlet extends AbstractServlet public static final String INHERITED_ACTUALS_PARAM = "inheritedActuals"; public static final String EXTRACT_INITIAL_CONFIG_PARAM = "extractInitialConfig"; + /** + * Signifies that the agent wishes the servlet to set the Content-Disposition on the + * response with the value attachment. This filename will be derived from the parameter value. + */ + public static final String CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM = "contentDispositionAttachmentFilename"; + public static final Set<String> RESERVED_PARAMS = new HashSet<>(Arrays.asList(DEPTH_PARAM, SORT_PARAM, ACTUALS_PARAM, INCLUDE_SYS_CONTEXT_PARAM, EXTRACT_INITIAL_CONFIG_PARAM, - INHERITED_ACTUALS_PARAM)); + INHERITED_ACTUALS_PARAM, + CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM)); private Class<? extends ConfiguredObject>[] _hierarchy; @@ -316,19 +323,23 @@ public class RestServlet extends AbstractServlet @Override protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // TODO - sort special params, everything else should act as a filter + String attachmentFilename = request.getParameter(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM); + boolean extractInitialConfig = getBooleanParameterFromRequest(request, EXTRACT_INITIAL_CONFIG_PARAM); + response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_OK); setCachingHeadersOnResponse(response); + setContentDispositionHeaderIfNecessary(response, attachmentFilename); Collection<ConfiguredObject<?>> allObjects = getObjects(request); - // TODO - sort special params, everything else should act as a filter - boolean extractInitialConfig = getBooleanParameterFromRequest(request, EXTRACT_INITIAL_CONFIG_PARAM); int depth; boolean actuals; boolean includeSystemContext; boolean inheritedActuals; + if(extractInitialConfig) { depth = Integer.MAX_VALUE; @@ -344,20 +355,35 @@ public class RestServlet extends AbstractServlet inheritedActuals = getBooleanParameterFromRequest(request, INHERITED_ACTUALS_PARAM); } - List<Map<String, Object>> output = new ArrayList<Map<String, Object>>(); + List<Map<String, Object>> output = new ArrayList<>(); for(ConfiguredObject configuredObject : allObjects) { output.add(_objectConverter.convertObjectToMap(configuredObject, getConfiguredClass(), depth, actuals, inheritedActuals, includeSystemContext, extractInitialConfig)); } + Writer writer = getOutputWriter(request, response); ObjectMapper mapper = new ObjectMapper(); mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); mapper.writeValue(writer, extractInitialConfig && output.size() == 1 ? output.get(0) : output); + } - response.setContentType("application/json"); - response.setStatus(HttpServletResponse.SC_OK); + private void setContentDispositionHeaderIfNecessary(final HttpServletResponse response, + final String attachmentFilename) + { + if (attachmentFilename != null) + { + String filenameRfc2183 = ensureFilenameIsRfc2183(attachmentFilename); + if (filenameRfc2183.length() > 0) + { + response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", filenameRfc2183)); + } + else + { + response.setHeader("Content-disposition", String.format("attachment")); // Agent will allow user to choose a name + } + } } private Class<? extends ConfiguredObject> getConfiguredClass() @@ -671,4 +697,10 @@ public class RestServlet extends AbstractServlet return Boolean.parseBoolean(request.getParameter(paramName)); } + private String ensureFilenameIsRfc2183(final String requestedFilename) + { + String fileNameRfc2183 = requestedFilename.replaceAll("[\\P{InBasic_Latin}\\\\:/]", ""); + return fileNameRfc2183; + } + } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHostNodeAndVirtualHost.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHostNodeAndVirtualHost.html index 3e3e931829..383c782d60 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHostNodeAndVirtualHost.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/addVirtualHostNodeAndVirtualHost.html @@ -59,6 +59,34 @@ <div id="addVirtualHostNode.typeFields"></div> + <div id="addVirtualHostNode.uploadFields" class="clear"> + <div class="formLabel-labelCell"> + <label for="addVirtualHostNode.upload">Upload virtualhost configuration from file:</label> + </div> + <div class="formLabel-controlCell"> + <input id="addVirtualHostNode.upload" type="checkbox" + data-dojo-type="dijit.form.CheckBox" + data-dojo-props=" + name: 'upload'" /> + </div> + <div id="addVirtualHostNode.fileFields" class="clear"> + <div class="formLabel-labelCell"> + <label for="addVirtualHostNode.file">Select JSON file*:</label> + </div> + <div class="tableContainer-valueCell formLabel-controlCell"> + <input type="file" id="addVirtualHostNode.file" + multiple="false" + data-dojo-type="dojox.form.Uploader" + data-dojo-props=" + label: 'Select'"/> + <span id="addVirtualHostNode.selectedFile" class="infoMessage"></span> + <span id="addVirtualHostNode.selectedFileStatus"></span> + </div> + </div> + </div> + + <div class="clear"></div> + <div data-dojo-type="dijit/TitlePane" data-dojo-props="title: 'Context variables', open: false"> <div id="addVirtualHostNode.context"></div> </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css b/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css index d04117b266..e6a0ce467f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/css/common.css @@ -215,6 +215,22 @@ div .messages { height: 16px; } +.loadingIcon +{ + background: url("../dojo/dojox/image/resources/images/loading.gif") no-repeat; + width: 25px; + height: 25px; + display: inline-block; +} + +.loadedIcon +{ + background: url("../dojo/dojox/mobile/themes/common/domButtons/compat/mblDomButtonDarkBlueCheck.png") no-repeat; + width: 25px; + height: 25px; + display: inline-block; +} + .infoMessage { padding: 5px; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html index eb742bbfa0..379a25bbcd 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/index.html @@ -25,6 +25,7 @@ <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/claro/EnhancedGrid.css"> <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/EnhancedGrid_rtl.css"> <link rel="stylesheet" href="dojo/dojox/form/resources/CheckedMultiSelect.css"> + <link rel="stylesheet" href="dojo/dojox/form/resources/FileInput.css" /> <link rel="stylesheet" href="css/common.css" media="screen"> <script> function getContextPath() diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js index cdc7890209..434e119736 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js @@ -93,6 +93,19 @@ define(["dojo/_base/xhr", that.stopButton = registry.byNode(query(".stopButton", containerNode)[0]); that.startButton = registry.byNode(query(".startButton", containerNode)[0]); that.editButton = registry.byNode(query(".editButton", containerNode)[0]); + that.downloadButton = registry.byNode(query(".downloadButton", containerNode)[0]); + that.downloadButton.on("click", + function(e) + { + var iframe = document.createElement('iframe'); + iframe.id = "downloader_" + that.name; + document.body.appendChild(iframe); + var suggestedAttachmentName = encodeURIComponent(that.name + ".json"); + iframe.src = "/api/latest/virtualhost/" + encodeURIComponent(that.modelObj.parent.name) + "/" + encodeURIComponent(that.name) + "?extractInitialConfig=true&contentDispositionAttachmentFilename=" + suggestedAttachmentName; + // It seems there is no way to remove this iframe in a manner that is cross browser compatible. + } + ); + that.deleteButton = registry.byNode(query(".deleteButton", containerNode)[0]); that.deleteButton.on("click", function(e) @@ -344,6 +357,7 @@ define(["dojo/_base/xhr", this.virtualHost.startButton.set("disabled", !this.vhostData.state || this.vhostData.state != "STOPPED"); this.virtualHost.stopButton.set("disabled", !this.vhostData.state || this.vhostData.state != "ACTIVE"); this.virtualHost.editButton.set("disabled", !this.vhostData.state || this.vhostData.state == "UNAVAILABLE"); + this.virtualHost.downloadButton.set("disabled", !this.vhostData.state || this.vhostData.state != "ACTIVE"); this.virtualHost.deleteButton.set("disabled", !this.vhostData.state); util.flattenStatistics( thisObj.vhostData ); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHostNodeAndVirtualHost.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHostNodeAndVirtualHost.js index 0a18a8909b..aa515fbe19 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHostNodeAndVirtualHost.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHostNodeAndVirtualHost.js @@ -44,6 +44,7 @@ define(["dojo/_base/xhr", "dijit/form/Form", "dijit/form/CheckBox", "dijit/form/RadioButton", + "dojox/form/Uploader", "dojox/validate/us", "dojox/validate/web", "dojo/domReady!"], @@ -61,6 +62,9 @@ define(["dojo/_base/xhr", var virtualHostNodeName = registry.byId("addVirtualHostNode.nodeName"); virtualHostNodeName.set("regExpGen", util.nameOrContextVarRegexp); + // Readers are HTML5 + this.reader = window.FileReader ? new FileReader() : undefined; + this.dialog = registry.byId("addVirtualHostNodeAndVirtualHost"); this.addButton = registry.byId("addVirtualHostNodeAndVirtualHost.addButton"); this.cancelButton = registry.byId("addVirtualHostNodeAndVirtualHost.cancelButton"); @@ -68,13 +72,22 @@ define(["dojo/_base/xhr", this.addButton.on("click", function(e){that._add(e);}); this.virtualHostNodeTypeFieldsContainer = dom.byId("addVirtualHostNode.typeFields"); + this.virtualHostNodeSelectedFileContainer = dom.byId("addVirtualHostNode.selectedFile"); + this.virtualHostNodeSelectedFileStatusContainer = dom.byId("addVirtualHostNode.selectedFileStatus"); + this.virtualHostNodeUploadFields = dom.byId("addVirtualHostNode.uploadFields"); + this.virtualHostNodeFileFields = dom.byId("addVirtualHostNode.fileFields"); + this.virtualHostNodeForm = registry.byId("addVirtualHostNode.form"); this.virtualHostNodeType = registry.byId("addVirtualHostNode.type"); + this.virtualHostNodeFileCheck = registry.byId("addVirtualHostNode.upload"); + this.virtualHostNodeFile = registry.byId("addVirtualHostNode.file"); + this.virtualHostNodeType.set("disabled", true); this.virtualHostTypeFieldsContainer = dom.byId("addVirtualHost.typeFields"); this.virtualHostForm = registry.byId("addVirtualHost.form"); this.virtualHostType = registry.byId("addVirtualHost.type"); + this.virtualHostType.set("disabled", true); this.supportedVirtualHostNodeTypes = metadata.getTypesForCategory("VirtualHostNode"); @@ -96,6 +109,21 @@ define(["dojo/_base/xhr", this.virtualHostType.set("store", this.virtualHostTypeStore); this.virtualHostType.set("disabled", false); this.virtualHostType.on("change", function(type){that._vhTypeChanged(type, that.virtualHostTypeFieldsContainer, "qpid/management/virtualhost/");}); + + if (this.reader) + { + this.reader.onload = function(e) {that._vhnUploadFileComplete(e);}; + this.virtualHostNodeFile.on("change", function(selected){that._vhnFileChanged(selected)}); + this.virtualHostNodeFileCheck.on("change", function(selected){that._vhnFileFlagChanged(selected)}); + } + else + { + // Fall back for IE8/9 which do not support FileReader + this.virtualHostNodeFile.set("disabled", true); + this.virtualHostNodeFileCheck.set("disabled", true); + } + + this.virtualHostNodeUploadFields.style.display = "none"; }, show: function() { @@ -164,6 +192,14 @@ define(["dojo/_base/xhr", this._processDropDownsForBdbHa(type); this._processDropDownsForJson(type); + var vhnTypeSelected = !(type == ''); + this.virtualHostNodeUploadFields.style.display = vhnTypeSelected ? "block" : "none"; + + if (!vhnTypeSelected) + { + this._vhnFileFlagChanged(false); + } + this._typeChanged(type, typeFieldsContainer, urlStem, "VirtualHostNode"); }, _vhTypeChanged: function (type, typeFieldsContainer, urlStem) @@ -203,6 +239,37 @@ define(["dojo/_base/xhr", ); } }, + _vhnFileFlagChanged: function (selected) + { + this.virtualHostForm.domNode.style.display = selected ? "none" : "block"; + this.virtualHostNodeFileFields.style.display = selected ? "block" : "none"; + this.virtualHostType.set("required", !selected); + this.virtualHostNodeFile.reset(); + this.virtualHostInitialConfiguration = undefined; + this.virtualHostNodeSelectedFileContainer.innerHTML = ""; + this.virtualHostNodeSelectedFileStatusContainer.className = ""; + }, + _vhnFileChanged: function (evt) + { + // We only ever expect a single file + var file = this.virtualHostNodeFile.domNode.children[0].files[0]; + + this.addButton.set("disabled", true); + this.virtualHostNodeSelectedFileContainer.innerHTML = file.name; + this.virtualHostNodeSelectedFileStatusContainer.className = "loadingIcon"; + + console.log("Beginning to read file " + file.name); + this.reader.readAsDataURL(file); + }, + _vhnUploadFileComplete: function(evt) + { + var reader = evt.target; + var result = reader.result; + console.log("File read complete, contents " + result); + this.virtualHostInitialConfiguration = result; + this.addButton.set("disabled", false); + this.virtualHostNodeSelectedFileStatusContainer.className = "loadedIcon"; + }, _processDropDownsForBdbHa: function (type) { if (type == "BDB_HA") @@ -249,6 +316,10 @@ define(["dojo/_base/xhr", }, _cancel: function(e) { + if (this.reader) + { + this.reader.abort(); + } this.dialog.hide(); }, _add: function(e) @@ -258,16 +329,32 @@ define(["dojo/_base/xhr", }, _submit: function() { - if(this.virtualHostNodeForm.validate() && this.virtualHostForm.validate()) + + var uploadVHConfig = this.virtualHostNodeFileCheck.get("checked"); + var virtualHostNodeData = undefined; + + if (uploadVHConfig && this.virtualHostNodeFile.getFileList().length > 0 && this.virtualHostNodeForm.validate()) { - var success = false,failureReason=null; + // VH config is being uploaded + virtualHostNodeData = this._getValues(this.virtualHostNodeForm); + var virtualHostNodeContext = this.virtualHostNodeContext.get("value"); + if (virtualHostNodeContext) + { + virtualHostNodeData["context"] = virtualHostNodeContext; + } - var virtualHostNodeData = this._getValues(this.virtualHostNodeForm); + // Add the loaded virtualhost configuration + virtualHostNodeData["virtualHostInitialConfiguration"] = this.virtualHostInitialConfiguration; + } + else if (!uploadVHConfig && this.virtualHostNodeForm.validate() && this.virtualHostForm.validate()) + { + virtualHostNodeData = this._getValues(this.virtualHostNodeForm); var virtualHostNodeContext = this.virtualHostNodeContext.get("value"); if (virtualHostNodeContext) { virtualHostNodeData["context"] = virtualHostNodeContext; } + var virtualHostData = this._getValues(this.virtualHostForm); var virtualHostContext = this.virtualHostContext.get("value"); if (virtualHostContext) @@ -278,48 +365,35 @@ define(["dojo/_base/xhr", //Default the VH name to be the same as the VHN name. virtualHostData["name"] = virtualHostNodeData["name"]; - var encodedVirtualHostNodeName = encodeURIComponent(virtualHostNodeData.name); - xhr.put({ - url: "api/latest/virtualhostnode/" + encodedVirtualHostNodeName, - sync: true, - handleAs: "json", - headers: { "Content-Type": "application/json"}, - putData: json.stringify(virtualHostNodeData), - load: function(x) {success = true; }, - error: function(error) {success = false; failureReason = error;} - }); - - if(success === true && virtualHostNodeData["type"] != "BDB_HA") - { - var encodedVirtualHostName = encodeURIComponent(virtualHostData.name); - xhr.put({ - url: "api/latest/virtualhost/" + encodedVirtualHostNodeName + "/" + encodedVirtualHostName, - sync: true, - handleAs: "json", - headers: { "Content-Type": "application/json"}, - putData: json.stringify(virtualHostData), - load: function (x) { - success = true; - }, - error: function (error) { - success = false; - failureReason = error; - } - }); - } + virtualHostNodeData["virtualHostInitialConfiguration"] = json.stringify(virtualHostData) - if (success == true) - { - this.dialog.hide(); - } - else - { - util.xhrErrorHandler(failureReason); - } } else { - alert('Form contains invalid data. Please correct first'); + alert('Form contains invalid data. Please correct first'); + return; + } + + var success = false,failureReason=null; + + var encodedVirtualHostNodeName = encodeURIComponent(virtualHostNodeData.name); + xhr.put({ + url: "api/latest/virtualhostnode/" + encodedVirtualHostNodeName, + sync: true, + handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify(virtualHostNodeData), + load: function(x) {success = true; }, + error: function(error) {success = false; failureReason = error;} + }); + + if (success == true) + { + this.dialog.hide(); + } + else + { + util.xhrErrorHandler(failureReason); } }, _getValues: function (form) diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html index a99c73fbf9..1a2b5293b6 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html @@ -114,6 +114,7 @@ <button data-dojo-type="dijit.form.Button" class="startButton" type="button" data-dojo-props="disabled: true">Start</button> <button data-dojo-type="dijit.form.Button" class="stopButton" type="button" data-dojo-props="disabled: true">Stop</button> <button data-dojo-type="dijit.form.Button" class="editButton" type="button" data-dojo-props="disabled: true">Edit</button> + <button data-dojo-type="dijit.form.Button" class="downloadButton" type="button" data-dojo-props="disabled: true">Download</button> <button data-dojo-type="dijit.form.Button" class="deleteButton" data-dojo-props="iconClass: 'dijitIconDelete'">Delete</button> </div> |