You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by vi...@apache.org on 2019/03/24 23:34:32 UTC
[drill] 05/05: DRILL-6562: Plugin Management improvements
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 <vi...@gmail.com>
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>