mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-06-08 21:38:21 +00:00
WIP
This commit is contained in:
parent
a91cddc7a2
commit
2db84d9996
@ -22,11 +22,13 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.exceptions.InvalidPageException;
|
||||
|
||||
@Controller
|
||||
@ -90,7 +92,9 @@ public class DefaultDbAdminController {
|
||||
public String list(Model model, @PathVariable String className,
|
||||
@RequestParam(required=false) Integer page, @RequestParam(required=false) String query,
|
||||
@RequestParam(required=false) Integer pageSize, @RequestParam(required=false) String sortKey,
|
||||
@RequestParam(required=false) String sortOrder) {
|
||||
@RequestParam(required=false) String sortOrder, @RequestParam MultiValueMap<String, String> otherParams) {
|
||||
System.out.println(otherParams);
|
||||
|
||||
if (page == null) page = 1;
|
||||
if (pageSize == null) pageSize = 50;
|
||||
|
||||
@ -98,8 +102,8 @@ public class DefaultDbAdminController {
|
||||
|
||||
try {
|
||||
PaginatedResult result = null;
|
||||
if (query != null) {
|
||||
result = repository.search(schema, query, page, pageSize, sortKey, sortOrder);
|
||||
if (query != null || !otherParams.isEmpty()) {
|
||||
result = repository.search(schema, query, page, pageSize, sortKey, sortOrder, otherParams);
|
||||
} else {
|
||||
result = repository.findAll(schema, page, pageSize, sortKey, sortOrder);
|
||||
}
|
||||
@ -337,9 +341,12 @@ public class DefaultDbAdminController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String settings(Model model) {
|
||||
model.addAttribute("activePage", "settings");
|
||||
return "settings";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package tech.ailef.dbadmin.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* This class registers some ModelAttribute objects that are
|
||||
* used in all templates.
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalController {
|
||||
|
||||
/**
|
||||
* A multi valued map containing the query parameters. It is used primarily
|
||||
* in building complex URL when performing faceted search with multiple filters.
|
||||
* @param request the incoming request
|
||||
* @return multi valued map of request parameters
|
||||
*/
|
||||
@ModelAttribute("queryParams")
|
||||
public Map<String, String[]> getQueryParams(HttpServletRequest request) {
|
||||
return request.getParameterMap();
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
@ -12,6 +13,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AdvancedJpaRepository extends SimpleJpaRepository {
|
||||
@ -27,47 +29,40 @@ public class AdvancedJpaRepository extends SimpleJpaRepository {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
public long count(String q) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public long count(String q, MultiValueMap<String, String> filteringParams) {
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery query = cb.createQuery(Long.class);
|
||||
Root root = query.from(schema.getJavaClass());
|
||||
|
||||
List<DbField> stringFields =
|
||||
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println("STRING F = " + stringFields);
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
for (DbField f : stringFields) {
|
||||
Path path = root.get(f.getJavaName());
|
||||
predicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
|
||||
}
|
||||
List<Predicate> finalPredicates = buildPredicates(q, filteringParams, cb, root);
|
||||
|
||||
query.select(cb.count(root.get(schema.getPrimaryKey().getName())))
|
||||
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
|
||||
.where(
|
||||
cb.and(
|
||||
finalPredicates.toArray(new Predicate[finalPredicates.size()])
|
||||
)
|
||||
);
|
||||
|
||||
Object o = entityManager.createQuery(query).getSingleResult();
|
||||
return (Long)o;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Object> search(String q, int page, int pageSize, String sortKey, String sortOrder) {
|
||||
public List<Object> search(String q, int page, int pageSize, String sortKey, String sortOrder, MultiValueMap<String, String> filteringParams) {
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery query = cb.createQuery(schema.getJavaClass());
|
||||
Root root = query.from(schema.getJavaClass());
|
||||
|
||||
List<DbField> stringFields =
|
||||
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
for (DbField f : stringFields) {
|
||||
Path path = root.get(f.getJavaName());
|
||||
predicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
|
||||
}
|
||||
List<Predicate> finalPredicates = buildPredicates(q, filteringParams, cb, root);
|
||||
|
||||
query.select(root)
|
||||
.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
|
||||
.where(
|
||||
cb.and(
|
||||
finalPredicates.toArray(new Predicate[finalPredicates.size()]) // query search on String fields
|
||||
)
|
||||
|
||||
);
|
||||
if (sortKey != null)
|
||||
query.orderBy(sortOrder.equals("DESC") ? cb.desc(root.get(sortKey)) : cb.asc(root.get(sortKey)));
|
||||
|
||||
@ -75,19 +70,72 @@ public class AdvancedJpaRepository extends SimpleJpaRepository {
|
||||
.setFirstResult((page - 1) * pageSize).getResultList();
|
||||
}
|
||||
|
||||
public List<Object> distinctFieldValues(DbField field) {
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
private List<Predicate> buildPredicates(String q, MultiValueMap<String, String> filteringParams,
|
||||
CriteriaBuilder cb, Path root) {
|
||||
List<Predicate> finalPredicates = new ArrayList<>();
|
||||
|
||||
Class<?> outputType = field.getType().getJavaClass();
|
||||
if (field.getConnectedType() != null) {
|
||||
outputType = field.getConnectedSchema().getPrimaryKey().getType().getJavaClass();
|
||||
List<DbField> stringFields =
|
||||
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Predicate> queryPredicates = new ArrayList<>();
|
||||
if (q != null) {
|
||||
for (DbField f : stringFields) {
|
||||
Path path = root.get(f.getJavaName());
|
||||
queryPredicates.add(cb.like(cb.lower(cb.toString(path)), "%" + q.toLowerCase() + "%"));
|
||||
}
|
||||
|
||||
CriteriaQuery query = cb.createQuery(outputType);
|
||||
Root root = query.from(schema.getJavaClass());
|
||||
|
||||
query.select(root.get(field.getJavaName()).as(outputType)).distinct(true);
|
||||
|
||||
return entityManager.createQuery(query).getResultList();
|
||||
Predicate queryPredicate = cb.or(queryPredicates.toArray(new Predicate[queryPredicates.size()]));
|
||||
finalPredicates.add(queryPredicate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute filtering predicates
|
||||
*/
|
||||
if (filteringParams != null) {
|
||||
List<String> ops = filteringParams.get("filter_op[]");
|
||||
List<String> fields = filteringParams.get("filter_field[]");
|
||||
List<String> values = filteringParams.get("filter_value[]");
|
||||
|
||||
|
||||
if (ops != null && fields != null && values != null) {
|
||||
if (ops.size() != fields.size() || fields.size() != values.size()
|
||||
|| ops.size() != values.size()) {
|
||||
throw new DbAdminException("Filtering parameters must have the same size");
|
||||
}
|
||||
|
||||
for (int i = 0; i < ops.size(); i++) {
|
||||
String op = ops.get(i);
|
||||
String field = fields.get(i);
|
||||
String value = values.get(i);
|
||||
|
||||
if (op.equalsIgnoreCase("equals")) {
|
||||
finalPredicates.add(cb.equal(cb.toString(root.get(field)), value));
|
||||
} else if (op.equalsIgnoreCase("contains")) {
|
||||
System.out.println("CONTAINS");
|
||||
finalPredicates.add(cb.like(cb.toString(root.get(field)), "%" + value + "%"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return finalPredicates;
|
||||
}
|
||||
|
||||
|
||||
// @SuppressWarnings("unchecked")
|
||||
// public List<Object> distinctFieldValues(DbField field) {
|
||||
// CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
//
|
||||
// Class<?> outputType = field.getType().getJavaClass();
|
||||
// if (field.getConnectedType() != null) {
|
||||
// outputType = field.getConnectedSchema().getPrimaryKey().getType().getJavaClass();
|
||||
// }
|
||||
//
|
||||
// CriteriaQuery query = cb.createQuery(outputType);
|
||||
// Root root = query.from(schema.getJavaClass());
|
||||
//
|
||||
// query.select(root.get(field.getJavaName()).as(outputType)).distinct(true);
|
||||
//
|
||||
// return entityManager.createQuery(query).getResultList();
|
||||
// }
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
@ -63,8 +64,8 @@ public class DbAdminRepository {
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
public long count(DbObjectSchema schema, String query) {
|
||||
return schema.getJpaRepository().count(query);
|
||||
public long count(DbObjectSchema schema, String query, MultiValueMap<String, String> filteringParams) {
|
||||
return schema.getJpaRepository().count(query, filteringParams);
|
||||
}
|
||||
|
||||
|
||||
@ -223,10 +224,11 @@ public class DbAdminRepository {
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
public PaginatedResult search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey, String sortOrder) {
|
||||
public PaginatedResult search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey,
|
||||
String sortOrder, MultiValueMap<String, String> filteringParams) {
|
||||
AdvancedJpaRepository jpaRepository = schema.getJpaRepository();
|
||||
|
||||
long maxElement = count(schema, query);
|
||||
long maxElement = count(schema, query, filteringParams);
|
||||
int maxPage = (int)(Math.ceil ((double)maxElement / pageSize));
|
||||
|
||||
if (page <= 0) page = 1;
|
||||
@ -236,7 +238,7 @@ public class DbAdminRepository {
|
||||
|
||||
return new PaginatedResult(
|
||||
new PaginationInfo(page, maxPage, pageSize, maxElement),
|
||||
jpaRepository.search(query, page, pageSize, sortKey, sortOrder).stream()
|
||||
jpaRepository.search(query, page, pageSize, sortKey, sortOrder, filteringParams).stream()
|
||||
.map(o -> new DbObject(o, schema))
|
||||
.toList()
|
||||
);
|
||||
@ -251,7 +253,7 @@ public class DbAdminRepository {
|
||||
public List<DbObject> search(DbObjectSchema schema, String query) {
|
||||
AdvancedJpaRepository jpaRepository = schema.getJpaRepository();
|
||||
|
||||
return jpaRepository.search(query, 1, 50, null, null).stream()
|
||||
return jpaRepository.search(query, 1, 50, null, null, null).stream()
|
||||
.map(o -> new DbObject(o, schema))
|
||||
.toList();
|
||||
}
|
||||
|
@ -183,9 +183,9 @@ public class DbObjectSchema {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public List<Object> getFieldValues(DbField field) {
|
||||
return jpaRepository.distinctFieldValues(field);
|
||||
}
|
||||
// public List<Object> getFieldValues(DbField field) {
|
||||
// return jpaRepository.distinctFieldValues(field);
|
||||
// }
|
||||
|
||||
public Object[] getInsertArray(Map<String, String> params, Map<String, MultipartFile> files) {
|
||||
int currentIndex = 0;
|
||||
|
@ -4,4 +4,5 @@ spring.datasource.username=sa
|
||||
spring.datasource.password=password
|
||||
#spring.h2.console.enabled=true
|
||||
|
||||
spring.jpa.show-sql=true
|
||||
|
||||
|
@ -38,5 +38,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="card mb-3 filterable-field" th:fragment="filter_field(field)">
|
||||
<div class="card-header noselect cursor-pointer">
|
||||
<i class="bi bi-caret-right filter-icon align-middle"></i>
|
||||
<span class="fw-bold align-middle" th:text="${field.getName()}"></span>
|
||||
</div>
|
||||
<div class="card-body d-none">
|
||||
|
||||
<form action="" method="GET">
|
||||
<th:block th:each="p : ${queryParams.keySet()}">
|
||||
<input th:each="v : ${queryParams.get(p)}" th:name="${p}" th:value="${v}" type="hidden">
|
||||
</th:block>
|
||||
<div class="input-group pe-2">
|
||||
<th:block th:if="${field.isForeignKey()}">
|
||||
<div th:replace="~{fragments/forms :: input_autocomplete(field=${field}, value='')}">
|
||||
</div>
|
||||
<input type="hidden" th:value="${field.getType()}" th:name="|__dbadmin_${field.getName()}_type|">
|
||||
</th:block>
|
||||
<th:block th:unless="${field.isForeignKey()}">
|
||||
<div class="container w-25">
|
||||
<select class="form-select w-auto" name="filter_op[]">
|
||||
<option value="equals">Equals</option>
|
||||
<option value="contains">Contains</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="hidden" name="filter_field[]" th:value="${field.getName()}">
|
||||
<input placeholder="NULL" th:type="${field.getType().getHTMLName()}"
|
||||
name="filter_value[]"
|
||||
class="form-control" th:id="|__id_${field.getName()}|"
|
||||
th:classAppend="${field.isPrimaryKey() && object != null ? 'disable' : ''}"
|
||||
th:required="${!field.isNullable() && !field.isPrimaryKey()}"
|
||||
step="any"
|
||||
oninvalid="this.setCustomValidity('This field is not nullable.')"
|
||||
oninput="this.setCustomValidity('')">
|
||||
<input type="hidden" th:value="${field.getType()}" th:name="|__dbadmin_${field.getName()}_type|">
|
||||
</th:block>
|
||||
<button class="ui-btn btn btn-primary">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!--
|
||||
<th:block th:if="${field.getConnectedType() != null}">
|
||||
<div th:each="val : ${schema.getFieldValues(field)}">
|
||||
<span th:text="${val}"></span>
|
||||
</div>
|
||||
</th:block>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -45,7 +45,7 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
|
||||
<h3 class="fw-bold mb-4 align-baseline w-100">
|
||||
<h3 class="fw-bold mb-4 align-baseline flex-grow-1">
|
||||
<span title="Java class name"> [[ ${schema.getJavaClass().getSimpleName()} ]] </span>
|
||||
<span title="Database table name" class="ms-3 label label-primary label-gray font-monospace">
|
||||
[[ ${schema.getTableName()} ]]
|
||||
@ -55,7 +55,8 @@
|
||||
|
||||
</h3>
|
||||
<h3><a title="Create new item"
|
||||
th:href="|/dbadmin/model/${schema.getClassName()}/create|"><i class="bi bi-plus-square"></i></a></h3>
|
||||
th:href="|/dbadmin/model/${schema.getClassName()}/create|"><i class="bi bi-plus-square"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div th:replace="~{fragments/table_selectable :: table(results=${page.getResults()}, schema=${schema})}">
|
||||
@ -66,20 +67,11 @@
|
||||
<div class="box">
|
||||
<h3 class="fw-bold mb-3"><i class="bi bi-funnel"></i> Filters</h3>
|
||||
|
||||
<div class="card mb-3 filterable-field" th:each="field : ${schema.getFilterableFields()}">
|
||||
<div class="card-header noselect cursor-pointer">
|
||||
<i class="bi bi-caret-right filter-icon align-middle"></i>
|
||||
<span class="fw-bold align-middle" th:text="${field.getName()}"></span>
|
||||
</div>
|
||||
|
||||
<div class="card-body d-none">
|
||||
<th:block th:if="${field.getConnectedType() != null}">
|
||||
<div th:each="val : ${schema.getFieldValues(field)}">
|
||||
<span th:text="${val}"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:each="field : ${schema.getFilterableFields()}">
|
||||
<div th:replace="~{fragments/forms :: filter_field(field=${field})}"></div>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user