Search by username in audit logs (#34)

This commit is contained in:
Francesco 2023-11-10 10:20:03 +01:00
parent bdbdd6e8a2
commit 7625462eae
11 changed files with 48 additions and 47 deletions

View File

@ -33,7 +33,6 @@ import jakarta.servlet.http.HttpServletResponse;
import tech.ailef.snapadmin.external.SnapAdmin; import tech.ailef.snapadmin.external.SnapAdmin;
import tech.ailef.snapadmin.external.SnapAdminProperties; import tech.ailef.snapadmin.external.SnapAdminProperties;
import tech.ailef.snapadmin.external.exceptions.SnapAdminException; import tech.ailef.snapadmin.external.exceptions.SnapAdminException;
import tech.ailef.snapadmin.external.exceptions.SnapAdminForbiddenException;
import tech.ailef.snapadmin.external.exceptions.SnapAdminNotFoundException; import tech.ailef.snapadmin.external.exceptions.SnapAdminNotFoundException;
import tech.ailef.snapadmin.internal.UserConfiguration; import tech.ailef.snapadmin.internal.UserConfiguration;

View File

@ -30,6 +30,11 @@ import tech.ailef.snapadmin.external.dbmapping.fields.DbFieldType;
import tech.ailef.snapadmin.external.exceptions.SnapAdminException; import tech.ailef.snapadmin.external.exceptions.SnapAdminException;
import tech.ailef.snapadmin.external.exceptions.UnsupportedFieldTypeException; import tech.ailef.snapadmin.external.exceptions.UnsupportedFieldTypeException;
/*
* A class that holds output fields from a user-provided SQL query
* run in the SQL console. If possible, this field is mapped to a proper
* {@link Dbfield} object, otherwise it is left as a raw object.
*/
public class DbQueryOutputField { public class DbQueryOutputField {
private String name; private String name;

View File

@ -23,6 +23,10 @@ package tech.ailef.snapadmin.external.dbmapping.query;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* A wrapper for results returned by user-provided SQL queries run via
* the SQL console.
*/
public class DbQueryResult { public class DbQueryResult {
private List<DbQueryResultRow> rows; private List<DbQueryResultRow> rows;

View File

@ -26,6 +26,10 @@ import java.util.Map;
import tech.ailef.snapadmin.external.exceptions.SnapAdminException; import tech.ailef.snapadmin.external.exceptions.SnapAdminException;
/**
* A single row of results coming from a user-provided SQL query
* run via the SQL console.
*/
public class DbQueryResultRow { public class DbQueryResultRow {
private Map<DbQueryOutputField, Object> values; private Map<DbQueryOutputField, Object> values;

View File

@ -64,6 +64,11 @@ public class LogsSearchRequest implements FilterRequest {
* The requested sort order, possibly null * The requested sort order, possibly null
*/ */
private String sortOrder; private String sortOrder;
/**
* The requested username filter
*/
private String username;
/** /**
* Returns the table specified in this search request. If the value is blank or 'Any', * Returns the table specified in this search request. If the value is blank or 'Any',
@ -170,6 +175,14 @@ public class LogsSearchRequest implements FilterRequest {
+ page + ", pageSize=" + pageSize + ", sortKey=" + sortKey + ", sortOrder=" + sortOrder + "]"; + page + ", pageSize=" + pageSize + ", sortKey=" + sortKey + ", sortOrder=" + sortOrder + "]";
} }
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
/** /**
* Build a Spring PageRequest object from the parameters in this request * Build a Spring PageRequest object from the parameters in this request
* @return a Spring PageRequest object * @return a Spring PageRequest object

View File

@ -26,6 +26,10 @@ import java.util.Map;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
/**
* Holds information about JPA validation errors occurring during
* creation or editing of items.
*/
public class ValidationErrorsContainer { public class ValidationErrorsContainer {
private Map<String, List<ConstraintViolation<?>>> errors = new HashMap<>(); private Map<String, List<ConstraintViolation<?>>> errors = new HashMap<>();

View File

@ -1,38 +0,0 @@
/*
* SnapAdmin - An automatically generated CRUD admin UI for Spring Boot apps
* Copyright (C) 2023 Ailef (http://ailef.tech)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.ailef.snapadmin.external.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
public class SnapAdminForbiddenException extends ResponseStatusException {
private static final long serialVersionUID = 4090093290330473479L;
public SnapAdminForbiddenException(String message) {
super(HttpStatus.NOT_FOUND, message);
}
@Override
public String getMessage() {
return getReason();
}
}

View File

@ -27,6 +27,6 @@ import tech.ailef.snapadmin.internal.model.UserAction;
public interface CustomActionRepository { public interface CustomActionRepository {
public List<UserAction> findActions(LogsSearchRequest r); public List<UserAction> findActions(LogsSearchRequest r);
public long countActions(String table, String actionType, String itemId); public long countActions(LogsSearchRequest request);
} }

View File

@ -54,6 +54,7 @@ public class CustomActionRepositoryImpl implements CustomActionRepository {
public List<UserAction> findActions(LogsSearchRequest request) { public List<UserAction> findActions(LogsSearchRequest request) {
String table = request.getTable(); String table = request.getTable();
String actionType = request.getActionType(); String actionType = request.getActionType();
String username = request.getUsername();
String itemId = request.getItemId(); String itemId = request.getItemId();
PageRequest page = request.toPageRequest(); PageRequest page = request.toPageRequest();
@ -68,7 +69,9 @@ public class CustomActionRepositoryImpl implements CustomActionRepository {
predicates.add(cb.equal(userAction.get("actionType"), actionType)); predicates.add(cb.equal(userAction.get("actionType"), actionType));
if (itemId != null) if (itemId != null)
predicates.add(cb.equal(userAction.get("primaryKey"), itemId)); predicates.add(cb.equal(userAction.get("primaryKey"), itemId));
if (username != null)
predicates.add(cb.equal(userAction.get("username"), username));
if (!predicates.isEmpty()) { if (!predicates.isEmpty()) {
query.select(userAction) query.select(userAction)
.where(cb.and( .where(cb.and(
@ -95,12 +98,17 @@ public class CustomActionRepositoryImpl implements CustomActionRepository {
* @return the number of user actions matching the filtering parameters * @return the number of user actions matching the filtering parameters
*/ */
@Override @Override
public long countActions(String table, String actionType, String itemId) { public long countActions(LogsSearchRequest request) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class); CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<UserAction> userAction = query.from(UserAction.class); Root<UserAction> userAction = query.from(UserAction.class);
String table = request.getTable();
String actionType = request.getActionType();
String itemId = request.getItemId();
String username = request.getUsername();
List<Predicate> predicates = new ArrayList<>(); List<Predicate> predicates = new ArrayList<>();
if (table != null) if (table != null)
predicates.add(cb.equal(userAction.get("onTable"), table)); predicates.add(cb.equal(userAction.get("onTable"), table));
@ -108,7 +116,9 @@ public class CustomActionRepositoryImpl implements CustomActionRepository {
predicates.add(cb.equal(userAction.get("actionType"), actionType)); predicates.add(cb.equal(userAction.get("actionType"), actionType));
if (itemId != null) if (itemId != null)
predicates.add(cb.equal(userAction.get("primaryKey"), itemId)); predicates.add(cb.equal(userAction.get("primaryKey"), itemId));
if (username != null)
predicates.add(cb.equal(userAction.get("username"), username));
if (!predicates.isEmpty()) { if (!predicates.isEmpty()) {
query.select(cb.count(userAction)) query.select(cb.count(userAction))
.where(cb.and( .where(cb.and(

View File

@ -60,12 +60,9 @@ public class UserActionService {
* @return a page of results matching the input request * @return a page of results matching the input request
*/ */
public PaginatedResult<UserAction> findActions(LogsSearchRequest request) { public PaginatedResult<UserAction> findActions(LogsSearchRequest request) {
String table = request.getTable();
String actionType = request.getActionType();
String itemId = request.getItemId();
PageRequest page = request.toPageRequest(); PageRequest page = request.toPageRequest();
long count = customRepo.countActions(table, actionType, itemId); long count = customRepo.countActions(request);
List<UserAction> actions = customRepo.findActions(request); List<UserAction> actions = customRepo.findActions(request);
int maxPage = (int)(Math.ceil ((double)count / page.getPageSize())); int maxPage = (int)(Math.ceil ((double)count / page.getPageSize()));

View File

@ -44,6 +44,9 @@
<span class="input-group-text ms-3">Item ID</span> <span class="input-group-text ms-3">Item ID</span>
<input type="text" class="form-control" name="itemId" <input type="text" class="form-control" name="itemId"
th:value="${searchRequest.getItemId()}"> th:value="${searchRequest.getItemId()}">
<span class="input-group-text ms-3">User</span>
<input type="text" class="form-control" name="username"
th:value="${searchRequest.getUsername()}">
<button class="ui-btn btn btn-primary ms-3">Filter</button> <button class="ui-btn btn btn-primary ms-3">Filter</button>
</div> </div>
</form> </form>