This commit is contained in:
Francesco 2023-09-20 20:18:56 +02:00
parent d34676f7a7
commit 82311fd23e
8 changed files with 51 additions and 25 deletions

View File

@ -39,19 +39,23 @@ import tech.ailef.dbadmin.misc.Utils;
@Controller @Controller
@RequestMapping("/dbadmin") @RequestMapping("/dbadmin")
/** /**
* FOR 0.0.3:
* @DisplayImage DONE TODO: write docs in README
* Fixed/improved edit page for binary fields (files) DONE
*
* TODO
* - double data source for internal database and settings * - double data source for internal database and settings
* - role based authorization (PRO) * - role based authorization (PRO)
* - Pagination in one to many results? * - Pagination in one to many results?
* - BLOB upload (WIP: check edit not working)
* - AI console (PRO) * - AI console (PRO)
* - Action logs * - Action logs
* - Boolean icons * - Boolean icons
* - Boolean in create/edit is checkbox * - Boolean in create/edit is checkbox
* - Documentation
* - SQL console (PRO) * - SQL console (PRO)
* - JPA Validation (PRO) * - JPA Validation (PRO)
* - Logging * - Logging
* - Selenium tests * - Selenium tests
* - @DisplayImage
* - Logs in web ui * - Logs in web ui
* - Tests: AutocompleteController, REST API, create/edit * - Tests: AutocompleteController, REST API, create/edit
*/ */
@ -81,7 +85,6 @@ public class DefaultDbAdminController {
model.addAttribute("activePage", "home"); model.addAttribute("activePage", "home");
model.addAttribute("title", "Entities | Index"); model.addAttribute("title", "Entities | Index");
return "home"; return "home";
} }
@ -272,8 +275,6 @@ public class DefaultDbAdminController {
@RequestParam MultiValueMap<String, String> formParams, @RequestParam MultiValueMap<String, String> formParams,
@RequestParam Map<String, MultipartFile> files, @RequestParam Map<String, MultipartFile> files,
RedirectAttributes attr) { RedirectAttributes attr) {
// Extract all parameters that have exactly 1 value, // Extract all parameters that have exactly 1 value,
// as these will be the raw values for the object that is being // as these will be the raw values for the object that is being
// created. // created.

View File

@ -22,6 +22,7 @@ import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.dbmapping.DbFieldValue; import tech.ailef.dbadmin.dbmapping.DbFieldValue;
import tech.ailef.dbadmin.dbmapping.DbObject; import tech.ailef.dbadmin.dbmapping.DbObject;
import tech.ailef.dbadmin.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.exceptions.DbAdminException;
@Controller @Controller
@RequestMapping("/dbadmin/download") @RequestMapping("/dbadmin/download")
@ -65,7 +66,18 @@ public class DownloadController {
if (object.isPresent()) { if (object.isPresent()) {
DbObject dbObject = object.get(); DbObject dbObject = object.get();
DbFieldValue dbFieldValue = dbObject.get(fieldName);
DbFieldValue dbFieldValue;
try {
dbFieldValue = dbObject.get(fieldName);
} catch (DbAdminException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Field not found", e);
}
if (dbFieldValue.getValue() == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "There's no file attached to this item");
}
byte[] file = (byte[])dbFieldValue.getValue(); byte[] file = (byte[])dbFieldValue.getValue();
String filename = schema.getClassName() + "_" + id + "_" + fieldName; String filename = schema.getClassName() + "_" + id + "_" + fieldName;

View File

@ -179,8 +179,11 @@ public class DbObject {
String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
Method[] methods = instance.getClass().getDeclaredMethods(); Method[] methods = instance.getClass().getDeclaredMethods();
DbField dbField = schema.getFieldByJavaName(fieldName);
if (dbField == null) return null;
String prefix = "get"; String prefix = "get";
if (schema.getFieldByJavaName(fieldName).getType() == DbFieldType.BOOLEAN) { if (dbField.getType() == DbFieldType.BOOLEAN) {
prefix = "is"; prefix = "is";
} }

View File

@ -56,7 +56,7 @@ tr.table-data-row td:last-child, tr.table-data-row th:last-child {
.row-icons { .row-icons {
font-size: 1.2rem; font-size: 1.2rem;
width: 128px; width: 96px;
} }
h1 .bi { h1 .bi {
@ -116,6 +116,8 @@ h1 a:hover {
max-height: 300px; max-height: 300px;
overflow: auto; overflow: auto;
z-index: 999; z-index: 999;
-webkit-box-shadow: 0px 11px 12px -1px rgba(0,0,0,0.13);
box-shadow: 0px 11px 12px -1px rgba(0,0,0,0.13);
} }
.suggestion { .suggestion {
@ -128,7 +130,7 @@ h1 a:hover {
.suggestion:hover { .suggestion:hover {
cursor: pointer; cursor: pointer;
background-color: #FFF; background-color: #EBF7FF;
border-bottom: 2px solid #ADDEFF; border-bottom: 2px solid #ADDEFF;
} }

View File

@ -1,15 +1,23 @@
function updateBulkActions(table, selected) { function updateBulkActions(table, selected) {
let divs = document.querySelectorAll(".bulk-actions"); let divs = document.querySelectorAll(".bulk-actions");
divs.forEach(div => { divs.forEach(div => {
div.innerHTML = `${selected} items selected <input type="submit" form="delete-form" class="ui-btn btn btn-secondary" value="Delete">`; div.innerHTML = `${selected} items selected <input type="submit" form="multi-delete-form" class="ui-btn btn btn-secondary" value="Delete">`;
}); });
} }
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
let selected = 0; let selected = 0;
if (document.getElementById('delete-form') != null) { document.querySelectorAll(".delete-form").forEach(form => {
document.getElementById('delete-form').addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
if (!confirm('Are you sure you want to delete this item?')) {
e.preventDefault();
}
});
});
if (document.getElementById('multi-delete-form') != null) {
document.getElementById('multi-delete-form').addEventListener('submit', function(e) {
if (selected == 0) { if (selected == 0) {
e.preventDefault(); e.preventDefault();
alert('No items selected'); alert('No items selected');

View File

@ -5,7 +5,15 @@
<tr th:fragment="data_row(row, selectable)" class="table-data-row"> <tr th:fragment="data_row(row, selectable)" class="table-data-row">
<td th:if=${selectable} class="table-checkbox"> <td th:if=${selectable} class="table-checkbox">
<input type="checkbox" class="form-check-input" name="ids" <input type="checkbox" class="form-check-input" name="ids"
th:value="${row.getPrimaryKeyValue()}" form="delete-form"> th:value="${row.getPrimaryKeyValue()}" form="multi-delete-form">
</td>
<td class="text-center row-icons">
<a class="ps-1" th:href="|/dbadmin/model/${schema.getJavaClass().getName()}/edit/${row.getPrimaryKeyValue()}|">
<i class="bi bi-pencil-square"></i></a>
<form class="delete-form" method="POST"
th:action="|/dbadmin/model/${schema.getJavaClass().getName()}/delete/${row.getPrimaryKeyValue()}|">
<button><i class="bi bi-trash"></i></button>
</form>
</td> </td>
<td th:each="field : ${schema.getSortedFields()}" <td th:each="field : ${schema.getSortedFields()}"
th:classAppend="${field.isBinary() ? 'text-center' : ''}"> th:classAppend="${field.isBinary() ? 'text-center' : ''}">
@ -20,15 +28,6 @@
<td th:each="colName : ${schema.getComputedColumnNames()}"> <td th:each="colName : ${schema.getComputedColumnNames()}">
<span th:text="${row.compute(colName)}"></span> <span th:text="${row.compute(colName)}"></span>
</td> </td>
<td class="text-center row-icons" th:if="${selectable}">
<a class="ps-1" th:href="|/dbadmin/model/${schema.getJavaClass().getName()}/edit/${row.getPrimaryKeyValue()}|">
<i class="bi bi-pencil-square"></i></a>
<form class="delete-form" method="POST"
th:action="|/dbadmin/model/${schema.getJavaClass().getName()}/delete/${row.getPrimaryKeyValue()}|">
<button><i class="bi bi-trash"></i></button>
</form>
</td>
</tr> </tr>

View File

@ -9,6 +9,7 @@
<div th:if="${results != null && results.size() > 0}"> <div th:if="${results != null && results.size() > 0}">
<table class="table table-striped align-middle mt-3"> <table class="table table-striped align-middle mt-3">
<tr class="table-data-row"> <tr class="table-data-row">
<th class="row-icons"></th>
<th th:each="field : ${schema.getSortedFields()}"> <th th:each="field : ${schema.getSortedFields()}">
<div class="m-0 p-0 d-flex justify-content-between"> <div class="m-0 p-0 d-flex justify-content-between">
<div class="column-title"> <div class="column-title">

View File

@ -2,18 +2,19 @@
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head></head> <head></head>
<body> <body>
<div class="table-selectable" th:fragment="table(results, schema)"> <div class="table-selectable table-responsive" th:fragment="table(results, schema)">
<div th:if="${results.isEmpty()}"> <div th:if="${results.isEmpty()}">
<p>This table contains no data.</p> <p>This table contains no data.</p>
</div> </div>
<div th:if="${results.size() > 0}"> <div th:if="${results.size() > 0}">
<form id="delete-form" th:action="|/dbadmin/model/${schema.getClassName()}/delete|" method="POST"> <form id="multi-delete-form" th:action="|/dbadmin/model/${schema.getClassName()}/delete|" method="POST">
</form> </form>
<nav th:replace="~{fragments/resources :: pagination(${page})}"> <nav th:replace="~{fragments/resources :: pagination(${page})}">
</nav> </nav>
<table class="table table-striped align-middle mt-3"> <table class="table table-striped align-middle mt-3">
<tr class="table-data-row"> <tr class="table-data-row">
<th class="table-checkbox"><input type="checkbox" class="form-check-input check-all"></th> <th class="table-checkbox"><input type="checkbox" class="form-check-input check-all"></th>
<th></th>
<th class="table-data-row" th:each="field : ${schema.getSortedFields()}"> <th class="table-data-row" th:each="field : ${schema.getSortedFields()}">
<div class="m-0 p-0 d-flex justify-content-between"> <div class="m-0 p-0 d-flex justify-content-between">
<div class="column-title"> <div class="column-title">
@ -56,7 +57,6 @@
</div> </div>
<p class="m-0 p-0 dbfieldtype"><small>COMPUTED</small></p> <p class="m-0 p-0 dbfieldtype"><small>COMPUTED</small></p>
</th> </th>
<th></th>
</tr> </tr>
<th:block th:each="r : ${results}"> <th:block th:each="r : ${results}">
<tr th:replace="~{fragments/data_row :: data_row(row=${r},selectable=${true})}"></tr> <tr th:replace="~{fragments/data_row :: data_row(row=${r},selectable=${true})}"></tr>