Add delete function #2319 (#2321)

This commit is contained in:
Koki Igarashi 2019-12-14 22:32:19 +09:00 committed by Shinsuke Sugaya
parent 40d2e9c284
commit f3b811320f
5 changed files with 248 additions and 34 deletions

View file

@ -73,17 +73,25 @@ public class AdminStorageAction extends FessAdminAction {
if (form.uploadFile == null) {
throwValidationError(messages -> messages.addErrorsStorageNoUploadFile(GLOBAL), () -> asListHtml(form.path));
}
logger.debug("form.path = {}", form.path);
verifyToken(() -> asListHtml(form.path));
final String fileName = form.uploadFile.getFileName();
final String objectName = getObjectName(form.path, form.uploadFile.getFileName());
try (final InputStream in = form.uploadFile.getInputStream()) {
final MinioClient minioClient = createClient(fessConfig);
minioClient.putObject(fessConfig.getStorageBucket(), form.uploadFile.getFileName(), in, (long) form.uploadFile.getFileSize(),
null, null, "application/octet-stream");
minioClient.putObject(fessConfig.getStorageBucket(), objectName, in, (long) form.uploadFile.getFileSize(), null, null,
"application/octet-stream");
} catch (final Exception e) {
throwValidationError(messages -> messages.addErrorsStorageFileUploadFailure(GLOBAL, fileName), () -> asListHtml(form.path));
if (logger.isDebugEnabled()) {
logger.debug("Failed to upload {}", objectName, e);
}
throwValidationError(messages -> messages.addErrorsStorageFileUploadFailure(GLOBAL, e.getLocalizedMessage()),
() -> asListHtml(form.path));
}
saveInfo(messages -> messages.addSuccessUploadFileToStorage(GLOBAL, fileName));
return redirect(getClass()); // no-op
saveInfo(messages -> messages.addSuccessUploadFileToStorage(GLOBAL, form.uploadFile.getFileName()));
if (StringUtil.isEmpty(form.path)) {
return redirect(getClass());
}
return redirectWith(getClass(), moreUrl("list/" + encodeId(form.path)));
}
@Execute
@ -96,7 +104,7 @@ public class AdminStorageAction extends FessAdminAction {
public ActionResponse download(final String id) {
final String[] values = decodeId(id);
if (StringUtil.isEmpty(values[1])) {
throwValidationError(messages -> messages.addErrorsStorageFileNotFound(GLOBAL), () -> asListHtml(values[0]));
throwValidationError(messages -> messages.addErrorsStorageFileNotFound(GLOBAL), () -> asListHtml(encodeId(values[0])));
}
return asStream(values[1]).contentTypeOctetStream().stream(
out -> {
@ -107,11 +115,43 @@ public class AdminStorageAction extends FessAdminAction {
logger.debug("Failed to access {}", fessConfig.getStorageEndpoint(), e);
}
throwValidationError(messages -> messages.addErrorsStorageAccessError(GLOBAL, e.getLocalizedMessage()),
() -> asListHtml(values[0]));
() -> asListHtml(encodeId(values[0])));
}
});
}
@Execute
public HtmlResponse delete(final String id) {
final String[] values = decodeId(id);
if (StringUtil.isEmpty(values[1])) {
throwValidationError(messages -> messages.addErrorsStorageFileNotFound(GLOBAL), () -> asListHtml(encodeId(values[0])));
}
logger.debug("values[0] = {}, values[1] = {}", values[0], values[1]);
final String objectName = getObjectName(values[0], values[1]);
try {
final MinioClient minioClient = createClient(fessConfig);
minioClient.removeObject(fessConfig.getStorageBucket(), objectName);
} catch (final Exception e) {
logger.debug("Failed to delete {}", values[1], e);
throwValidationError(messages -> messages.addErrorsFailedToDeleteFile(GLOBAL, e.getLocalizedMessage()),
() -> asListHtml(encodeId(values[0])));
}
saveInfo(messages -> messages.addSuccessDeleteFile(GLOBAL, values[1]));
if (StringUtil.isEmpty(values[0])) {
return redirect(getClass());
}
return redirectWith(getClass(), moreUrl("list/" + encodeId(values[0])));
}
@Execute
public HtmlResponse createDir(final ItemForm form) {
validate(form, messages -> {}, () -> asListHtml(form.path));
if (StringUtil.isBlank(form.name)) {
throwValidationError(messages -> messages.addErrorsStorageDirectoryNameIsInvalid(GLOBAL), () -> asListHtml(form.path));
}
return redirectWith(getClass(), moreUrl("list/" + encodeId(getObjectName(form.path, form.name))));
}
public static List<Map<String, Object>> getFileItems(final String prefix) {
final FessConfig fessConfig = ComponentUtil.getFessConfig();
final ArrayList<Map<String, Object>> list = new ArrayList<>();
@ -124,7 +164,8 @@ public class AdminStorageAction extends FessAdminAction {
final String objectName = item.objectName();
map.put("id", URLEncoder.encode(objectName, Constants.UTF_8_CHARSET));
map.put("name", getName(objectName));
map.put("size", item.size());
map.put("hashCode", item.hashCode());
map.put("size", item.objectSize());
map.put("directory", item.isDir());
if (!item.isDir()) {
map.put("lastModified", item.lastModified());
@ -201,7 +242,7 @@ public class AdminStorageAction extends FessAdminAction {
}
buf.append(values[i]);
}
return URLEncoder.encode(buf.toString(), Constants.UTF_8_CHARSET);
return urlEncode(buf.toString());
}
return StringUtil.EMPTY;
}
@ -215,13 +256,32 @@ public class AdminStorageAction extends FessAdminAction {
}
buf.append(s);
final Map<String, String> map = new HashMap<>();
map.put("id", URLEncoder.encode(buf.toString(), Constants.UTF_8_CHARSET));
map.put("id", urlEncode(buf.toString()));
map.put("name", s);
list.add(map);
}));
return list;
}
protected static String getPathPrefix(final String path) {
return StringUtil.isEmpty(path) ? StringUtil.EMPTY : path + "/";
}
protected static String getObjectName(final String path, final String name) {
return getPathPrefix(path) + name;
}
protected static String urlEncode(final String str) {
if (str == null) {
return null;
}
return URLEncoder.encode(str, Constants.UTF_8_CHARSET);
}
protected static String encodeId(final String str) {
return urlEncode(urlEncode(str));
}
private HtmlResponse asListHtml(final String prefix) {
return asHtml(path_AdminStorage_AdminStorageJsp).useForm(ItemForm.class).renderWith(data -> {
RenderDataUtil.register(data, "endpoint", fessConfig.getStorageEndpoint());

View file

@ -18,10 +18,9 @@ package org.codelibs.fess.app.web.admin.storage;
import javax.validation.constraints.Size;
import org.lastaflute.web.ruts.multipart.MultipartFormFile;
import org.lastaflute.web.validation.Required;
public class ItemForm {
@Required
public String path;
@Size(max = 100)

View file

@ -419,6 +419,9 @@ public class FessMessages extends FessLabels {
/** The key of the message: Upload file is required. */
public static final String ERRORS_storage_no_upload_file = "{errors.storage_no_upload_file}";
/** The key of the message: Directory name is invalid. */
public static final String ERRORS_storage_directory_name_is_invalid = "{errors.storage_directory_name_is_invalid}";
/** The key of the message: Updated parameters. */
public static final String SUCCESS_update_crawler_params = "{success.update_crawler_params}";
@ -2414,6 +2417,20 @@ public class FessMessages extends FessLabels {
return this;
}
/**
* Add the created action message for the key 'errors.storage_directory_name_is_invalid' with parameters.
* <pre>
* message: Directory name is invalid.
* </pre>
* @param property The property name for the message. (NotNull)
* @return this. (NotNull)
*/
public FessMessages addErrorsStorageDirectoryNameIsInvalid(String property) {
assertPropertyNotNull(property);
add(property, new UserMessage(ERRORS_storage_directory_name_is_invalid));
return this;
}
/**
* Add the created action message for the key 'success.update_crawler_params' with parameters.
* <pre>

View file

@ -165,6 +165,7 @@ errors.storage_file_not_found=The target file is not found in Storage.
errors.storage_file_download_failure=Failed to download {0}.
errors.storage_access_error=Storage access error: {0}
errors.storage_no_upload_file=Upload file is required.
errors.storage_directory_name_is_invalid=Directory name is invalid.
success.update_crawler_params=Updated parameters.
success.delete_doc_from_index=Started a process to delete the document from index.

View file

@ -42,20 +42,100 @@
<div class="data-wrapper">
<div class="row">
<div class="col-sm-12">
Path: ${f:h(endpoint)}/${f:h(bucket)}<c:forEach var="item" varStatus="s" items="${pathItems}">/<a href="${contextPath}/admin/storage/list/${f:u(item.id)}/">${f:h(item.name)}</a></c:forEach>
</div>
<div class="col-sm-12">
<la:form action="/admin/storage/upload/" enctype="multipart/form-data" styleClass="form-inline">
<div class="form-group">
<label for="uploadFile"> <la:message key="labels.storage_upload_file" />
</label> <input type="file" name="uploadFile" class="form-control" />
<a class="fa fa-home" aria-hidden="true" href="${contextPath}/admin/storage/">(Bucket: ${f:h(endpoint)}/${f:h(bucket)})</a>
<c:forEach var="item" varStatus="s" items="${pathItems}">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
<span><a href="${contextPath}/admin/storage/list/${f:u(item.id)}/">${f:h(item.name)}</a></span>
</c:forEach>
<i class="fa fa-chevron-right" aria-hidden="true"></i>
<div type="button" class="btn btn-success btn-xs" name="createDir" data-toggle="modal"
data-target="#createDir">
<i class="fa fa-plus" aria-hidden="true"></i>
</div>
<div class="modal modal-primary" id="createDir"
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_create" />
</h4>
</div>
<div class="modal-body col-sm-12">
<la:form action="/admin/storage/createDir/" enctype="multipart/form-data" styleClass="form-inline">
<div class="form-group">
<input type="text" name="name" class="form-control" />
</div>
<input type="hidden" name="path" value="${path}" />
<button type="submit" class="btn btn-success" name="createDir">
<em class="fa fa-make"></em>
<la:message key="labels.crud_button_create" />
</button>
</la:form>
</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>
</div>
</div>
</div>
<button type="submit" class="btn btn-success" name="upload">
<em class="fa fa-upload"></em>
<la:message key="labels.storage_button_upload" />
</button>
</la:form>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div type="button" class="btn btn-success pull-right" name="upload" data-toggle="modal"
data-target="#uploadeFile"
value="<la:message key="labels.storage_button_upload" />"
>
<em class="fa fa-upload"></em>
<la:message key="labels.storage_button_upload" />
</div>
<div class="modal modal-primary" id="uploadeFile"
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.storage_upload_file" />
</h4>
</div>
<div class="modal-body col-sm-12">
<la:form action="/admin/storage/upload/" enctype="multipart/form-data" styleClass="form-inline">
<div class="form-group">
<input type="file" name="uploadFile" class="form-control" />
</div>
<input type="hidden" name="path" value="${path}" />
<button type="submit" class="btn btn-success" name="upload">
<em class="fa fa-upload"></em>
<la:message key="labels.storage_button_upload" />
</button>
</la:form>
</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>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<table class="table table-bordered table-striped dataTable">
<tbody>
@ -77,23 +157,81 @@
<td>..</td>
<td></td>
<td></td>
<td></td>
</tr></c:if>
<c:forEach var="data" varStatus="s" items="${fileItems}">
<c:if test="${not data.directory}">
<tr
data-href="${contextPath}/admin/storage/download/${f:u(data.id)}/">
<td>${f:h(data.name)}</td>
<tr>
<td>
<em class="fa fa-file"></em>
${f:h(data.name)}
</td>
<td>${f:h(data.size)}</td>
<td>${f:h(data.lastModifed)}</td>
</tr>
</c:if><c:if test="${data.directory.booleanValue()}">
<td>${f:h(data.lastModified)}</td>
</c:if>
<c:if test="${data.directory.booleanValue()}">
<tr
data-href="${contextPath}/admin/storage/list/${f:u(data.id)}/">
<td>${f:h(data.name)}</td>
<td>
<em class="fa fa-folder-open"></em>
${f:h(data.name)}
</td>
<td></td>
<td></td>
</tr>
</c:if>
<td>
<c:if test="${not data.directory}">
<a class="btn btn-primary btn-xs" role="button" name="download" data-toggle="modal"
href="${contextPath}/admin/storage/download/${f:u(data.id)}/" download="${f:u(data.name)}"
value="<la:message key="labels.design_download_button" />"
>
<em class="fa fa-download"></em>
<la:message key="labels.design_download_button" />
</a>
<button type="button" class="btn btn-danger btn-xs" name="delete" data-toggle="modal"
data-target="#confirmToDelete-${f:h(data.hashCode)}"
value="<la:message key="labels.design_delete_button" />"
>
<em class="fa fa-times"></em>
<la:message key="labels.design_delete_button" />
</button>
<div class="modal modal-danger fade" id="confirmToDelete-${f:h(data.hashCode)}"
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" /> : ${f:h(data.name)}
</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="${contextPath}/admin/storage/delete/${f:u(data.id)}/" styleClass="form-horizontal">
<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>
</c:if>
</td>
</tr>
</c:forEach>
</tbody>
</table>
@ -114,4 +252,3 @@
<jsp:include page="/WEB-INF/view/common/admin/foot.jsp"></jsp:include>
</body>
</html>