diff --git a/src/main/java/tech/ailef/dbadmin/annotations/DisplayImage.java b/src/main/java/tech/ailef/dbadmin/annotations/DisplayImage.java new file mode 100644 index 0000000..8828301 --- /dev/null +++ b/src/main/java/tech/ailef/dbadmin/annotations/DisplayImage.java @@ -0,0 +1,11 @@ +package tech.ailef.dbadmin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DisplayImage { +} \ No newline at end of file diff --git a/src/main/java/tech/ailef/dbadmin/controller/DefaultDbAdminController.java b/src/main/java/tech/ailef/dbadmin/controller/DefaultDbAdminController.java index f002e35..be5a78f 100644 --- a/src/main/java/tech/ailef/dbadmin/controller/DefaultDbAdminController.java +++ b/src/main/java/tech/ailef/dbadmin/controller/DefaultDbAdminController.java @@ -39,14 +39,6 @@ import tech.ailef.dbadmin.misc.Utils; @Controller @RequestMapping("/dbadmin") /** - * - Sort controls DONE - * - @DisplayFormat for fields DONE - * - Fix pagination in product where total count = page size = 50 (it shows 'next' button and then empty page) DONE - * - Show number of entries in home DONE - * - @ComputedColumn name parameter DONE - * - Basic search - * - Improve create/edit UX WIP - * - blob edit doesn't show if it's present WIP * - double data source for internal database and settings * - role based authorization (PRO) * - Pagination in one to many results? @@ -54,13 +46,12 @@ import tech.ailef.dbadmin.misc.Utils; * - AI console (PRO) * - Action logs * - Boolean icons - * - @Filterable * - Boolean in create/edit is checkbox * - SQL console (PRO) * - JPA Validation (PRO) * - Logging - * - TODO FIX: list model page crash - * EDIT error on table product + * - Selenium tests + * - @DisplayImage * - Logs in web ui * - Tests: AutocompleteController, REST API, create/edit */ diff --git a/src/main/java/tech/ailef/dbadmin/controller/DownloadController.java b/src/main/java/tech/ailef/dbadmin/controller/DownloadController.java index a6446e9..354b1a2 100644 --- a/src/main/java/tech/ailef/dbadmin/controller/DownloadController.java +++ b/src/main/java/tech/ailef/dbadmin/controller/DownloadController.java @@ -8,6 +8,7 @@ import org.apache.tika.mime.MimeTypes; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -31,6 +32,28 @@ public class DownloadController { @Autowired private DbAdmin dbAdmin; + + @GetMapping(value="/{className}/{fieldName}/{id}/image", produces = MediaType.IMAGE_JPEG_VALUE) + @ResponseBody + public ResponseEntity serveImage(@PathVariable String className, + @PathVariable String fieldName, @PathVariable String id) { + + DbObjectSchema schema = dbAdmin.findSchemaByClassName(className); + + Optional object = repository.findById(schema, id); + + if (object.isPresent()) { + DbObject dbObject = object.get(); + DbFieldValue dbFieldValue = dbObject.get(fieldName); + byte[] file = (byte[])dbFieldValue.getValue(); + return ResponseEntity.ok(file); + } else { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Object with id " + id + " not found"); + } + + + } + @GetMapping("/{className}/{fieldName}/{id}") @ResponseBody public ResponseEntity serveFile(@PathVariable String className, diff --git a/src/main/java/tech/ailef/dbadmin/dbmapping/AdvancedJpaRepository.java b/src/main/java/tech/ailef/dbadmin/dbmapping/AdvancedJpaRepository.java index e28a83c..05c6a7a 100644 --- a/src/main/java/tech/ailef/dbadmin/dbmapping/AdvancedJpaRepository.java +++ b/src/main/java/tech/ailef/dbadmin/dbmapping/AdvancedJpaRepository.java @@ -80,6 +80,7 @@ public class AdvancedJpaRepository extends SimpleJpaRepository { .setFirstResult((page - 1) * pageSize).getResultList(); } + @SuppressWarnings("unchecked") private List buildPredicates(String q, Set queryFilters, CriteriaBuilder cb, Path root) { List finalPredicates = new ArrayList<>(); @@ -152,7 +153,8 @@ public class AdvancedJpaRepository extends SimpleJpaRepository { return finalPredicates; } - public void update(DbObjectSchema schema, Map params, Map files) { + @SuppressWarnings("unchecked") + public int update(DbObjectSchema schema, Map params, Map files) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaUpdate update = cb.createCriteriaUpdate(schema.getJavaClass()); @@ -162,8 +164,9 @@ public class AdvancedJpaRepository extends SimpleJpaRepository { for (DbField field : schema.getSortedFields()) { if (field.isPrimaryKey()) continue; - if (params.getOrDefault("__keep_" + field.getJavaName(), "off").equals("on")) { - System.out.println("SKIPPING: " + field); + boolean keepValue = params.getOrDefault("__keep_" + field.getJavaName(), "off").equals("on"); + + if (keepValue) { continue; } @@ -175,8 +178,10 @@ public class AdvancedJpaRepository extends SimpleJpaRepository { } else { try { MultipartFile file = files.get(field.getName()); - if (file != null) - value = file.getBytes(); + if (file != null) { + if (file.isEmpty()) value = null; + else value = file.getBytes(); + } } catch (IOException e) { throw new DbAdminException(e); } @@ -184,11 +189,11 @@ public class AdvancedJpaRepository extends SimpleJpaRepository { update.set(employee.get(field.getJavaName()), value); } + String pkName = schema.getPrimaryKey().getJavaName(); update.where(cb.equal(employee.get(pkName), params.get(schema.getPrimaryKey().getName()))); Query query = entityManager.createQuery(update); - int rowCount = query.executeUpdate(); - + return query.executeUpdate(); } } diff --git a/src/main/java/tech/ailef/dbadmin/dbmapping/DbField.java b/src/main/java/tech/ailef/dbadmin/dbmapping/DbField.java index 57a1550..752fdf0 100644 --- a/src/main/java/tech/ailef/dbadmin/dbmapping/DbField.java +++ b/src/main/java/tech/ailef/dbadmin/dbmapping/DbField.java @@ -4,6 +4,8 @@ import java.lang.reflect.Field; import com.fasterxml.jackson.annotation.JsonIgnore; +import tech.ailef.dbadmin.annotations.DisplayImage; + public class DbField { protected String dbName; @@ -114,6 +116,10 @@ public class DbField { return type == DbFieldType.BYTE_ARRAY; } + public boolean isImage() { + return field.getAnnotation(DisplayImage.class) != null; + } + public String getFormat() { return format; } diff --git a/src/main/resources/static/css/dbadmin.css b/src/main/resources/static/css/dbadmin.css index 2121c85..b2876a5 100644 --- a/src/main/resources/static/css/dbadmin.css +++ b/src/main/resources/static/css/dbadmin.css @@ -1,3 +1,7 @@ +.separator-light { + opacity: 25%; +} + form.delete-form { display: inline-block; } @@ -173,4 +177,15 @@ AUTOCOMPLETE .filterable-field .card-header:hover { background-color: #F0F0F0; +} + +/** + * Images + */ +.thumb-image { + max-width: 128px; +} +.img-muted { + filter: brightness(50%); + } \ No newline at end of file diff --git a/src/main/resources/static/js/create.js b/src/main/resources/static/js/create.js index 71506a5..58e73e1 100644 --- a/src/main/resources/static/js/create.js +++ b/src/main/resources/static/js/create.js @@ -1,3 +1,24 @@ +function showFileInput(inputElement) { + inputElement.classList.add('d-block'); + inputElement.classList.remove('d-none'); + inputElement.value = ''; + + let img = document.getElementById(`__thumb_${inputElement.name}`); + if (img != null) { + img.classList.add('img-muted'); + } +} + +function hideFileInput(inputElement) { + inputElement.classList.add('d-none'); + inputElement.classList.remove('d-block'); + + let img = document.getElementById(`__thumb_${inputElement.name}`); + if (img != null) { + img.classList.remove('img-muted'); + } +} + document.addEventListener("DOMContentLoaded", () => { let checkboxes = document.querySelectorAll(".binary-field-checkbox"); @@ -5,22 +26,16 @@ document.addEventListener("DOMContentLoaded", () => { let fieldName = checkbox.dataset.fieldname; if (!checkbox.checked) { - document.querySelector(`input[name="${fieldName}"]`).classList.add('d-block'); - document.querySelector(`input[name="${fieldName}"]`).classList.remove('d-none'); - document.querySelector(`input[name="${fieldName}"]`).value = ''; + showFileInput(document.querySelector(`input[name="${fieldName}"]`)); } else { - document.querySelector(`input[name="${fieldName}"]`).classList.add('d-none'); - document.querySelector(`input[name="${fieldName}"]`).classList.remove('d-block'); + hideFileInput(document.querySelector(`input[name="${fieldName}"]`)); } checkbox.addEventListener('change', function(e) { if (!e.target.checked) { - document.querySelector(`input[name="${fieldName}"]`).classList.add('d-block'); - document.querySelector(`input[name="${fieldName}"]`).classList.remove('d-none'); - document.querySelector(`input[name="${fieldName}"]`).value = ''; + showFileInput(document.querySelector(`input[name="${fieldName}"]`)); } else { - document.querySelector(`input[name="${fieldName}"]`).classList.add('d-none'); - document.querySelector(`input[name="${fieldName}"]`).classList.remove('d-block'); + hideFileInput(document.querySelector(`input[name="${fieldName}"]`)); } }); diff --git a/src/main/resources/templates/fragments/data_row.html b/src/main/resources/templates/fragments/data_row.html index 872c2cc..e1de7f5 100644 --- a/src/main/resources/templates/fragments/data_row.html +++ b/src/main/resources/templates/fragments/data_row.html @@ -7,7 +7,8 @@ - + NULL @@ -61,12 +62,19 @@ +
+ +
+ + th:href="|/dbadmin/download/${schema.getClassName()}/${field.getJavaName()}/${object.getPrimaryKeyValue()}|"> Download ([[ ${object.get(field).getValue().length} ]] bytes) + +
NULL diff --git a/src/main/resources/templates/model/create.html b/src/main/resources/templates/model/create.html index c4654ed..606b877 100644 --- a/src/main/resources/templates/model/create.html +++ b/src/main/resources/templates/model/create.html @@ -13,9 +13,10 @@

- Entities + Entities - [[ ${schema.getJavaClass().getSimpleName()} ]] + + [[ ${schema.getJavaClass().getSimpleName()} ]] @@ -30,8 +31,9 @@
- - +
- Binary field flag + + Binary field -
+ Edit options +
Keep current data +
+ +
+ File input +
diff --git a/src/main/resources/templates/model/show.html b/src/main/resources/templates/model/show.html index 7f04556..8af214d 100644 --- a/src/main/resources/templates/model/show.html +++ b/src/main/resources/templates/model/show.html @@ -12,7 +12,7 @@

Entities - + [[ ${schema.getJavaClass().getSimpleName()} ]] [[ ${object.getDisplayName()} ]] @@ -44,7 +44,7 @@ - +