mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-08-07 12:41:11 +00:00
0.1.0
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
package tech.ailef.dbadmin;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@ConditionalOnProperty(name = "dbadmin.enabled", matchIfMissing = true)
|
||||
@ComponentScan
|
||||
@EnableConfigurationProperties(DbAdminProperties.class)
|
||||
@AutoConfiguration
|
||||
public class DbAdminAutoConfiguration {
|
||||
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
//package tech.ailef.dbadmin.dto;
|
||||
//
|
||||
//import java.util.Set;
|
||||
//
|
||||
//public class ListModelRequest {
|
||||
// private String className;
|
||||
//
|
||||
// private String query;
|
||||
//
|
||||
// private Integer page;
|
||||
//
|
||||
// private Integer pageSize;
|
||||
//
|
||||
// private String sortKey;
|
||||
//
|
||||
// private String sortOrder;
|
||||
//
|
||||
// private Set<QueryFilter> queryFilters;
|
||||
//
|
||||
// private PaginationInfo paginationInfo;
|
||||
//
|
||||
// public ListModelRequest(String className, String query, Integer page, Integer pageSize, String sortKey,
|
||||
// String sortOrder, Set<QueryFilter> queryFilters, PaginationInfo paginationInfo) {
|
||||
// super();
|
||||
// this.className = className;
|
||||
// this.query = query;
|
||||
// this.page = page;
|
||||
// this.pageSize = pageSize;
|
||||
// this.sortKey = sortKey;
|
||||
// this.sortOrder = sortOrder;
|
||||
// this.queryFilters = queryFilters;
|
||||
// this.paginationInfo = paginationInfo;
|
||||
// }
|
||||
//
|
||||
//
|
||||
//// @RequestParam MultiValueMap<String, String> otherParams,
|
||||
//}
|
@@ -1,30 +0,0 @@
|
||||
package tech.ailef.dbadmin.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import tech.ailef.dbadmin.dbmapping.DbObject;
|
||||
|
||||
public class PaginatedResult {
|
||||
private PaginationInfo pagination;
|
||||
|
||||
private List<DbObject> results;
|
||||
|
||||
public PaginatedResult(PaginationInfo pagination, List<DbObject> page) {
|
||||
this.pagination = pagination;
|
||||
this.results = page;
|
||||
}
|
||||
|
||||
public PaginationInfo getPagination() {
|
||||
return pagination;
|
||||
}
|
||||
|
||||
public List<DbObject> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public int getActualResults() {
|
||||
return getResults().size();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin;
|
||||
package tech.ailef.dbadmin.external;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -10,9 +10,7 @@ import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -26,14 +24,13 @@ import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import tech.ailef.dbadmin.annotations.DisplayFormat;
|
||||
import tech.ailef.dbadmin.dbmapping.AdvancedJpaRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbField;
|
||||
import tech.ailef.dbadmin.dbmapping.DbFieldType;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.misc.Utils;
|
||||
import tech.ailef.dbadmin.external.annotations.DisplayFormat;
|
||||
import tech.ailef.dbadmin.external.dbmapping.AdvancedJpaRepository;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbField;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbFieldType;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.misc.Utils;
|
||||
|
||||
/**
|
||||
* The main DbAdmin class responsible for the initialization phase. This class scans
|
||||
@@ -48,7 +45,7 @@ import tech.ailef.dbadmin.misc.Utils;
|
||||
public class DbAdmin {
|
||||
private static final Logger logger = Logger.getLogger(DbAdmin.class.getName());
|
||||
|
||||
@PersistenceContext
|
||||
// @PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
private List<DbObjectSchema> schemas = new ArrayList<>();
|
||||
@@ -91,6 +88,18 @@ public class DbAdmin {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a schema by its table name
|
||||
* @param tableName the table name on the database
|
||||
* @return
|
||||
* @throws DbAdminException if corresponding schema not found
|
||||
*/
|
||||
public DbObjectSchema findSchemaByTableName(String tableName) {
|
||||
return schemas.stream().filter(s -> s.getTableName().equals(tableName)).findFirst().orElseThrow(() -> {
|
||||
return new DbAdminException("Schema " + tableName + " not found.");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a schema by its class
|
||||
* @param klass
|
76
src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java
vendored
Normal file
76
src/main/java/tech/ailef/dbadmin/external/DbAdminAutoConfiguration.java
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package tech.ailef.dbadmin.external;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import tech.ailef.dbadmin.internal.InternalDbAdminConfiguration;
|
||||
|
||||
@ConditionalOnProperty(name = "dbadmin.enabled", matchIfMissing = true)
|
||||
@ComponentScan
|
||||
@EnableConfigurationProperties(DbAdminProperties.class)
|
||||
@Configuration
|
||||
@EnableJpaRepositories(
|
||||
entityManagerFactoryRef = "internalEntityManagerFactory",
|
||||
transactionManagerRef = "internalTransactionManager",
|
||||
basePackages = { "tech.ailef.dbadmin.internal.repository" }
|
||||
)
|
||||
@EnableTransactionManagement
|
||||
@Import(InternalDbAdminConfiguration.class)
|
||||
public class DbAdminAutoConfiguration {
|
||||
@Autowired
|
||||
private DbAdminProperties props;
|
||||
|
||||
@Bean
|
||||
public DataSource internalDataSource() {
|
||||
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
|
||||
dataSourceBuilder.driverClassName("org.h2.Driver");
|
||||
if (props.isTestMode()) {
|
||||
dataSourceBuilder.url("jdbc:h2:mem:test");
|
||||
} else {
|
||||
dataSourceBuilder.url("jdbc:h2:file:./dbadmin_internal");
|
||||
}
|
||||
|
||||
dataSourceBuilder.username("sa");
|
||||
dataSourceBuilder.password("password");
|
||||
return dataSourceBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean internalEntityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(internalDataSource());
|
||||
factoryBean.setPersistenceUnitName("internal");
|
||||
factoryBean.setPackagesToScan("tech.ailef.dbadmin.internal.model"); // , "tech.ailef.dbadmin.repository");
|
||||
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
|
||||
properties.setProperty("hibernate.hbm2ddl.auto", "update");
|
||||
factoryBean.setJpaProperties(properties);
|
||||
factoryBean.afterPropertiesSet();
|
||||
return factoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager internalTransactionManager() {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setEntityManagerFactory(internalEntityManagerFactory().getObject());
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin;
|
||||
package tech.ailef.dbadmin.external;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -26,6 +26,8 @@ public class DbAdminProperties {
|
||||
*/
|
||||
private String modelsPackage;
|
||||
|
||||
private boolean testMode = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
@@ -50,11 +52,20 @@ public class DbAdminProperties {
|
||||
this.modelsPackage = modelsPackage;
|
||||
}
|
||||
|
||||
public boolean isTestMode() {
|
||||
return testMode;
|
||||
}
|
||||
|
||||
public void setTestMode(boolean testMode) {
|
||||
this.testMode = testMode;
|
||||
}
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
Map<String, String> conf = new HashMap<>();
|
||||
conf.put("enabled", enabled + "");
|
||||
conf.put("baseUrl", baseUrl);
|
||||
conf.put("modelsPackage", modelsPackage);
|
||||
conf.put("testMode", testMode + "");
|
||||
return conf;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.annotations;
|
||||
package tech.ailef.dbadmin.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.annotations;
|
||||
package tech.ailef.dbadmin.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.annotations;
|
||||
package tech.ailef.dbadmin.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.annotations;
|
||||
package tech.ailef.dbadmin.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.annotations;
|
||||
package tech.ailef.dbadmin.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.controller;
|
||||
package tech.ailef.dbadmin.external.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -19,6 +19,7 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -27,16 +28,21 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.exceptions.InvalidPageException;
|
||||
import tech.ailef.dbadmin.misc.Utils;
|
||||
import tech.ailef.dbadmin.external.DbAdmin;
|
||||
import tech.ailef.dbadmin.external.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.external.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.external.dto.LogsSearchRequest;
|
||||
import tech.ailef.dbadmin.external.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.external.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.external.exceptions.InvalidPageException;
|
||||
import tech.ailef.dbadmin.external.misc.Utils;
|
||||
import tech.ailef.dbadmin.internal.model.UserAction;
|
||||
import tech.ailef.dbadmin.internal.model.UserSetting;
|
||||
import tech.ailef.dbadmin.internal.repository.UserSettingsRepository;
|
||||
import tech.ailef.dbadmin.internal.service.UserActionService;
|
||||
|
||||
/**
|
||||
* The main DbAdmin controller that register most of the routes of the web interface.
|
||||
@@ -52,7 +58,14 @@ public class DefaultDbAdminController {
|
||||
|
||||
@Autowired
|
||||
private DbAdmin dbAdmin;
|
||||
|
||||
@Autowired
|
||||
private UserActionService userActionService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private UserSettingsRepository userSettingsRepo;
|
||||
|
||||
/**
|
||||
* Home page with list of schemas
|
||||
* @param model
|
||||
@@ -61,6 +74,7 @@ public class DefaultDbAdminController {
|
||||
*/
|
||||
@GetMapping
|
||||
public String index(Model model, @RequestParam(required = false) String query) {
|
||||
|
||||
List<DbObjectSchema> schemas = dbAdmin.getSchemas();
|
||||
if (query != null && !query.isBlank()) {
|
||||
schemas = schemas.stream().filter(s -> {
|
||||
@@ -149,7 +163,7 @@ public class DefaultDbAdminController {
|
||||
DbObjectSchema schema = dbAdmin.findSchemaByClassName(className);
|
||||
|
||||
try {
|
||||
PaginatedResult result = null;
|
||||
PaginatedResult<DbObject> result = null;
|
||||
if (query != null || !otherParams.isEmpty()) {
|
||||
result = repository.search(schema, query, page, pageSize, sortKey, sortOrder, queryFilters);
|
||||
} else {
|
||||
@@ -264,6 +278,8 @@ public class DefaultDbAdminController {
|
||||
attr.addFlashAttribute("error", e.getMessage());
|
||||
}
|
||||
|
||||
saveAction(new UserAction(schema.getTableName(), id, "DELETE"));
|
||||
|
||||
return "redirect:/" + properties.getBaseUrl() + "/model/" + className;
|
||||
}
|
||||
|
||||
@@ -291,6 +307,10 @@ public class DefaultDbAdminController {
|
||||
if (countDeleted > 0)
|
||||
attr.addFlashAttribute("message", "Deleted " + countDeleted + " of " + ids.length + " items");
|
||||
|
||||
for (String id : ids) {
|
||||
saveAction(new UserAction(schema.getTableName(), id, "DELETE"));
|
||||
}
|
||||
|
||||
return "redirect:/" + properties.getBaseUrl() + "/model/" + className;
|
||||
}
|
||||
|
||||
@@ -353,6 +373,7 @@ public class DefaultDbAdminController {
|
||||
repository.attachManyToMany(schema, newPrimaryKey, multiValuedParams);
|
||||
pkValue = newPrimaryKey.toString();
|
||||
attr.addFlashAttribute("message", "Item created successfully.");
|
||||
saveAction(new UserAction(schema.getTableName(), pkValue, "CREATE"));
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
attr.addFlashAttribute("errorTitle", "Unable to INSERT row");
|
||||
attr.addFlashAttribute("error", e.getMessage());
|
||||
@@ -376,6 +397,7 @@ public class DefaultDbAdminController {
|
||||
repository.update(schema, params, files);
|
||||
repository.attachManyToMany(schema, pkValue, multiValuedParams);
|
||||
attr.addFlashAttribute("message", "Item saved successfully.");
|
||||
saveAction(new UserAction(schema.getTableName(), pkValue, "EDIT"));
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
attr.addFlashAttribute("errorTitle", "Unable to UPDATE row (no changes applied)");
|
||||
attr.addFlashAttribute("error", e.getMessage());
|
||||
@@ -391,6 +413,7 @@ public class DefaultDbAdminController {
|
||||
Object newPrimaryKey = repository.create(schema, params, files, pkValue);
|
||||
repository.attachManyToMany(schema, newPrimaryKey, multiValuedParams);
|
||||
attr.addFlashAttribute("message", "Item created successfully");
|
||||
saveAction(new UserAction(schema.getTableName(), pkValue, "CREATE"));
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
attr.addFlashAttribute("errorTitle", "Unable to INSERT row (no changes applied)");
|
||||
attr.addFlashAttribute("error", e.getMessage());
|
||||
@@ -409,6 +432,23 @@ public class DefaultDbAdminController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/logs")
|
||||
public String logs(Model model, LogsSearchRequest searchRequest) {
|
||||
model.addAttribute("activePage", "logs");
|
||||
model.addAttribute(
|
||||
"page",
|
||||
userActionService.findActions(
|
||||
searchRequest.getTable(),
|
||||
searchRequest.getActionType(),
|
||||
searchRequest.getItemId(),
|
||||
searchRequest.toPageRequest()
|
||||
)
|
||||
);
|
||||
model.addAttribute("schemas", dbAdmin.getSchemas());
|
||||
model.addAttribute("searchRequest", searchRequest);
|
||||
return "logs";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String settings(Model model) {
|
||||
@@ -416,5 +456,16 @@ public class DefaultDbAdminController {
|
||||
return "settings";
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/settings")
|
||||
public String settings(@RequestParam Map<String, String> params, Model model) {
|
||||
for (String paramName : params.keySet()) {
|
||||
userSettingsRepo.save(new UserSetting(paramName, params.get(paramName)));
|
||||
}
|
||||
model.addAttribute("activePage", "settings");
|
||||
return "settings";
|
||||
}
|
||||
|
||||
private UserAction saveAction(UserAction action) {
|
||||
return userActionService.save(action);
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.controller;
|
||||
package tech.ailef.dbadmin.external.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -17,12 +17,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbFieldValue;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.DbAdmin;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbFieldValue;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
|
||||
/**
|
||||
* Controller to serve file or images (`@DisplayImage`)
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.controller;
|
||||
package tech.ailef.dbadmin.external.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -7,7 +7,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import tech.ailef.dbadmin.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.external.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.internal.UserConfiguration;
|
||||
|
||||
/**
|
||||
* This class registers some ModelAttribute objects that are
|
||||
@@ -18,6 +19,9 @@ public class GlobalController {
|
||||
|
||||
@Autowired
|
||||
private DbAdminProperties props;
|
||||
|
||||
@Autowired
|
||||
private UserConfiguration userConf;
|
||||
|
||||
/**
|
||||
* A multi valued map containing the query parameters. It is used primarily
|
||||
@@ -36,7 +40,18 @@ public class GlobalController {
|
||||
* @return
|
||||
*/
|
||||
@ModelAttribute("baseUrl")
|
||||
public String getBaseUrl(HttpServletRequest request) {
|
||||
public String getBaseUrl() {
|
||||
return props.getBaseUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@ModelAttribute("requestUrl")
|
||||
public String getRequestUrl(HttpServletRequest request) {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@ModelAttribute("userConf")
|
||||
public UserConfiguration getUserConf() {
|
||||
return userConf;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.controller.rest;
|
||||
package tech.ailef.dbadmin.external.controller.rest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.dto.AutocompleteSearchResult;
|
||||
import tech.ailef.dbadmin.external.DbAdmin;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.external.dto.AutocompleteSearchResult;
|
||||
|
||||
/**
|
||||
* API controller for autocomplete results
|
@@ -1,8 +1,6 @@
|
||||
package tech.ailef.dbadmin.controller.rest;
|
||||
package tech.ailef.dbadmin.external.controller.rest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,12 +12,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.DbAdmin;
|
||||
import tech.ailef.dbadmin.external.DbAdminProperties;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
|
||||
import tech.ailef.dbadmin.external.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = {"/${dbadmin.baseUrl}/api", "/${dbadmin.baseUrl}/api/"})
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
@@ -21,9 +21,9 @@ import jakarta.persistence.criteria.CriteriaUpdate;
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import tech.ailef.dbadmin.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.external.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AdvancedJpaRepository extends SimpleJpaRepository {
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -18,14 +18,14 @@ 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.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import tech.ailef.dbadmin.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.dto.PaginationInfo;
|
||||
import tech.ailef.dbadmin.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.exceptions.InvalidPageException;
|
||||
import tech.ailef.dbadmin.external.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.external.dto.PaginationInfo;
|
||||
import tech.ailef.dbadmin.external.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.exceptions.InvalidPageException;
|
||||
|
||||
/**
|
||||
* Implements the basic CRUD operations (and some more)
|
||||
@@ -117,7 +117,7 @@ public class DbAdminRepository {
|
||||
}
|
||||
|
||||
|
||||
return new PaginatedResult(
|
||||
return new PaginatedResult<DbObject>(
|
||||
new PaginationInfo(page, maxPage, pageSize, maxElement, null, new HashSet<>()),
|
||||
results
|
||||
);
|
||||
@@ -128,18 +128,18 @@ public class DbAdminRepository {
|
||||
* @param schema
|
||||
* @param params
|
||||
*/
|
||||
@Transactional
|
||||
@Transactional("transactionManager")
|
||||
public void update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
|
||||
schema.getJpaRepository().update(schema, params, files);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Transactional
|
||||
@Transactional("transactionManager")
|
||||
private void save(DbObjectSchema schema, DbObject o) {
|
||||
schema.getJpaRepository().save(o.getUnderlyingInstance());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Transactional("transactionManager")
|
||||
public void attachManyToMany(DbObjectSchema schema, Object id, Map<String, List<String>> params) {
|
||||
Optional<DbObject> optional = findById(schema, id);
|
||||
|
||||
@@ -215,7 +215,7 @@ public class DbAdminRepository {
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
public PaginatedResult search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey,
|
||||
public PaginatedResult<DbObject> search(DbObjectSchema schema, String query, int page, int pageSize, String sortKey,
|
||||
String sortOrder, Set<QueryFilter> queryFilters) {
|
||||
AdvancedJpaRepository jpaRepository = schema.getJpaRepository();
|
||||
|
||||
@@ -227,7 +227,7 @@ public class DbAdminRepository {
|
||||
throw new InvalidPageException();
|
||||
}
|
||||
|
||||
return new PaginatedResult(
|
||||
return new PaginatedResult<DbObject>(
|
||||
new PaginationInfo(page, maxPage, pageSize, maxElement, query, queryFilters),
|
||||
jpaRepository.search(query, page, pageSize, sortKey, sortOrder, queryFilters).stream()
|
||||
.map(o -> new DbObject(o, schema))
|
||||
@@ -256,7 +256,7 @@ public class DbAdminRepository {
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Transactional
|
||||
@Transactional("transactionManager")
|
||||
public void delete(DbObjectSchema schema, String id) {
|
||||
schema.getJpaRepository().deleteById(id);
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import tech.ailef.dbadmin.annotations.DisplayImage;
|
||||
import tech.ailef.dbadmin.external.annotations.DisplayImage;
|
||||
|
||||
public class DbField {
|
||||
protected String dbName;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -11,8 +11,8 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import tech.ailef.dbadmin.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
|
||||
public enum DbFieldType {
|
||||
INTEGER {
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -11,9 +11,9 @@ import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import tech.ailef.dbadmin.annotations.DisplayName;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.misc.Utils;
|
||||
import tech.ailef.dbadmin.external.annotations.DisplayName;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.misc.Utils;
|
||||
|
||||
public class DbObject {
|
||||
private Object instance;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dbmapping;
|
||||
package tech.ailef.dbadmin.external.dbmapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
@@ -16,11 +16,11 @@ import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import tech.ailef.dbadmin.DbAdmin;
|
||||
import tech.ailef.dbadmin.annotations.ComputedColumn;
|
||||
import tech.ailef.dbadmin.annotations.Filterable;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.misc.Utils;
|
||||
import tech.ailef.dbadmin.external.DbAdmin;
|
||||
import tech.ailef.dbadmin.external.annotations.ComputedColumn;
|
||||
import tech.ailef.dbadmin.external.annotations.Filterable;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.misc.Utils;
|
||||
|
||||
public class DbObjectSchema {
|
||||
/**
|
@@ -1,6 +1,6 @@
|
||||
package tech.ailef.dbadmin.dto;
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
import tech.ailef.dbadmin.dbmapping.DbObject;
|
||||
import tech.ailef.dbadmin.external.dbmapping.DbObject;
|
||||
|
||||
public class AutocompleteSearchResult {
|
||||
private Object id;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dto;
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
public enum CompareOperator {
|
||||
GT {
|
98
src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java
vendored
Normal file
98
src/main/java/tech/ailef/dbadmin/external/dto/LogsSearchRequest.java
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class LogsSearchRequest {
|
||||
private String table;
|
||||
|
||||
private String actionType;
|
||||
|
||||
private String itemId;
|
||||
|
||||
private int page;
|
||||
|
||||
private int pageSize;
|
||||
|
||||
private String sortKey;
|
||||
|
||||
private String sortOrder;
|
||||
|
||||
public String getTable() {
|
||||
return table == null || table.isBlank() || table.equalsIgnoreCase("Any") ? null : table;
|
||||
}
|
||||
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public String getActionType() {
|
||||
return actionType == null || actionType.isBlank() || actionType.equalsIgnoreCase("Any") ? null : actionType;
|
||||
}
|
||||
|
||||
public void setActionType(String actionType) {
|
||||
this.actionType = actionType;
|
||||
}
|
||||
|
||||
public String getItemId() {
|
||||
return itemId == null || itemId.isBlank() ? null : itemId;
|
||||
}
|
||||
|
||||
public void setItemId(String itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(int pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public String getSortKey() {
|
||||
return sortKey;
|
||||
}
|
||||
|
||||
public void setSortKey(String sortKey) {
|
||||
this.sortKey = sortKey;
|
||||
}
|
||||
|
||||
public String getSortOrder() {
|
||||
return sortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(String sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogsSearchRequest [table=" + table + ", actionType=" + actionType + ", itemId=" + itemId + ", page="
|
||||
+ page + ", pageSize=" + pageSize + ", sortKey=" + sortKey + ", sortOrder=" + sortOrder + "]";
|
||||
}
|
||||
|
||||
public PageRequest toPageRequest() {
|
||||
int actualPage = page - 1 < 0 ? 0 : page - 1;
|
||||
int actualPageSize = pageSize <= 0 ? 50 : pageSize;
|
||||
if (sortKey == null)
|
||||
return PageRequest.of(actualPage, actualPageSize);
|
||||
|
||||
if (sortOrder == null) sortOrder = "ASC";
|
||||
|
||||
if (sortOrder.equals("DESC")) {
|
||||
return PageRequest.of(actualPage, actualPageSize, Sort.by(sortKey).descending());
|
||||
} else {
|
||||
return PageRequest.of(actualPage, actualPageSize, Sort.by(sortKey).ascending());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java
vendored
Normal file
32
src/main/java/tech/ailef/dbadmin/external/dto/PaginatedResult.java
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PaginatedResult<T> {
|
||||
private PaginationInfo pagination;
|
||||
|
||||
private List<T> results;
|
||||
|
||||
public PaginatedResult(PaginationInfo pagination, List<T> page) {
|
||||
this.pagination = pagination;
|
||||
this.results = page;
|
||||
}
|
||||
|
||||
public PaginationInfo getPagination() {
|
||||
return pagination;
|
||||
}
|
||||
|
||||
public List<T> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return results.isEmpty();
|
||||
}
|
||||
|
||||
public int getNumberOfResults() {
|
||||
return getResults().size();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dto;
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -8,7 +8,7 @@ import java.util.stream.IntStream;
|
||||
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import tech.ailef.dbadmin.misc.Utils;
|
||||
import tech.ailef.dbadmin.external.misc.Utils;
|
||||
|
||||
/**
|
||||
* Attached as output to requests that have a paginated response,
|
||||
@@ -35,6 +35,7 @@ public class PaginationInfo {
|
||||
*/
|
||||
private int pageSize;
|
||||
|
||||
// TODO: Check if used
|
||||
private long maxElement;
|
||||
|
||||
private Set<QueryFilter> queryFilters;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.dto;
|
||||
package tech.ailef.dbadmin.external.dto;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.exceptions;
|
||||
package tech.ailef.dbadmin.external.exceptions;
|
||||
|
||||
public class DbAdminException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8120227031645804467L;
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.exceptions;
|
||||
package tech.ailef.dbadmin.external.exceptions;
|
||||
|
||||
/**
|
||||
* Thrown during the computation of pagination if the requested
|
@@ -1,4 +1,4 @@
|
||||
package tech.ailef.dbadmin.misc;
|
||||
package tech.ailef.dbadmin.external.misc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -8,9 +8,9 @@ import java.util.Set;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import tech.ailef.dbadmin.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.exceptions.DbAdminException;
|
||||
import tech.ailef.dbadmin.external.dto.CompareOperator;
|
||||
import tech.ailef.dbadmin.external.dto.QueryFilter;
|
||||
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
|
||||
|
||||
public interface Utils {
|
||||
public static String camelToSnake(String v) {
|
||||
@@ -24,6 +24,8 @@ public interface Utils {
|
||||
|
||||
public static MultiValueMap<String, String> computeParams(Set<QueryFilter> filters) {
|
||||
MultiValueMap<String, String> r = new LinkedMultiValueMap<>();
|
||||
if (filters == null)
|
||||
return r;
|
||||
|
||||
r.put("filter_field", new ArrayList<>());
|
||||
r.put("filter_op", new ArrayList<>());
|
@@ -0,0 +1,12 @@
|
||||
package tech.ailef.dbadmin.internal;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@ConditionalOnProperty(name = "dbadmin.enabled", matchIfMissing = true)
|
||||
@ComponentScan
|
||||
@Configuration
|
||||
public class InternalDbAdminConfiguration {
|
||||
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package tech.ailef.dbadmin.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import tech.ailef.dbadmin.internal.model.UserSetting;
|
||||
import tech.ailef.dbadmin.internal.repository.UserSettingsRepository;
|
||||
|
||||
@Component
|
||||
public class UserConfiguration {
|
||||
@Autowired
|
||||
private UserSettingsRepository repo;
|
||||
|
||||
public String get(String settingName) {
|
||||
Optional<UserSetting> setting = repo.findById(settingName);
|
||||
if (setting.isPresent())
|
||||
return setting.get().getSettingValue();
|
||||
return defaultValues().get(settingName);
|
||||
}
|
||||
|
||||
private Map<String, String> defaultValues() {
|
||||
Map<String, String> values = new HashMap<>();
|
||||
values.put("brandName", "Spring Boot Database Admin");
|
||||
return values;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
package tech.ailef.dbadmin.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterFactory;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Lob;
|
||||
|
||||
@Entity
|
||||
public class UserAction {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Lob
|
||||
@Column(nullable = false)
|
||||
private String sql;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String onTable;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String primaryKey;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String actionType;
|
||||
|
||||
public UserAction() {
|
||||
}
|
||||
|
||||
public UserAction(String onTable, String primaryKey, String actionType) {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.sql = "SQL TODO";
|
||||
this.onTable = onTable;
|
||||
this.actionType = actionType;
|
||||
this.primaryKey = primaryKey;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public void setSql(String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
public String getOnTable() {
|
||||
return onTable;
|
||||
}
|
||||
|
||||
public void setOnTable(String onTable) {
|
||||
this.onTable = onTable;
|
||||
}
|
||||
|
||||
public String getPrimaryKey() {
|
||||
return primaryKey;
|
||||
}
|
||||
|
||||
public void setPrimaryKey(String primaryKey) {
|
||||
this.primaryKey = primaryKey;
|
||||
}
|
||||
|
||||
public String getActionType() {
|
||||
return actionType;
|
||||
}
|
||||
|
||||
public void setActionType(String actionType) {
|
||||
this.actionType = actionType;
|
||||
}
|
||||
|
||||
public String getFormattedDate() {
|
||||
return new DateTimeFormatterFactory("YYYY-MM-dd HH:mm:ss").createDateTimeFormatter().format(createdAt);
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package tech.ailef.dbadmin.internal.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class UserSetting {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
private String settingValue;
|
||||
|
||||
public UserSetting() {
|
||||
}
|
||||
|
||||
public UserSetting(String id, String settingValue) {
|
||||
this.id = id;
|
||||
this.settingValue = settingValue;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSettingValue() {
|
||||
return settingValue;
|
||||
}
|
||||
|
||||
public void setSettingValue(String settingValue) {
|
||||
this.settingValue = settingValue;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package tech.ailef.dbadmin.internal.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import tech.ailef.dbadmin.internal.model.UserAction;
|
||||
|
||||
public interface CustomActionRepository {
|
||||
public List<UserAction> findActions(String table, String actionType, String itemId, PageRequest pageRequest);
|
||||
|
||||
public long countActions(String table, String actionType, String itemId);
|
||||
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package tech.ailef.dbadmin.internal.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import tech.ailef.dbadmin.internal.model.UserAction;
|
||||
|
||||
@Component
|
||||
public class CustomActionRepositoryImpl implements CustomActionRepository {
|
||||
|
||||
@PersistenceContext(unitName = "internal")
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public List<UserAction> findActions(String table, String actionType, String itemId, PageRequest page) {
|
||||
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<UserAction> query = cb.createQuery(UserAction.class);
|
||||
Root<UserAction> userAction = query.from(UserAction.class);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<Predicate>();
|
||||
if (table != null)
|
||||
predicates.add(cb.equal(userAction.get("onTable"), table));
|
||||
if (actionType != null)
|
||||
predicates.add(cb.equal(userAction.get("actionType"), actionType));
|
||||
if (itemId != null)
|
||||
predicates.add(cb.equal(userAction.get("primaryKey"), itemId));
|
||||
|
||||
if (!predicates.isEmpty()) {
|
||||
query.select(userAction)
|
||||
.where(cb.and(
|
||||
predicates.toArray(new Predicate[predicates.size()])));
|
||||
}
|
||||
|
||||
return entityManager.createQuery(query)
|
||||
.setMaxResults(page.getPageSize())
|
||||
.setFirstResult((int)page.getOffset())
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countActions(String table, String actionType, String itemId) {
|
||||
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> query = cb.createQuery(Long.class);
|
||||
Root<UserAction> userAction = query.from(UserAction.class);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
if (table != null)
|
||||
predicates.add(cb.equal(userAction.get("onTable"), table));
|
||||
if (actionType != null)
|
||||
predicates.add(cb.equal(userAction.get("actionType"), actionType));
|
||||
if (itemId != null)
|
||||
predicates.add(cb.equal(userAction.get("primaryKey"), itemId));
|
||||
|
||||
if (!predicates.isEmpty()) {
|
||||
query.select(cb.count(userAction))
|
||||
.where(cb.and(
|
||||
predicates.toArray(new Predicate[predicates.size()])));
|
||||
} else {
|
||||
query.select(cb.count(userAction));
|
||||
}
|
||||
|
||||
return entityManager.createQuery(query).getSingleResult();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package tech.ailef.dbadmin.internal.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import tech.ailef.dbadmin.internal.model.UserAction;
|
||||
|
||||
@Repository
|
||||
public interface UserActionRepository extends JpaRepository<UserAction, Integer>, CustomActionRepository {
|
||||
public List<UserAction> findAllByOnTableAndActionTypeAndPrimaryKey(String table, String actionType, String primaryKey, PageRequest pageRequest);
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package tech.ailef.dbadmin.internal.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import tech.ailef.dbadmin.internal.model.UserSetting;
|
||||
|
||||
@Repository
|
||||
public interface UserSettingsRepository extends JpaRepository<UserSetting, String> {
|
||||
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package tech.ailef.dbadmin.internal.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import tech.ailef.dbadmin.external.dto.PaginatedResult;
|
||||
import tech.ailef.dbadmin.external.dto.PaginationInfo;
|
||||
import tech.ailef.dbadmin.internal.model.UserAction;
|
||||
import tech.ailef.dbadmin.internal.repository.CustomActionRepositoryImpl;
|
||||
import tech.ailef.dbadmin.internal.repository.UserActionRepository;
|
||||
|
||||
@Service
|
||||
public class UserActionService {
|
||||
@Autowired
|
||||
private UserActionRepository repo;
|
||||
|
||||
@Autowired
|
||||
private CustomActionRepositoryImpl customRepo;
|
||||
|
||||
@Transactional("internalTransactionManager")
|
||||
public UserAction save(UserAction a) {
|
||||
return repo.save(a);
|
||||
}
|
||||
|
||||
public PaginatedResult<UserAction> findActions(String table, String actionType, String userId, PageRequest page) {
|
||||
long count = customRepo.countActions(table, actionType, userId);
|
||||
List<UserAction> actions = customRepo.findActions(table, actionType, userId, page);
|
||||
int maxPage = (int)(Math.ceil ((double)count / page.getPageSize()));
|
||||
|
||||
return new PaginatedResult<>(
|
||||
new PaginationInfo(page.getPageNumber() + 1, maxPage, page.getPageSize(), count, null, null),
|
||||
actions
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
#spring.jpa.hibernate.ddl-auto=create
|
||||
|
||||
#spring.datasource.url=jdbc:h2:file:./database
|
||||
#spring.datasource.username=sa
|
||||
#spring.datasource.password=password
|
||||
#spring.datasource.dbadmin.url=jdbc:h2:file:./database
|
||||
#spring.datasource.dbadmin.username=sa
|
||||
#spring.datasource.dbadmin.password=password
|
||||
#spring.h2.console.enabled=true
|
||||
|
||||
#spring.jpa.show-sql=true
|
||||
|
12
src/main/resources/static/js/logs.js
Normal file
12
src/main/resources/static/js/logs.js
Normal file
@@ -0,0 +1,12 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let form = document.getElementById('log-filter-form');
|
||||
|
||||
if (form == null) return;
|
||||
|
||||
let selects = form.querySelectorAll('select');
|
||||
selects.forEach(select => {
|
||||
select.addEventListener('change', function(e) {
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
});
|
@@ -10,6 +10,7 @@
|
||||
<script type="text/javascript" src="/js/autocomplete.js"></script>
|
||||
<script type="text/javascript" src="/js/autocomplete-multi.js"></script>
|
||||
<script type="text/javascript" src="/js/filters.js"></script>
|
||||
<script type="text/javascript" src="/js/logs.js"></script>
|
||||
<script type="text/javascript" src="/js/create.js"></script>
|
||||
<title th:text="${title != null ? title + ' | Spring Boot DB Admin Panel' : 'Spring Boot DB Admin Panel'}"></title>
|
||||
<script th:inline="javascript">
|
||||
@@ -30,7 +31,9 @@
|
||||
|
||||
<nav class="navbar fixed-top navbar-expand-lg bg-accent color-white" th:fragment="navbar">
|
||||
<div class="container-fluid">
|
||||
<a class=" fw-bold navbar-brand" href="/"><i class="bi bi-hexagon-fill"></i> Spring Boot DB Admin Panel</a>
|
||||
<a class=" fw-bold navbar-brand" href="/"><i class="bi bi-hexagon-fill"></i>
|
||||
[[ ${userConf.get('brandName')} ]]
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@@ -64,6 +67,30 @@
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'logs') ? 'active' : ''}">
|
||||
<a th:href="|/${baseUrl}/logs|">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-file-text"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Logs
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'settings') ? 'active' : ''}">
|
||||
<a th:href="|/${baseUrl}/settings|">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-gear"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Settings
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<!--
|
||||
<li th:class="${#strings.equals(activePage, 'console') ? 'active' : ''}">
|
||||
<a href="/live">
|
||||
@@ -89,18 +116,6 @@
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'settings') ? 'active' : ''}">
|
||||
<a th:href="|/${baseUrl}/settings|">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-gear"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Settings
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
</div>
|
||||
@@ -123,14 +138,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<nav aria-label="Results pagination" th:fragment="pagination(page)">
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
<div th:if="${page != null && page.getPagination().getMaxPage() != 1}" class="d-flex">
|
||||
<ul class="pagination me-3">
|
||||
<li class="page-item" th:if="${page.getPagination().getCurrentPage() != 1}">
|
||||
<a class="page-link"
|
||||
th:href="@{|/${baseUrl}/model/${schema.getClassName()}${page.getPagination().getLink(page.getPagination.getCurrentPage() - 1)}|}"
|
||||
th:href="@{|${requestUrl}${page.getPagination().getLink(page.getPagination.getCurrentPage() - 1)}|}"
|
||||
aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
@@ -139,7 +155,7 @@
|
||||
|
||||
<li class="page-item" th:each="p : ${page.getPagination().getBeforePages()}">
|
||||
<a class="page-link"
|
||||
th:href="@{|/${baseUrl}/model/${schema.getClassName()}${page.getPagination().getLink(p)}|}" th:text="${p}"></a>
|
||||
th:href="@{|${requestUrl}${page.getPagination().getLink(p)}|}" th:text="${p}"></a>
|
||||
</li>
|
||||
|
||||
<li class="page-item active">
|
||||
@@ -148,13 +164,13 @@
|
||||
|
||||
<li class="page-item" th:each="p : ${page.getPagination().getAfterPages()}">
|
||||
<a class="page-link"
|
||||
th:href="@{|/${baseUrl}/model/${schema.getClassName()}${page.getPagination().getLink(p)}|}"
|
||||
th:href="@{|${requestUrl}${page.getPagination().getLink(p)}|}"
|
||||
th:text="${p}"></a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
th:if="${!page.getPagination().isLastPage()}"
|
||||
th:href="@{|/${baseUrl}/model/${schema.getClassName()}${page.getPagination().getLink(page.getPagination.getCurrentPage() + 1)}|}"
|
||||
th:href="@{|${requestUrl}${page.getPagination().getLink(page.getPagination.getCurrentPage() + 1)}|}"
|
||||
aria-label="Next">
|
||||
<span class="sr-only">Next</span>
|
||||
<span aria-hidden="true">»</span>
|
||||
@@ -162,15 +178,15 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="me-3">
|
||||
<form method="GET" th:action="@{|/${baseUrl}/model/${schema.getClassName()}|}">
|
||||
<form method="GET" th:action="@{|${requestUrl}|}">
|
||||
<input type="hidden" th:value="${page.getPagination().getCurrentPage()}" th:name="page">
|
||||
<input type="hidden" th:value="${query}" th:name="query">
|
||||
<input type="hidden" name="pageSize">
|
||||
<th:block th:each="p : ${queryParams.keySet()}">
|
||||
<input th:each="v : ${queryParams.get(p)}"
|
||||
th:name="${p}" th:value="${v}" type="hidden"
|
||||
th:if="${p.startsWith('filter_')}">
|
||||
</th:block>
|
||||
<input th:each="v : ${queryParams.get(p)}"
|
||||
th:name="${p}" th:value="${v}" type="hidden"
|
||||
th:if="${p.startsWith('filter_')}">
|
||||
</th:block>
|
||||
<select class="form-select page-size">
|
||||
<option disabled>Page size</option>
|
||||
<option th:selected="${page.getPagination().getPageSize() == 50}">50</option>
|
||||
@@ -183,14 +199,14 @@
|
||||
|
||||
<div class="d-flex align-items-center" th:if="${page.getPagination().getMaxPage() > 1}">
|
||||
<p class="m-0 p-0">
|
||||
<i>Showing [[ ${page.getActualResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
<i>Showing [[ ${page.getNumberOfResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center" th:if="${page.getPagination().getMaxPage() == 1}">
|
||||
<div class="me-3">
|
||||
<form method="GET" th:action="@{|/${baseUrl}/model/${schema.getClassName()}|}">
|
||||
<form method="GET" th:action="@{|${requestUrl}|}">
|
||||
<input type="hidden" th:value="${page.getPagination().getCurrentPage()}" th:name="page">
|
||||
<input type="hidden" th:value="${query}" th:name="query">
|
||||
<input type="hidden" name="pageSize">
|
||||
@@ -204,13 +220,14 @@
|
||||
</form>
|
||||
</div>
|
||||
<p class="m-0 p-0">
|
||||
<i>Showing [[ ${page.getActualResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
<i>Showing [[ ${page.getNumberOfResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="bulk-actions">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</html>
|
||||
|
101
src/main/resources/templates/logs.html
Normal file
101
src/main/resources/templates/logs.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head th:replace="~{fragments/resources::head}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-light main-wrapper">
|
||||
<nav th:replace="~{fragments/resources :: navbar}"></nav>
|
||||
<div class="d-flex">
|
||||
<div th:replace="~{fragments/resources :: sidebar('logs')}"></div>
|
||||
<div class="main-content bg-lighter">
|
||||
<th:block th:replace="~{fragments/resources :: alerts}"></th:block>
|
||||
<h1 class="fw-bold mb-4"><i class="align-middle bi bi-gear"></i>
|
||||
<span class="align-middle">Logs</span>
|
||||
</h1>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<div class="box">
|
||||
<h3 class="fw-bold">Logs</h3>
|
||||
<div class="w-75">
|
||||
<form th:action="|/${baseUrl}/logs|" class="mt-3" id="log-filter-form" method="GET">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Action type</span>
|
||||
<select name="actionType" class="form-select">
|
||||
<option>Any</option>
|
||||
<option value="CREATE"
|
||||
th:selected="${searchRequest.getActionType() != null
|
||||
&& searchRequest.getActionType().equalsIgnoreCase('CREATE') }">Create</option>
|
||||
<option value="EDIT"
|
||||
th:selected="${searchRequest.getActionType() != null
|
||||
&& searchRequest.getActionType().equalsIgnoreCase('EDIT') }">Edit</option>
|
||||
<option value="DELETE"
|
||||
th:selected="${searchRequest.getActionType() != null
|
||||
&& searchRequest.getActionType().equalsIgnoreCase('DELETE') }">Delete</option>
|
||||
</select>
|
||||
<span class="input-group-text ms-3">Table</span>
|
||||
<select name="table" class="form-select">
|
||||
<option value="Any">Any</option>
|
||||
<option th:each="schema : ${schemas}"
|
||||
th:value="${schema.getTableName()}"
|
||||
th:text="${schema.getTableName()}"
|
||||
th:selected="${schema.getTableName().equals(searchRequest.getTable())}">
|
||||
</option>
|
||||
</select>
|
||||
<span class="input-group-text ms-3">Item ID</span>
|
||||
<input type="text" class="form-control" name="itemId"
|
||||
th:value="${searchRequest.getItemId()}">
|
||||
<button class="ui-btn btn btn-primary ms-3">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator mt-3 mb-3"></div>
|
||||
|
||||
|
||||
<div class="mt-3" th:if="${page.isEmpty()}">
|
||||
<div class="alert alert-warning">There are no results for your filtering criteria</div>
|
||||
</div>
|
||||
<div class="table-responsive mt-3" th:if="${!page.isEmpty()}">
|
||||
<nav th:replace="~{fragments/resources :: pagination(page=${page})}"></nav>
|
||||
|
||||
<table class="table table-striped mt-3">
|
||||
<tr class="table-data-row">
|
||||
<th>Action type</th>
|
||||
<th>Table</th>
|
||||
<th>Item ID</th>
|
||||
<th>Time</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr th:each="entry : ${page.getResults()}" class="table-data-row align-middle">
|
||||
<td th:text="${entry.getActionType()}">
|
||||
</td>
|
||||
<td th:text="${entry.getOnTable()}">
|
||||
</td>
|
||||
<td th:text="${entry.getPrimaryKey()}">
|
||||
</td>
|
||||
<td th:text="${entry.getFormattedDate()}">
|
||||
</td>
|
||||
<td>
|
||||
<!--
|
||||
<a href="#" class="ui-btn btn btn-primary">
|
||||
Diff <i class="text-white bi bi-search"></i>
|
||||
</a>
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<nav th:replace="~{fragments/resources :: pagination(page=${page})}"></nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@@ -17,19 +17,27 @@
|
||||
<div class="w-100 d-flex inner-navigation">
|
||||
<a href="#" class="active">
|
||||
<div class="ui-tab ps-5 pe-5 p-3">
|
||||
<i class="bi bi-database pe-2"></i> APPEARANCE
|
||||
<i class="bi bi-database pe-2"></i> GENERAL
|
||||
</div>
|
||||
</a>
|
||||
<a href="#">
|
||||
<!-- <a href="#">
|
||||
<div class="ui-tab ps-5 pe-5 p-3">
|
||||
<i class="bi bi-table pe-2"></i> DATA
|
||||
</div>
|
||||
</a>
|
||||
</a> -->
|
||||
<div class="inner-navigation-border flex-grow-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="box with-navigation">
|
||||
SETTINGS
|
||||
<h3 class="fw-bold">General</h3>
|
||||
<form th:action="|/${baseUrl}/settings|" method="POST">
|
||||
<label for="brandName">Brand name</label>
|
||||
<span class="m-0 p-0 text-muted">What appears in the top bar</span>
|
||||
<input class="form-control mt-2" type="text"
|
||||
id="brandName" name="brandName"
|
||||
th:value="${userConf.get('brandName')}">
|
||||
<button class="ui-btn btn btn-primary mt-3">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user