Browse Source

fix #2229 upload jar file

Shinsuke Sugaya 5 years ago
parent
commit
0001a8e909

+ 55 - 6
src/main/java/org/codelibs/fess/app/web/admin/plugin/AdminPluginAction.java

@@ -15,6 +15,10 @@
  */
 package org.codelibs.fess.app.web.admin.plugin;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -22,9 +26,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.codelibs.core.io.CopyUtil;
 import org.codelibs.fess.app.web.base.FessAdminAction;
 import org.codelibs.fess.helper.PluginHelper;
 import org.codelibs.fess.helper.PluginHelper.Artifact;
+import org.codelibs.fess.helper.PluginHelper.ArtifactType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.lastaflute.web.Execute;
@@ -37,6 +43,8 @@ public class AdminPluginAction extends FessAdminAction {
 
     private static final Logger logger = LoggerFactory.getLogger(AdminPluginAction.class);
 
+    private static final String UPLOAD = "upload";
+
     @Override
     protected void setupHtmlData(final ActionRuntime runtime) {
         super.setupHtmlData(runtime);
@@ -53,7 +61,7 @@ public class AdminPluginAction extends FessAdminAction {
     public HtmlResponse delete(final DeleteForm form) {
         validate(form, messages -> {}, () -> asHtml(path_AdminPlugin_AdminPluginJsp));
         verifyToken(() -> asHtml(path_AdminPlugin_AdminPluginJsp));
-        final Artifact artifact = new Artifact(form.name, form.version);
+        final Artifact artifact = new Artifact(form.name, form.version, null);
         deleteArtifact(artifact);
         saveInfo(messages -> messages.addSuccessDeletePlugin(GLOBAL, artifact.getFileName()));
         return redirect(getClass());
@@ -63,12 +71,48 @@ public class AdminPluginAction extends FessAdminAction {
     public HtmlResponse install(final InstallForm form) {
         validate(form, messages -> {}, () -> asHtml(path_AdminPlugin_AdminPluginInstallpluginJsp));
         verifyToken(() -> asHtml(path_AdminPlugin_AdminPluginInstallpluginJsp));
-        final Artifact artifact = getArtifactFromInstallForm(form);
-        if (artifact == null) {
-            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asListHtml());
+        if (UPLOAD.equals(form.id)) {
+            if (form.jarFile == null) {
+                throwValidationError(messages -> messages.addErrorsPluginFileIsNotFound(GLOBAL, form.id), () -> asListHtml());
+            }
+            if (!form.jarFile.getFileName().endsWith(".jar")) {
+                throwValidationError(messages -> messages.addErrorsFileIsNotSupported(GLOBAL, form.jarFile.getFileName()),
+                        () -> asListHtml());
+            }
+            final String filename = form.jarFile.getFileName();
+            final File tempFile = ComponentUtil.getSystemHelper().createTempFile("tmp-adminplugin-", ".jar");
+            try (final InputStream is = form.jarFile.getInputStream(); final OutputStream os = new FileOutputStream(tempFile)) {
+                CopyUtil.copy(is, os);
+            } catch (final Exception e) {
+                if (tempFile.exists() && !tempFile.delete()) {
+                    logger.warn("Failed to delete {}.", tempFile.getAbsolutePath());
+                }
+                logger.debug("Failed to copy " + filename, e);
+                throwValidationError(messages -> messages.addErrorsFailedToInstallPlugin(GLOBAL, filename), () -> asListHtml());
+            }
+            new Thread(() -> {
+                try {
+                    final PluginHelper pluginHelper = ComponentUtil.getPluginHelper();
+                    final Artifact artifact =
+                            pluginHelper.getArtifactFromFileName(ArtifactType.UNKNOWN, filename, tempFile.getAbsolutePath());
+                    pluginHelper.installArtifact(artifact);
+                } catch (final Exception e) {
+                    logger.warn("Failed to install " + filename, e);
+                } finally {
+                    if (tempFile.exists() && !tempFile.delete()) {
+                        logger.warn("Failed to delete {}.", tempFile.getAbsolutePath());
+                    }
+                }
+            }).start();
+            saveInfo(messages -> messages.addSuccessInstallPlugin(GLOBAL, form.jarFile.getFileName()));
+        } else {
+            final Artifact artifact = getArtifactFromInstallForm(form);
+            if (artifact == null) {
+                throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asListHtml());
+            }
+            installArtifact(artifact);
+            saveInfo(messages -> messages.addSuccessInstallPlugin(GLOBAL, artifact.getFileName()));
         }
-        installArtifact(artifact);
-        saveInfo(messages -> messages.addSuccessInstallPlugin(GLOBAL, artifact.getFileName()));
         return redirect(getClass());
     }
 
@@ -88,6 +132,11 @@ public class AdminPluginAction extends FessAdminAction {
     public static List<Map<String, String>> getAllAvailableArtifacts() {
         final PluginHelper pluginHelper = ComponentUtil.getPluginHelper();
         final List<Map<String, String>> result = new ArrayList<>();
+        final Map<String, String> map = new HashMap<>();
+        map.put("id", UPLOAD);
+        map.put("name", "");
+        map.put("version", "");
+        result.add(map);
         for (final PluginHelper.ArtifactType artifactType : PluginHelper.ArtifactType.values()) {
             result.addAll(Arrays.stream(pluginHelper.getAvailableArtifacts(artifactType)).map(AdminPluginAction::beanToMap)
                     .collect(Collectors.toList()));

+ 2 - 0
src/main/java/org/codelibs/fess/app/web/admin/plugin/InstallForm.java

@@ -17,6 +17,7 @@ package org.codelibs.fess.app.web.admin.plugin;
 
 import javax.validation.constraints.Size;
 
+import org.lastaflute.web.ruts.multipart.MultipartFormFile;
 import org.lastaflute.web.validation.Required;
 
 public class InstallForm {
@@ -25,4 +26,5 @@ public class InstallForm {
     @Size(max = 400)
     public String id;
 
+    public MultipartFormFile jarFile;
 }

+ 37 - 21
src/main/java/org/codelibs/fess/helper/PluginHelper.java

@@ -19,6 +19,7 @@ import static org.codelibs.core.stream.StreamUtil.split;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
@@ -65,7 +66,8 @@ public class PluginHelper {
 
     protected LoadingCache<ArtifactType, Artifact[]> availableArtifacts = CacheBuilder.newBuilder().maximumSize(10)
             .expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<ArtifactType, Artifact[]>() {
-                public Artifact[] load(ArtifactType key) {
+                @Override
+                public Artifact[] load(final ArtifactType key) {
                     final List<Artifact> list = new ArrayList<>();
                     for (final String url : getRepositories()) {
                         if (url.endsWith(".yaml")) {
@@ -95,16 +97,16 @@ public class PluginHelper {
 
     protected List<Artifact> loadArtifactsFromRepository(final String url) {
         final String content = getRepositoryContent(url);
-        ObjectMapper objectMapper = new YAMLMapper();
+        final ObjectMapper objectMapper = new YAMLMapper();
         try {
             @SuppressWarnings("unchecked")
-            List<Map<?, ?>> result = objectMapper.readValue(content, List.class);
+            final List<Map<?, ?>> result = objectMapper.readValue(content, List.class);
             if (result != null) {
                 return result.stream().map(o -> new Artifact((String) o.get("name"), (String) o.get("version"), (String) o.get("url")))
                         .collect(Collectors.toList());
             }
             return Collections.emptyList();
-        } catch (Exception e) {
+        } catch (final Exception e) {
             throw new PluginException("Failed to access " + url, e);
         }
     }
@@ -129,7 +131,7 @@ public class PluginHelper {
                         if (version.endsWith("SNAPSHOT")) {
                             final String snapshotVersion = getSnapshotActualVersion(builder, pluginUrl, version);
                             if (StringUtil.isNotBlank(snapshotVersion)) {
-                                String actualVersion = version.replace("SNAPSHOT", snapshotVersion);
+                                final String actualVersion = version.replace("SNAPSHOT", snapshotVersion);
                                 list.add(new Artifact(name, actualVersion, pluginUrl + version + "/" + name + "-" + actualVersion + ".jar"));
                             } else if (logger.isDebugEnabled()) {
                                 logger.debug("Snapshot name is not found: " + name + "/" + version);
@@ -161,7 +163,7 @@ public class PluginHelper {
             final Document doc = builder.parse(is);
             final NodeList snapshotNodeList = doc.getElementsByTagName("snapshot");
             if (snapshotNodeList.getLength() > 0) {
-                NodeList nodeList = snapshotNodeList.item(0).getChildNodes();
+                final NodeList nodeList = snapshotNodeList.item(0).getChildNodes();
                 for (int i = 0; i < nodeList.getLength(); i++) {
                     final Node node = nodeList.item(i);
                     if ("timestamp".equalsIgnoreCase(node.getNodeName())) {
@@ -192,7 +194,7 @@ public class PluginHelper {
     public Artifact[] getInstalledArtifacts(final ArtifactType artifactType) {
         if (artifactType == ArtifactType.UNKNOWN) {
             final File[] jarFiles = ResourceUtil.getPluginJarFiles((d, n) -> {
-                for (ArtifactType type : ArtifactType.values()) {
+                for (final ArtifactType type : ArtifactType.values()) {
                     if (n.startsWith(type.getId())) {
                         return false;
                     }
@@ -216,12 +218,16 @@ public class PluginHelper {
         return list.toArray(new Artifact[list.size()]);
     }
 
-    protected Artifact getArtifactFromFileName(final ArtifactType artifactType, final String name) {
-        String baseName = StringUtils.removeEndIgnoreCase(name, ".jar");
-        List<String> nameList = new ArrayList<>();
-        List<String> versionList = new ArrayList<>();
+    protected Artifact getArtifactFromFileName(final ArtifactType artifactType, final String filename) {
+        return getArtifactFromFileName(artifactType, filename, null);
+    }
+
+    public Artifact getArtifactFromFileName(final ArtifactType artifactType, final String filename, final String url) {
+        final String baseName = StringUtils.removeEndIgnoreCase(filename, ".jar");
+        final List<String> nameList = new ArrayList<>();
+        final List<String> versionList = new ArrayList<>();
         boolean isName = true;
-        for (String value : baseName.split("-")) {
+        for (final String value : baseName.split("-")) {
             if (isName && value.length() > 0 && value.charAt(0) >= '0' && value.charAt(0) <= '9') {
                 isName = false;
             }
@@ -231,21 +237,31 @@ public class PluginHelper {
                 versionList.add(value);
             }
         }
-        return new Artifact(nameList.stream().collect(Collectors.joining("-")), versionList.stream().collect(Collectors.joining("-")));
+        return new Artifact(nameList.stream().collect(Collectors.joining("-")), versionList.stream().collect(Collectors.joining("-")), url);
     }
 
     public void installArtifact(final Artifact artifact) {
         final String fileName = artifact.getFileName();
-        try (final CurlResponse response = Curl.get(artifact.getUrl()).execute()) {
-            if (response.getHttpStatusCode() != 200) {
-                throw new PluginException("HTTP Status " + response.getHttpStatusCode() + " : failed to get the artifact from "
-                        + artifact.getUrl());
+        final String url = artifact.getUrl();
+        if (StringUtil.isBlank(url)) {
+            throw new PluginException("url is blank: " + artifact.getName());
+        } else if (url.startsWith("http:") || url.startsWith("https:")) {
+            try (final CurlResponse response = Curl.get(url).execute()) {
+                if (response.getHttpStatusCode() != 200) {
+                    throw new PluginException("HTTP Status " + response.getHttpStatusCode() + " : failed to get the artifact from " + url);
+                }
+                try (final InputStream in = response.getContentAsStream()) {
+                    CopyUtil.copy(in, ResourceUtil.getPluginPath(fileName).toFile());
+                }
+            } catch (final Exception e) {
+                throw new PluginException("Failed to install the artifact " + artifact.getName(), e);
             }
-            try (final InputStream in = response.getContentAsStream()) {
+        } else {
+            try (final InputStream in = new FileInputStream(url)) {
                 CopyUtil.copy(in, ResourceUtil.getPluginPath(fileName).toFile());
+            } catch (final Exception e) {
+                throw new PluginException("Failed to install the artifact " + artifact.getName(), e);
             }
-        } catch (final Exception e) {
-            throw new PluginException("Failed to install the artifact " + artifact.getName(), e);
         }
 
         switch (artifact.getType()) {
@@ -283,7 +299,7 @@ public class PluginHelper {
         }
     }
 
-    public Artifact getArtifact(String name, String version) {
+    public Artifact getArtifact(final String name, final String version) {
         if (StringUtil.isBlank(name) || StringUtil.isBlank(version)) {
             return null;
         }

+ 9 - 1
src/main/java/org/codelibs/fess/helper/SystemHelper.java

@@ -50,6 +50,7 @@ import org.apache.commons.lang3.LocaleUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.config.Configurator;
+import org.codelibs.core.exception.IORuntimeException;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.misc.Pair;
 import org.codelibs.fess.Constants;
@@ -469,6 +470,14 @@ public class SystemHelper {
         return System.getProperty(Constants.FESS_LOG_LEVEL, Level.WARN.toString());
     }
 
+    public File createTempFile(String prefix, String suffix) {
+        try {
+            return File.createTempFile(prefix, suffix);
+        } catch (IOException e) {
+            throw new IORuntimeException(e);
+        }
+    }
+
     public String getVersion() {
         return version;
     }
@@ -484,5 +493,4 @@ public class SystemHelper {
     public String getProductVersion() {
         return productVersion;
     }
-
 }

+ 9 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java

@@ -2976,6 +2976,15 @@ public class FessLabels extends UserMessages {
     /** The key of the message: Install Plugin */
     public static final String LABELS_plugin_install_title = "{labels.plugin_install_title}";
 
+    /** The key of the message: Jar File */
+    public static final String LABELS_plugin_jar_file = "{labels.plugin_jar_file}";
+
+    /** The key of the message: Local */
+    public static final String LABELS_plugin_local_install = "{labels.plugin_local_install}";
+
+    /** The key of the message: Remote */
+    public static final String LABELS_plugin_remote_install = "{labels.plugin_remote_install}";
+
     /** The key of the message: Install */
     public static final String LABELS_crud_button_install = "{labels.crud_button_install}";
 

+ 36 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java

@@ -347,6 +347,12 @@ public class FessMessages extends FessLabels {
     /** The key of the message: {0} is not supported. */
     public static final String ERRORS_file_is_not_supported = "{errors.file_is_not_supported}";
 
+    /** The key of the message: {0} is not found. */
+    public static final String ERRORS_plugin_file_is_not_found = "{errors.plugin_file_is_not_found}";
+
+    /** The key of the message: Failed to install {0}. */
+    public static final String ERRORS_failed_to_install_plugin = "{errors.failed_to_install_plugin}";
+
     /** The key of the message: The given query has unknown condition. */
     public static final String ERRORS_invalid_query_unknown = "{errors.invalid_query_unknown}";
 
@@ -2034,6 +2040,36 @@ public class FessMessages extends FessLabels {
         return this;
     }
 
+    /**
+     * Add the created action message for the key 'errors.plugin_file_is_not_found' with parameters.
+     * <pre>
+     * message: {0} is not found.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @param arg0 The parameter arg0 for message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsPluginFileIsNotFound(String property, String arg0) {
+        assertPropertyNotNull(property);
+        add(property, new UserMessage(ERRORS_plugin_file_is_not_found, arg0));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.failed_to_install_plugin' with parameters.
+     * <pre>
+     * message: Failed to install {0}.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @param arg0 The parameter arg0 for message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToInstallPlugin(String property, String arg0) {
+        assertPropertyNotNull(property);
+        add(property, new UserMessage(ERRORS_failed_to_install_plugin, arg0));
+        return this;
+    }
+
     /**
      * Add the created action message for the key 'errors.invalid_query_unknown' with parameters.
      * <pre>

+ 3 - 0
src/main/resources/fess_label.properties

@@ -983,4 +983,7 @@ labels.plugin_version=Version
 labels.plugin_delete=Delete
 labels.plugin_install=Install
 labels.plugin_install_title=Install Plugin
+labels.plugin_jar_file=Jar File
+labels.plugin_local_install=Local
+labels.plugin_remote_install=Remote
 labels.crud_button_install=Install

+ 3 - 0
src/main/resources/fess_label_en.properties

@@ -983,4 +983,7 @@ labels.plugin_version=Version
 labels.plugin_delete=Delete
 labels.plugin_install=Install
 labels.plugin_install_title=Install Plugin
+labels.plugin_jar_file=Jar File
+labels.plugin_local_install=Local
+labels.plugin_remote_install=Remote
 labels.crud_button_install=Install

+ 3 - 0
src/main/resources/fess_label_ja.properties

@@ -983,4 +983,7 @@ labels.plugin_version=バージョン
 labels.plugin_delete=削除
 labels.plugin_install=インストール
 labels.plugin_install_title=プラグインのインストール
+labels.plugin_jar_file=Jarファイル
+labels.plugin_local_install=ローカル
+labels.plugin_remote_install=リモート
 labels.crud_button_install=インストール

+ 2 - 0
src/main/resources/fess_message.properties

@@ -137,6 +137,8 @@ errors.could_not_delete_logged_in_user=Could not delete logged in user.
 errors.unauthorized_request=Unauthorized request.
 errors.failed_to_print_thread_dump=Failed to print thread dump.
 errors.file_is_not_supported={0} is not supported.
+errors.plugin_file_is_not_found={0} is not found.
+errors.failed_to_install_plugin=Failed to install {0}.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.

+ 2 - 0
src/main/resources/fess_message_en.properties

@@ -133,6 +133,8 @@ errors.could_not_delete_logged_in_user=Could not delete logged in user.
 errors.unauthorized_request=Unauthorized request.
 errors.failed_to_print_thread_dump=Failed to print thread dump.
 errors.file_is_not_supported={0} is not supported.
+errors.plugin_file_is_not_found={0} is not found.
+errors.failed_to_install_plugin=Failed to install {0}.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.

+ 3 - 1
src/main/resources/fess_message_ja.properties

@@ -23,7 +23,7 @@ constraints.NotNull.message = {item} は未入力です。
 constraints.Null.message = {item} は null でなければなりません。
 constraints.Past.message = {item} は過去の値にする必要があります。
 constraints.Pattern.message = {item} が 「{regexp}」 に一致しません。
-constraints.Size.message = {item}のサイズは {min} から {max} の範囲にしてください。
+constraints.Size.message = {item}のサイズは {min} 文字から {max} 文字の範囲にしてください。
 # ----------------------------------------------------------
 # Hibernate Validator
 # -------------------
@@ -139,6 +139,8 @@ errors.invalid_header_for_request_file=ヘッダー行が正しくありませ
 errors.could_not_delete_logged_in_user=ログインしているユーザーは削除できません。
 errors.failed_to_print_thread_dump=スレッドダンプの出力に失敗しました。
 errors.file_is_not_supported={0}はサポートされていません。
+errors.plugin_file_is_not_found={0}が見つかりません。
+errors.failed_to_install_plugin={0}のインストールに失敗しました。
 
 errors.property_required={0}は必須です。
 errors.property_type_integer={0}は数値です。

+ 119 - 121
src/main/webapp/WEB-INF/view/admin/plugin/admin_plugin.jsp

@@ -1,131 +1,129 @@
 <%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
 <html>
 <head>
-    <meta charset="UTF-8">
-    <title><la:message key="labels.admin_brand_title" /> | <la:message
-            key="labels.plugin_title" /></title>
-    <jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message key="labels.plugin_title" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
 </head>
 <body class="hold-transition skin-blue sidebar-mini">
-<div class="wrapper">
-    <jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
-    <jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
-        <jsp:param name="menuCategoryType" value="system" />
-        <jsp:param name="menuType" value="plugin" />
-    </jsp:include>
-    <div class="content-wrapper">
-        <section class="content-header">
-            <h1>
-                <la:message key="labels.plugin_title" />
-            </h1>
-            <jsp:include page="/WEB-INF/view/common/admin/crud/breadcrumb.jsp"></jsp:include>
-        </section>
-        <section class="content">
-            <div class="row">
-                <div class="col-md-12">
-                    <div class="box box-primary">
-						<div class="box-header with-border">
-							<h3 class="box-title">
-								<la:message key="labels.crud_title_list" />
-							</h3>
-							<div class="btn-group pull-right">
-								<la:link href="installplugin" styleClass="btn btn-success btn-xs">
-									<em class="fa fa-plus"></em>
-									<la:message key="labels.plugin_install" />
-								</la:link>
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="plugin" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.plugin_title" />
+				</h1>
+				<jsp:include page="/WEB-INF/view/common/admin/crud/breadcrumb.jsp"></jsp:include>
+			</section>
+			<section class="content">
+				<div class="row">
+					<div class="col-md-12">
+						<div class="box box-primary">
+							<div class="box-header with-border">
+								<h3 class="box-title">
+									<la:message key="labels.crud_title_list" />
+								</h3>
+								<div class="btn-group pull-right">
+									<la:link href="installplugin" styleClass="btn btn-success btn-xs">
+										<em class="fa fa-plus"></em>
+										<la:message key="labels.plugin_install" />
+									</la:link>
+								</div>
 							</div>
+							<!-- /.box-header -->
+							<div class="box-body">
+								<%-- Message --%>
+								<div>
+									<la:info id="msg" message="true">
+										<div class="alert alert-info">${msg}</div>
+									</la:info>
+									<la:errors />
+								</div>
+								<%-- List --%>
+								<div class="data-wrapper">
+									<div class="row">
+										<div class="col-sm-12">
+											<table class="table table-bordered table-striped dataTable">
+												<tbody>
+													<tr>
+														<th><la:message key="labels.plugin_type" /></th>
+														<th><la:message key="labels.plugin_name" /></th>
+														<th><la:message key="labels.plugin_version" /></th>
+														<th></th>
+													</tr>
+													<c:forEach var="artifact" varStatus="s" items="${installedArtifactItems}">
+														<tr>
+															<td>${f:h(artifact.type)}</td>
+															<td>${f:h(artifact.name)}</td>
+															<td>${f:h(artifact.version)}</td>
+															<td>
+																<button type="button" class="btn btn-danger btn-xs" name="delete" data-toggle="modal"
+																	data-target='#confirmToDelete-${f:h(artifact.name)}-${f:h(artifact.version).replace(".", "\\.")}'
+																	value="<la:message key="labels.design_delete_button" />"
+																>
+																	<em class="fa fa-trash"></em>
+																	<la:message key="labels.design_delete_button" />
+																</button>
+																<div class="modal modal-danger fade" id='confirmToDelete-${f:h(artifact.name)}-${f:h(artifact.version)}'
+																	tabindex="-1" role="dialog"
+																>
+																	<div class="modal-dialog">
+																		<div class="modal-content">
+																			<div class="modal-header">
+																				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+																					<span aria-hidden="true">×</span>
+																				</button>
+																				<h4 class="modal-title">
+																					<la:message key="labels.crud_title_delete" />
+																				</h4>
+																			</div>
+																			<div class="modal-body">
+																				<p>
+																					<la:message key="labels.crud_delete_confirmation" />
+																				</p>
+																			</div>
+																			<div class="modal-footer">
+																				<button type="button" class="btn btn-outline pull-left" data-dismiss="modal">
+																					<la:message key="labels.crud_button_cancel" />
+																				</button>
+																				<la:form action="/admin/plugin/" styleClass="form-horizontal">
+																					<input type="hidden" name="name" value="${f:h(artifact.name)}">
+																					<input type="hidden" name="version" value="${f:h(artifact.version)}">
+																					<button type="submit" class="btn btn-outline btn-danger" name="delete"
+																						value="<la:message key="labels.crud_button_delete" />"
+																					>
+																						<em class="fa fa-trash"></em>
+																						<la:message key="labels.crud_button_delete" />
+																					</button>
+																				</la:form>
+																			</div>
+																		</div>
+																	</div>
+																</div>
+															</td>
+														</tr>
+													</c:forEach>
+												</tbody>
+											</table>
+										</div>
+									</div>
+								</div>
+								<!-- /.data-wrapper -->
+							</div>
+							<!-- /.box-body -->
 						</div>
-                        <!-- /.box-header -->
-                        <div class="box-body">
-                            <%-- Message --%>
-                            <div>
-                                <la:info id="msg" message="true">
-                                    <div class="alert alert-info">${msg}</div>
-                                </la:info>
-                                <la:errors />
-                            </div>
-                            <%-- List --%>
-                            <div class="data-wrapper">
-                                <div class="row">
-                                    <div class="col-sm-12">
-                                        <table class="table table-bordered table-striped dataTable">
-                                            <tbody>
-                                            <tr>
-                                                <th><la:message key="labels.plugin_type" /></th>
-                                                <th><la:message key="labels.plugin_name" /></th>
-                                                <th><la:message key="labels.plugin_version" /></th>
-                                                <th></th>
-                                            </tr>
-                                            <c:forEach var="artifact" varStatus="s"
-                                                       items="${installedArtifactItems}">
-                                                <tr>
-                                                    <td>${f:h(artifact.type)}</td>
-                                                    <td>${f:h(artifact.name)}</td>
-                                                    <td>${f:h(artifact.version)}</td>
-                                                    <td>
-                                                        <button type="button" class="btn btn-danger btn-xs" name="delete"
-                                                                data-toggle="modal" data-target='#confirmToDelete-${f:h(artifact.name)}-${f:h(artifact.version).replace(".", "\\.")}'
-                                                                value="<la:message key="labels.design_delete_button" />">
-                                                            <em class="fa fa-trash"></em>
-                                                            <la:message key="labels.design_delete_button" />
-                                                        </button>
-                                                        <div class="modal modal-danger fade" id='confirmToDelete-${f:h(artifact.name)}-${f:h(artifact.version)}'
-                                                             tabindex="-1" role="dialog">
-                                                            <div class="modal-dialog">
-                                                                <div class="modal-content">
-                                                                    <div class="modal-header">
-                                                                        <button type="button" class="close" data-dismiss="modal"
-                                                                                aria-label="Close">
-                                                                            <span aria-hidden="true">×</span>
-                                                                        </button>
-                                                                        <h4 class="modal-title">
-                                                                            <la:message key="labels.crud_title_delete" />
-                                                                        </h4>
-                                                                    </div>
-                                                                    <div class="modal-body">
-                                                                        <p>
-                                                                            <la:message key="labels.crud_delete_confirmation" />
-                                                                        </p>
-                                                                    </div>
-                                                                    <div class="modal-footer">
-                                                                        <button type="button" class="btn btn-outline pull-left"
-                                                                                data-dismiss="modal">
-                                                                            <la:message key="labels.crud_button_cancel" />
-                                                                        </button>
-																		<la:form action="/admin/plugin/" styleClass="form-horizontal">
-																		<input type="hidden" name="name" value="${f:h(artifact.name)}">
-																		<input type="hidden" name="version" value="${f:h(artifact.version)}">
-                                                                        <button type="submit" class="btn btn-outline btn-danger"
-                                                                                name="delete"
-                                                                                value="<la:message key="labels.crud_button_delete" />">
-                                                                            <em class="fa fa-trash"></em>
-                                                                            <la:message key="labels.crud_button_delete" />
-                                                                        </button>
-                                                                        </la:form>
-                                                                    </div>
-                                                                </div>
-                                                            </div>
-                                                        </div>
-                                                    </td>
-                                                </tr>
-                                            </c:forEach>
-                                            </tbody>
-                                        </table>
-                                    </div>
-                                </div>
-                            </div>
-                            <!-- /.data-wrapper -->
-                        </div>
-                        <!-- /.box-body -->
-                    </div>
-                    <!-- /.box -->
-                </div>
-            </div>
-        </section>
-    </div>
-    <jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
-</div>
-<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+						<!-- /.box -->
+					</div>
+				</div>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
 </body>
 </html>
 

+ 90 - 70
src/main/webapp/WEB-INF/view/admin/plugin/admin_plugin_installplugin.jsp

@@ -1,77 +1,97 @@
 <%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><!DOCTYPE html>
 <html>
 <head>
-    <meta charset="UTF-8">
-    <title><la:message key="labels.admin_brand_title" /> | <la:message
-            key="labels.plugin_install_title" /></title>
-    <jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
+<meta charset="UTF-8">
+<title><la:message key="labels.admin_brand_title" /> | <la:message key="labels.plugin_install_title" /></title>
+<jsp:include page="/WEB-INF/view/common/admin/head.jsp"></jsp:include>
 </head>
 <body class="hold-transition skin-blue sidebar-mini">
-<div class="wrapper">
-    <jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
-    <jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
-        <jsp:param name="menuCategoryType" value="system" />
-        <jsp:param name="menuType" value="plugin" />
-    </jsp:include>
-    <div class="content-wrapper">
-        <section class="content-header">
-            <h1>
-                <la:message key="labels.plugin_install_title" />
-            </h1>
-        </section>
-        <section class="content">
-            <div class="row">
-                <div class="col-md-12">
-                    <la:info id="msg" message="true">
-                        <div class="alert alert-info">${msg}</div>
-                    </la:info>
-                    <la:errors property="_global" />
-                </div>
-                <div class="col-md-6">
-                    <div class="box box-primary">
-                        <la:form action="/admin/plugin/">
-                            <div class="box-header with-border">
-                                <h3 class="box-title">
-                                    <la:message key="labels.plugin_install" />
-                                </h3>
-                            </div>
-                            <!-- /.box-header -->
-                            <div class="box-body">
-                                <div class="form-group">
-                                    <la:errors property="selectedArtifact" />
-                                    <la:select styleId="artifacts" property="id" styleClass="form-control">
-                                        <c:forEach var="item" varStatus="s"
-                                                   items="${availableArtifactItems}">
-                                            <la:option value="${f:h(item.id)}">${f:h(item.name)}-${f:h(item.version)}</la:option>
-                                        </c:forEach>
-                                    </la:select>
-                                </div>
-                            </div>
-                            <!-- /.box-body -->
-                            <div class="box-footer">
-                                <button type="submit" class="btn btn-default"
-                                        name="back"
-                                        value="<la:message key="labels.crud_button_back" />">
-                                    <em class="fa fa-arrow-circle-left"></em>
-                                    <la:message key="labels.crud_button_back" />
-                                </button>
-                                <button type="submit" class="btn btn-warning"
-                                        name="install"
-                                        value="<la:message key="labels.crud_button_install" />">
-                                    <em class="fa fa-plus"></em>
-                                    <la:message key="labels.crud_button_install" />
-                                </button>
-                            </div>
-                            <!-- /.box-footer -->
-                        </la:form>
-                    </div>
-                        <!-- /.box -->
-                </div>
-            </div>
-        </section>
-    </div>
-    <jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
-</div>
-<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
+	<div class="wrapper">
+		<jsp:include page="/WEB-INF/view/common/admin/header.jsp"></jsp:include>
+		<jsp:include page="/WEB-INF/view/common/admin/sidebar.jsp">
+			<jsp:param name="menuCategoryType" value="system" />
+			<jsp:param name="menuType" value="plugin" />
+		</jsp:include>
+		<div class="content-wrapper">
+			<section class="content-header">
+				<h1>
+					<la:message key="labels.plugin_install_title" />
+				</h1>
+			</section>
+			<section class="content">
+				<div class="row">
+					<div class="col-md-12">
+						<la:info id="msg" message="true">
+							<div class="alert alert-info">${msg}</div>
+						</la:info>
+						<la:errors property="_global" />
+					</div>
+					<div class="col-md-6">
+						<div class="box box-primary">
+							<la:form action="/admin/plugin/install" enctype="multipart/form-data">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.plugin_install" />
+									</h3>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-body">
+									<ul class="nav nav-tabs" role="tablist">
+										<li role="presentation" class="active"><a href="#remote" aria-controls="remote" role="tab"
+											data-toggle="tab"
+										><la:message key="labels.plugin_remote_install" /></a></li>
+										<li role="presentation"><a href="#local" aria-controls="local" role="tab" data-toggle="tab"><la:message
+													key="labels.plugin_local_install"
+												/></a></li>
+									</ul>
+									<div class="tab-content">
+										<div role="tabpanel" class="tab-pane active" id="remote">
+											<div class="box-body">
+												<div class="form-group">
+													<la:errors property="selectedArtifact" />
+													<la:select styleId="artifacts" property="id" styleClass="form-control">
+														<c:forEach var="item" varStatus="s" items="${availableArtifactItems}">
+															<la:option value="${f:h(item.id)}">${f:h(item.name)}-${f:h(item.version)}</la:option>
+														</c:forEach>
+													</la:select>
+												</div>
+											</div>
+										</div>
+										<div role="tabpanel" class="tab-pane" id="local">
+											<div class="box-body">
+												<div class="form-group">
+													<label for="name" class="col-md-2 control-label"><la:message key="labels.plugin_jar_file" /></label>
+													<div>
+														<input type="file" name="jarFile" />
+													</div>
+												</div>
+											</div>
+										</div>
+									</div>
+								</div>
+								<!-- /.box-body -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-default" name="back" value="<la:message key="labels.crud_button_back" />">
+										<em class="fa fa-arrow-circle-left"></em>
+										<la:message key="labels.crud_button_back" />
+									</button>
+									<button type="submit" class="btn btn-warning" name="install"
+										value="<la:message key="labels.crud_button_install" />"
+									>
+										<em class="fa fa-plus"></em>
+										<la:message key="labels.crud_button_install" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</la:form>
+						</div>
+						<!-- /.box -->
+					</div>
+				</div>
+			</section>
+		</div>
+		<jsp:include page="/WEB-INF/view/common/admin/footer.jsp"></jsp:include>
+	</div>
+	<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
 </body>
 </html>