This is an automated email from the ASF dual-hosted git repository. vitalii pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/drill.git
commit be73250e68ffd836541223cf4aca395896362866 Author: Vitalii Diravka <[email protected]> AuthorDate: Mon Jul 23 13:03:50 2018 +0300 DRILL-6562: Plugin Management improvements - allow export plugin configs to json or hocon file formt - allow export plugins configs for all/enabled/disabled groups - add modals for export plugins and create new plugin - storage UI improvements,responsive Storage page - StorageResources refactoring. Remove redundant deletePlugin() DELETE request - fix broken message for deletePlugin closes #1692 --- .../drill/exec/server/rest/LogsResources.java | 15 +- .../drill/exec/server/rest/StorageResources.java | 150 +++++++------ exec/java-exec/src/main/resources/rest/options.ftl | 4 +- .../main/resources/rest/static/js/serverMessage.js | 35 +++ .../src/main/resources/rest/storage/list.ftl | 250 ++++++++++++++++++--- .../src/main/resources/rest/storage/update.ftl | 120 ++++++---- 6 files changed, 424 insertions(+), 150 deletions(-) diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogsResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogsResources.java index 51cf994..4aa2061 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogsResources.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogsResources.java @@ -35,6 +35,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; @@ -44,7 +45,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileReader; -import java.io.FilenameFilter; import java.io.IOException; import java.util.Collection; import java.util.LinkedHashMap; @@ -138,9 +138,9 @@ public class LogsResources { @Produces(MediaType.TEXT_PLAIN) public Response getFullLog(@PathParam("name") final String name) { File file = getFileByName(getLogFolder(), name); - Response.ResponseBuilder response = Response.ok(file); - response.header("Content-Disposition", String.format("attachment;filename=\"%s\"", name)); - return response.build(); + return Response.ok(file) + .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", name)) + .build(); } private File getLogFolder() { @@ -148,12 +148,7 @@ public class LogsResources { } private File getFileByName(File folder, final String name) { - File[] files = folder.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String fileName) { - return fileName.equals(name); - } - }); + File[] files = folder.listFiles((dir, fileName) -> fileName.equals(name)); if (files.length == 0) { throw new DrillRuntimeException (name + " doesn't exist"); } diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java index 1d6d148..8d71bdb 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java @@ -19,26 +19,31 @@ package org.apache.drill.exec.server.rest; import java.io.IOException; import java.io.StringReader; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.xml.bind.annotation.XmlRootElement; +import com.fasterxml.jackson.core.JsonParser; import org.apache.drill.common.exceptions.ExecutionSetupException; import org.apache.drill.common.logical.StoragePluginConfig; import org.apache.drill.exec.server.rest.DrillRestServer.UserAuthEnabled; @@ -49,7 +54,6 @@ import org.glassfish.jersey.server.mvc.Viewable; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.drill.shaded.guava.com.google.common.collect.Lists; import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.ADMIN_ROLE; @@ -63,34 +67,35 @@ public class StorageResources { @Inject ObjectMapper mapper; @Inject SecurityContext sc; - private static final Comparator<PluginConfigWrapper> PLUGIN_COMPARATOR = new Comparator<PluginConfigWrapper>() { - @Override - public int compare(PluginConfigWrapper o1, PluginConfigWrapper o2) { - return o1.getName().compareTo(o2.getName()); - } - }; + private static final String JSON_FORMAT = "json"; + private static final String HOCON_FORMAT = "conf"; + private static final String ALL_PLUGINS = "all"; + private static final String ENABLED_PLUGINS = "enabled"; + private static final String DISABLED_PLUGINS = "disabled"; + + private static final Comparator<PluginConfigWrapper> PLUGIN_COMPARATOR = + Comparator.comparing(PluginConfigWrapper::getName); @GET - @Path("/storage.json") + @Path("/storage/{group}/plugins/export/{format}") @Produces(MediaType.APPLICATION_JSON) - public List<PluginConfigWrapper> getStoragePluginsJSON() { - - List<PluginConfigWrapper> list = Lists.newArrayList(); - for (Map.Entry<String, StoragePluginConfig> entry : Lists.newArrayList(storage.getStore().getAll())) { - PluginConfigWrapper plugin = new PluginConfigWrapper(entry.getKey(), entry.getValue()); - list.add(plugin); - } - - Collections.sort(list, PLUGIN_COMPARATOR); - - return list; + public Response getConfigsFor(@PathParam("group") String pluginGroup, @PathParam("format") String format) { + return isSupported(format) + ? Response.ok() + .entity(getConfigsFor(pluginGroup).toArray()) + .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s_storage_plugins.%s\"", + pluginGroup, format)) + .build() + : Response.status(Response.Status.NOT_FOUND.getStatusCode(), + String.format("Unknown file type %s for %s Storage Plugin Configs", format, pluginGroup)) + .build(); } @GET @Path("/storage") @Produces(MediaType.TEXT_HTML) - public Viewable getStoragePlugins() { - List<PluginConfigWrapper> list = getStoragePluginsJSON(); + public Viewable getPlugins() { + List<PluginConfigWrapper> list = getConfigsFor(ALL_PLUGINS); return ViewableWithPermissions.create(authEnabled.get(), "/rest/storage/list.ftl", sc, list); } @@ -98,7 +103,7 @@ public class StorageResources { @GET @Path("/storage/{name}.json") @Produces(MediaType.APPLICATION_JSON) - public PluginConfigWrapper getStoragePluginJSON(@PathParam("name") String name) { + public PluginConfigWrapper getPluginConfig(@PathParam("name") String name) { try { // TODO: DRILL-6412: No need to get StoragePlugin. It is enough to have plugin name and config here StoragePlugin plugin = storage.getPlugin(name); @@ -106,7 +111,7 @@ public class StorageResources { return new PluginConfigWrapper(name, plugin.getConfig()); } } catch (Exception e) { - logger.info("Failure while trying to access storage config: {}", name, e); + logger.error("Failure while trying to access storage config: {}", name, e); } return new PluginConfigWrapper(name, null); } @@ -114,54 +119,46 @@ public class StorageResources { @GET @Path("/storage/{name}") @Produces(MediaType.TEXT_HTML) - public Viewable getStoragePlugin(@PathParam("name") String name) { - PluginConfigWrapper plugin = getStoragePluginJSON(name); - return ViewableWithPermissions.create(authEnabled.get(), "/rest/storage/update.ftl", sc, plugin); + public Viewable getPlugin(@PathParam("name") String name) { + return ViewableWithPermissions.create(authEnabled.get(), "/rest/storage/update.ftl", sc, + getPluginConfig(name)); } @GET @Path("/storage/{name}/enable/{val}") @Produces(MediaType.APPLICATION_JSON) public JsonResult enablePlugin(@PathParam("name") String name, @PathParam("val") Boolean enable) { - PluginConfigWrapper plugin = getStoragePluginJSON(name); + PluginConfigWrapper plugin = getPluginConfig(name); try { - if (plugin.setEnabledInStorage(storage, enable)) { - return message("success"); - } else { - return message("error (plugin does not exist)"); - } + return plugin.setEnabledInStorage(storage, enable) + ? message("Success") + : message("Error (plugin does not exist)"); } catch (ExecutionSetupException e) { - logger.debug("Error in enabling storage name: " + name + " flag: " + enable); - return message("error (unable to enable/ disable storage)"); + logger.debug("Error in enabling storage name: {} flag: {}", name, enable); + return message("Error (unable to enable / disable storage)"); } } @GET - @Path("/storage/{name}/export") - @Produces(MediaType.APPLICATION_JSON) - public Response exportPlugin(@PathParam("name") String name) { - Response.ResponseBuilder response = Response.ok(getStoragePluginJSON(name)); - response.header("Content-Disposition", String.format("attachment;filename=\"%s.json\"", name)); - return response.build(); - } - - @DELETE - @Path("/storage/{name}.json") + @Path("/storage/{name}/export/{format}") @Produces(MediaType.APPLICATION_JSON) - public JsonResult deletePluginJSON(@PathParam("name") String name) { - PluginConfigWrapper plugin = getStoragePluginJSON(name); - if (plugin.deleteFromStorage(storage)) { - return message("success"); - } else { - return message("error (unable to delete storage)"); - } + public Response exportPlugin(@PathParam("name") String name, @PathParam("format") String format) { + return isSupported(format) + ? Response.ok(getPluginConfig(name)) + .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s.%s\"", name, format)) + .build() + : Response.status(Response.Status.NOT_FOUND.getStatusCode(), + String.format("Unknown file type %s for Storage Plugin Config: %s", format, name)) + .build(); } @GET @Path("/storage/{name}/delete") @Produces(MediaType.APPLICATION_JSON) public JsonResult deletePlugin(@PathParam("name") String name) { - return deletePluginJSON(name); + return getPluginConfig(name).deleteFromStorage(storage) + ? message("Success") + : message("Error (unable to delete %s storage plugin)", name); } @POST @@ -171,35 +168,62 @@ public class StorageResources { public JsonResult createOrUpdatePluginJSON(PluginConfigWrapper plugin) { try { plugin.createOrUpdateInStorage(storage); - return message("success"); + return message("Success"); } catch (ExecutionSetupException e) { logger.error("Unable to create/ update plugin: " + plugin.getName(), e); - return message("Error while creating/ updating storage : " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())); + return message("Error while creating / updating storage : %s", e.getCause() == null ? e.getMessage() : + e.getCause().getMessage()); } } @POST - @Path("/storage/{name}") - @Consumes("application/x-www-form-urlencoded") + @Path("/storage/create_update") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) public JsonResult createOrUpdatePlugin(@FormParam("name") String name, @FormParam("config") String storagePluginConfig) { try { + mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); StoragePluginConfig config = mapper.readValue(new StringReader(storagePluginConfig), StoragePluginConfig.class); return createOrUpdatePluginJSON(new PluginConfigWrapper(name, config)); } catch (JsonMappingException e) { logger.debug("Error in JSON mapping: {}", storagePluginConfig, e); - return message("error (invalid JSON mapping)"); + return message("Error (invalid JSON mapping)"); } catch (JsonParseException e) { logger.debug("Error parsing JSON: {}", storagePluginConfig, e); - return message("error (unable to parse JSON)"); + return message("Error (unable to parse JSON)"); } catch (IOException e) { logger.debug("Failed to read: {}", storagePluginConfig, e); - return message("error (unable to read)"); + return message("Error (unable to read)"); } } - private JsonResult message(String message) { - return new JsonResult(message); + private JsonResult message(String message, Object... args) { + return new JsonResult(String.format(message, args)); + } + + private boolean isSupported(String format) { + return JSON_FORMAT.equalsIgnoreCase(format) || HOCON_FORMAT.equalsIgnoreCase(format); + } + + private List<PluginConfigWrapper> getConfigsFor(String pluginGroup) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(storage.getStore().getAll(), Spliterator.ORDERED), false) + .filter(byPluginGroup(pluginGroup)) + .map(entry -> new PluginConfigWrapper(entry.getKey(), entry.getValue())) + .sorted(PLUGIN_COMPARATOR) + .collect(Collectors.toList()); + } + + private Predicate<Map.Entry<String, StoragePluginConfig>> byPluginGroup(String pluginGroup) { + if (ALL_PLUGINS.equalsIgnoreCase(pluginGroup)) { + return entry -> true; + } else if (ENABLED_PLUGINS.equalsIgnoreCase(pluginGroup)) { + return entry -> entry.getValue().isEnabled(); + } else if (DISABLED_PLUGINS.equalsIgnoreCase(pluginGroup)) { + return entry -> !entry.getValue().isEnabled(); + } else { + return entry -> false; + } } @XmlRootElement diff --git a/exec/java-exec/src/main/resources/rest/options.ftl b/exec/java-exec/src/main/resources/rest/options.ftl index 9b934e9..d05dd7f 100644 --- a/exec/java-exec/src/main/resources/rest/options.ftl +++ b/exec/java-exec/src/main/resources/rest/options.ftl @@ -47,10 +47,10 @@ let optionKind = $("#"+optionName+" input[name='kind']").attr("value"); //Extracting value from the form's INPUT element let optionValue = $("#"+optionName+" input[name='value']").val(); - if (optionKind == "BOOLEAN") { + if (optionKind === "BOOLEAN") { //Extracting boolean value from the form's SELECT element (since this is a dropdown input) optionValue = $("#"+optionName+" select[name='value']").val(); - } else if (optionKind != "STRING") { //i.e. it is a number (FLOAT/DOUBLE/LONG) + } else if (optionKind !== "STRING") { //i.e. it is a number (FLOAT/DOUBLE/LONG) if (isNaN(optionValue)) { let actualOptionName=optionName.replace(/\\\./gi, "."); let alertValues = {'_numericOption_': optionValue, '_optionName_': actualOptionName }; diff --git a/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js b/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js new file mode 100644 index 0000000..aeb9960 --- /dev/null +++ b/exec/java-exec/src/main/resources/rest/static/js/serverMessage.js @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// Shows Json message from the server +function serverMessage(data) { + const messageEl = $("#message"); + if (data.result === "Success") { + messageEl.removeClass("hidden") + .removeClass("alert-danger") + .addClass("alert-info") + .text(data.result).alert(); + setTimeout(function() { location.reload(); }, 800); + } else { + messageEl.addClass("hidden"); + // Wait a fraction of a second before showing the message again. This + // makes it clear if a second attempt gives the same error as + // the first that a "new" message came back from the server + setTimeout(function() { + messageEl.removeClass("hidden") + .removeClass("alert-info") + .addClass("alert-danger") + .text("Please retry: " + data.result).alert(); + }, 200); + } +} diff --git a/exec/java-exec/src/main/resources/rest/storage/list.ftl b/exec/java-exec/src/main/resources/rest/storage/list.ftl index 016c019..c821a9d 100644 --- a/exec/java-exec/src/main/resources/rest/storage/list.ftl +++ b/exec/java-exec/src/main/resources/rest/storage/list.ftl @@ -17,27 +17,60 @@ limitations under the License. --> + <#include "*/generic.ftl"> <#macro page_head> + <script src="/static/js/jquery.form.js"></script> + + <!-- Ace Libraries for Syntax Formatting --> + <script src="/static/js/ace-code-editor/ace.js" type="text/javascript" charset="utf-8"></script> + <script src="/static/js/ace-code-editor/theme-eclipse.js" type="text/javascript" charset="utf-8"></script> + <script src="/static/js/serverMessage.js"></script> </#macro> <#macro page_body> <div class="page-header"> </div> - <h4>Enabled Storage Plugins</h4> - <div class="table-responsive"> - <table class="table"> + + <h4 class="col-xs-6">Plugin Management</h4> + <table style="margin: 10px" class="table"> + <tbody> + <tr> + <td style="border:none;"> + <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#new-plugin-modal"> + Create + </button> + <button type="button" class="btn btn-primary" name="all" data-toggle="modal" data-target="#pluginsModal"> + Export all + </button> + </td> + </tr> + </tbody> + </table> + + <div class="page-header" style="margin: 5px;"></div> + + <div class="table-responsive col-sm-12 col-md-6 col-lg-5 col-xl-5"> + <h4>Enabled Storage Plugins</h4> + <table class="table table-hover"> <tbody> <#list model as plugin> <#if plugin.enabled() == true> <tr> - <td style="border:none; width:200px;"> + <td style="border:none; max-width: 200px; overflow: hidden; text-overflow: ellipsis;"> ${plugin.getName()} </td> <td style="border:none;"> - <a class="btn btn-primary" href="/storage/${plugin.getName()}">Update</a> - <a class="btn btn-default" onclick="doEnable('${plugin.getName()}', false)">Disable</a> - <a class="btn btn-default" href="/storage/${plugin.getName()}/export"">Export</a> + <button type="button" class="btn btn-primary" onclick="location.href='/storage/${plugin.getName()}'"> + Update + </button> + <button type="button" class="btn btn-warning" onclick="doEnable('${plugin.getName()}', false)"> + Disable + </button> + <button type="button" class="btn" name="${plugin.getName()}" data-toggle="modal" + data-target="#pluginsModal"> + Export + </button> </td> </tr> </#if> @@ -45,21 +78,28 @@ </tbody> </table> </div> - <div class="page-header"> - </div> - <h4>Disabled Storage Plugins</h4> - <div class="table-responsive"> - <table class="table"> + + <div class="table-responsive col-sm-12 col-md-6 col-lg-7 col-xl-7"> + <h4>Disabled Storage Plugins</h4> + <table class="table table-hover"> <tbody> <#list model as plugin> <#if plugin.enabled() == false> <tr> - <td style="border:none; width:200px;"> + <td style="border:none; max-width: 200px; overflow: hidden; text-overflow: ellipsis;"> ${plugin.getName()} </td> <td style="border:none;"> - <a class="btn btn-primary" href="/storage/${plugin.getName()}">Update</a> - <a class="btn btn-primary" onclick="doEnable('${plugin.getName()}', true)">Enable</a> + <button type="button" class="btn btn-primary" onclick="location.href='/storage/${plugin.getName()}'"> + Update + </button> + <button type="button" class="btn btn-success" onclick="doEnable('${plugin.getName()}', true)"> + Enable + </button> + <button type="button" class="btn" name="${plugin.getName()}" data-toggle="modal" + data-target="#pluginsModal"> + Export + </button> </td> </tr> </#if> @@ -67,29 +107,175 @@ </tbody> </table> </div> - <div class="page-header"> + + + <#-- Modal window for exporting plugin config (including group plugins modal) --> + <div class="modal fade" id="pluginsModal" tabindex="-1" role="dialog" aria-labelledby="exportPlugin" aria-hidden="true"> + <div class="modal-dialog modal-sm" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="exportPlugin">Plugin config</h4> + </div> + <div class="modal-body"> + <div id="format" style="display: inline-block; position: relative;"> + <label for="format">Format</label> + <div class="radio"> + <label> + <input type="radio" name="format" id="json" value="json" checked="checked"> + JSON + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="format" id="hocon" value="conf"> + HOCON + </label> + </div> + </div> + + <div id="plugin-set" class="" style="display: inline-block; position: relative; float: right;"> + <label for="format">Plugin group</label> + <div class="radio"> + <label> + <input type="radio" name="group" id="all" value="all" checked="checked"> + ALL + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="group" id="enabled" value="enabled"> + ENABLED + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="group" id="disabled" value="disabled"> + DISABLED + </label> + </div> + </div> + </div> + + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" id="export" class="btn btn-primary">Export</button> + </div> + </div> + </div> </div> - <div> - <h4>New Storage Plugin</h4> - <form class="form-inline" id="newStorage" role="form" action="/" method="GET"> - <div class="form-group"> - <input type="text" class="form-control" id="storageName" placeholder="Storage Name"> + <#-- Modal window for exporting plugin config (including group plugins modal) --> + + <#-- Modal window for creating plugin --> + <div class="modal fade" id="new-plugin-modal" role="dialog" aria-labelledby="configuration"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="configuration">New Storage Plugin</h4> + </div> + <div class="modal-body"> + + <form id="createForm" role="form" action="/storage/create_update" method="POST"> + <input type="text" class="form-control" name="name" placeholder="Storage Name"> + <h3>Configuration</h3> + <div class="form-group"> + <div id="editor" class="form-control"></div> + <textarea class="form-control" id="config" name="config" data-editor="json" style="display: none;"> + </textarea> + </div> + <div style="text-align: right; margin: 10px"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="submit" class="btn btn-primary" onclick="doCreate()">Create</button> + </div> + </form> + + <div id="message" class="hidden alert alert-info"> + </div> + </div> </div> - <button type="submit" class="btn btn-default" onclick="doSubmit()">Create</button> - </form> + </div> </div> + <#-- Modal window for creating plugin --> + <script> - function doSubmit() { - var name = document.getElementById("storageName"); - var form = document.getElementById("newStorage"); - form.action = "/storage/" + name.value; - form.submit(); - }; function doEnable(name, flag) { - $.get("/storage/" + name + "/enable/" + flag, function(data) { - location.reload(); + if (flag || confirm(name + ' plugin will be disabled')) { + $.get("/storage/" + name + "/enable/" + flag, function() { + location.reload(); + }); + } + } + + function doCreate() { + $("#createForm").ajaxForm({ + dataType: 'json', + success: serverMessage + }); + } + + // Formatting create plugin textarea + $('#new-plugin-modal').on('show.bs.modal', function() { + const editor = ace.edit("editor"); + const textarea = $('textarea[name="config"]'); + + editor.setAutoScrollEditorIntoView(true); + editor.setOption("maxLines", 25); + editor.setOption("minLines", 10); + editor.renderer.setShowGutter(true); + editor.renderer.setOption('showLineNumbers', true); + editor.renderer.setOption('showPrintMargin', false); + editor.getSession().setMode("ace/mode/json"); + editor.setTheme("ace/theme/eclipse"); + + // copy back to textarea on form submit... + editor.getSession().on('change', function(){ + textarea.val(editor.getSession().getValue()); + }); + }); + + // Modal windows management + let exportInstance; // global variable + $('#pluginsModal').on('show.bs.modal', function(event) { + console.log("alarm"); + const button = $(event.relatedTarget); // Button that triggered the modal + const modal = $(this); + exportInstance = button.attr("name"); + + const optionalBlock = modal.find('#plugin-set'); + if (exportInstance === "all") { + optionalBlock.removeClass('hide'); + modal.find('.modal-title').text('Export all Plugins configs'); + } else { + modal.find('#plugin-set').addClass('hide'); + modal.find('.modal-title').text(exportInstance.toUpperCase() + ' Plugin config'); + } + + modal.find('#export').click(function() { + let format; + if (modal.find('#json').is(":checked")) { + format = 'json'; + } + if (modal.find('#hocon').is(":checked")) { + format = 'conf'; + } + let url; + if (exportInstance === "all") { + let pluginGroup = ""; + if (modal.find('#all').is(":checked")) { + pluginGroup = 'all'; + } else if (modal.find('#enabled').is(":checked")) { + pluginGroup = 'enabled'; + } else if (modal.find('#disabled').is(":checked")) { + pluginGroup = 'disabled'; + } + url = '/storage/' + pluginGroup + '/plugins/export/' + format; + } else { + url = '/storage/' + exportInstance + '/export/' + format; + } + window.open(url); }); - }; + }); </script> </#macro> diff --git a/exec/java-exec/src/main/resources/rest/storage/update.ftl b/exec/java-exec/src/main/resources/rest/storage/update.ftl index 4247963..3e827f7 100644 --- a/exec/java-exec/src/main/resources/rest/storage/update.ftl +++ b/exec/java-exec/src/main/resources/rest/storage/update.ftl @@ -17,20 +17,21 @@ limitations under the License. --> + <#include "*/generic.ftl"> <#macro page_head> <script src="/static/js/jquery.form.js"></script> - <!-- Ace Libraries for Syntax Formatting --> <script src="/static/js/ace-code-editor/ace.js" type="text/javascript" charset="utf-8"></script> <script src="/static/js/ace-code-editor/theme-eclipse.js" type="text/javascript" charset="utf-8"></script> + <script src="/static/js/serverMessage.js"></script> </#macro> <#macro page_body> <div class="page-header"> </div> <h3>Configuration</h3> - <form id="updateForm" role="form" action="/storage/${model.getName()}" method="POST"> + <form id="updateForm" role="form" action="/storage/create_update" method="POST"> <input type="hidden" name="name" value="${model.getName()}" /> <div class="form-group"> <div id="editor" class="form-control"></div> @@ -38,26 +39,58 @@ </textarea> </div> <a class="btn btn-default" href="/storage">Back</a> - <button class="btn btn-default" type="submit" onclick="doUpdate();"> - <#if model.exists()>Update<#else>Create</#if> - </button> - <#if model.exists()> - <#if model.enabled()> - <a id="enabled" class="btn btn-default">Disable</a> - <#else> - <a id="enabled" class="btn btn-primary">Enable</a> - </#if> - <a class="btn btn-default" href="/storage/${model.getName()}/export"">Export</a> - <a id="del" class="btn btn-danger" onclick="deleteFunction()">Delete</a> + <button class="btn btn-default" type="submit" onclick="doUpdate();">Update</button> + <#if model.enabled()> + <a id="enabled" class="btn btn-default">Disable</a> + <#else> + <a id="enabled" class="btn btn-primary">Enable</a> </#if> + <button type="button" class="btn btn-default export" name="${model.getName()}" data-toggle="modal" + data-target="#pluginsModal"> + Export + </button> + <a id="del" class="btn btn-danger" onclick="deleteFunction()">Delete</a> </form> <br> <div id="message" class="hidden alert alert-info"> </div> - <script> - var editor = ace.edit("editor"); - var textarea = $('textarea[name="config"]'); + <#-- Modal window--> + <div class="modal fade" id="pluginsModal" tabindex="-1" role="dialog" aria-labelledby="exportPlugin" aria-hidden="true"> + <div class="modal-dialog modal-sm" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="exportPlugin">Plugin config</h4> + </div> + <div class="modal-body"> + <div id="format" style="display: inline-block; position: relative;"> + <label for="format">Format</label> + <div class="radio"> + <label> + <input type="radio" name="format" id="json" value="json" checked="checked"> + JSON + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="format" id="hocon" value="conf"> + HOCON + </label> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" id="export" class="btn btn-primary">Export</button> + </div> + </div> + </div> + </div> + + <script> + const editor = ace.edit("editor"); + const textarea = $('textarea[name="config"]'); editor.setAutoScrollEditorIntoView(true); editor.setOption("maxLines", 25); @@ -86,36 +119,37 @@ }); }); function doUpdate() { - $("#updateForm").ajaxForm(function(data) { - var messageEl = $("#message"); - if (data.result == "success") { - messageEl.removeClass("hidden") - .removeClass("alert-danger") - .addClass("alert-info") - .text(data.result).alert(); - setTimeout(function() { location.reload(); }, 800); - } else { - messageEl.addClass("hidden"); - // Wait a fraction of a second before showing the message again. This - // makes it clear if a second attempt gives the same error as - // the first that a "new" message came back from the server - setTimeout(function() { - messageEl.removeClass("hidden") - .removeClass("alert-info") - .addClass("alert-danger") - .text("Please retry: " + data.result).alert(); - }, 200); - } + $("#updateForm").ajaxForm({ + dataType: 'json', + success: serverMessage }); - }; + } + function deleteFunction() { - var temp = confirm("Are you sure?"); - if (temp == true) { - $.get("/storage/${model.getName()}/delete", function(data) { - window.location.href = "/storage"; - }); + if (confirm("Are you sure?")) { + $.get("/storage/${model.getName()}/delete", serverMessage); } - }; + } + + // Modal window management + $('#pluginsModal').on('show.bs.modal', function (event) { + const button = $(event.relatedTarget); // Button that triggered the modal + let exportInstance = button.attr("name"); + const modal = $(this); + modal.find('.modal-title').text(exportInstance.toUpperCase() +' Plugin configs'); + modal.find('.btn-primary').click(function(){ + let format = ""; + if (modal.find('#json').is(":checked")) { + format = 'json'; + } + if (modal.find('#hocon').is(":checked")) { + format = 'conf'; + } + + let url = '/storage/' + exportInstance + '/export/' + format; + window.open(url); + }); + }) </script> </#macro>
