mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-06-09 05:48:20 +00:00
0.1.0
This commit is contained in:
parent
95e8dea90f
commit
cdbad51641
@ -10,6 +10,7 @@ Features:
|
||||
* List objects with pagination and sorting
|
||||
* Show detailed object page which also includes `@OneToMany`, `@ManyToMany`, etc... fields
|
||||
* Create/Edit objects
|
||||
* Action logs (i.e. see a history of all write operations done through the web UI)
|
||||
* Search
|
||||
* Customization
|
||||
|
||||
|
205
docs/docs.html
Normal file
205
docs/docs.html
Normal file
@ -0,0 +1,205 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css" integrity="sha384-b6lVK+yci+bfDmaY1u0zE8YYJt0TZxLEAFyYSLHId4xoVvsrQu3INevFKo+Xir8e" crossorigin="anonymous">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js" integrity="sha512-1/RvZTcCDEUjY/CypiMz+iqqtaoQfAITmNSJY17Myp4Ms5mdxPS5UV7iOfdZoxcGhzFbOm6sntTKJppjvuhg4g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<title>Documentation | Spring Boot DB Admin Panel</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/java.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/xml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/properties.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('h2, h3, h4, h5, h6').forEach(heading => {
|
||||
let tag = heading.tagName.replace('H', '');
|
||||
|
||||
document.getElementById('toc').innerHTML +=
|
||||
`<li class="ms-${tag}"><a href="#${heading.id}">${heading.innerHTML}</li>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-light">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg bg-light">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold" href="#">Spring Boot Database Admin Docs</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>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/aileftech/spring-boot-database-admin" target="_blank">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!--
|
||||
<form class="d-flex" role="search">
|
||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||
</form>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End nav -->
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-3 pt-3">
|
||||
<ol id="toc" class="toc" style="position: fixed">
|
||||
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-9 main-content pt-3 ps-4">
|
||||
|
||||
<h1>Spring Boot Database Admin documentation</h1>
|
||||
|
||||
<h2 id="introduction">1. Introduction</h2>
|
||||
|
||||
<p>The following documentation outlines how to install, configure and customize Spring Boot Database Admin panel. Refer to this document for troubleshooting and if you still encounter problems, please report it as an issue on Github.</p>
|
||||
<div class="separator"></div>
|
||||
<h2 id="getting-started">2. Getting started</h2>
|
||||
<p>Getting started with Spring Boot Database Admin requires including it as a dependency and then performing a simple configuration step.</p>
|
||||
|
||||
<h3 id="installation">2.1 Installation</h3>
|
||||
<p>At the moment the code is not yet distributed on Maven, so it is necessary to build and install manually. Clone the Github repo and <code>mvn install</code> the project into your local repository. Then, include the dependency in your <code>pom.xml</code>:</p>
|
||||
|
||||
<pre>
|
||||
<code class="language-xml"><dependency>
|
||||
<groupId>tech.ailef</groupId>
|
||||
<artifactId>spring-boot-db-admin</artifactId>
|
||||
<version>0.0.4</version>
|
||||
</dependency>
|
||||
</code>
|
||||
</pre>
|
||||
<p class="tip"><span class="title"><i class="bi bi-info-circle"></i> TIP</span> The version in this snippet might be different from the one you pulled from Github. Make sure to edit it to match the version contained in the project's <code>pom.xml</code> file.</p>
|
||||
|
||||
<h3 id="configuration">2.2 Configuration</h3>
|
||||
<p>After including the dependency, a few configuration steps are required on your end in order to integrate the library into your project. </p>
|
||||
<p>The first one is configuring your <code>application.properties</code> file:</p>
|
||||
|
||||
<pre>
|
||||
<code class="language-properties"># The first-level part of the URL path: http://localhost:8080/${baseUrl}/
|
||||
dbadmin.baseUrl=admin
|
||||
|
||||
# The package that contains your @Entity classes
|
||||
dbadmin.modelsPackage=your.models.package
|
||||
|
||||
# OPTIONAL PARAMETERS
|
||||
# Whether to enable to web interface
|
||||
# dbadmin.enabled=true
|
||||
#
|
||||
# Set to true if you need to run the tests, as it will customize
|
||||
# the database configuration for the internal DataSource
|
||||
# dbadmin.testMode=false
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>After this, you must tell Spring to import the Spring Boot Database Admin configuration. To do this, annotate your <code>@SpringBootApplication</code> class containing the <code>main</code> method with the following:</p>
|
||||
|
||||
<pre>
|
||||
<code class="language-java">@ImportAutoConfiguration(DbAdminAutoConfiguration.class)
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>This will autoconfigure the various Spring Boot Database Admin components when your application starts.</p>
|
||||
<p>If everything is setup correctly, you will see Spring Boot Database Admin confirming it in the log messages that appear when you start your application. Keep in mind that if you specify the wrong models package, it will still work but provide you an empty interface. To check, visit <a target="_blank" href="http://localhost:8080/admin">http://localhost:8080/admin</a>.</p>
|
||||
<div class="separator"></div>
|
||||
<h2 id="customization">3. Customization</h2>
|
||||
<p>There are two ways to customize the appearance and behaviour of Spring Boot Database Admin:</p>
|
||||
<ol>
|
||||
<li>Applying annotations on your <code>@Entity</code> classes, fields and methods</li>
|
||||
<li>Using the Settings panel through the web interface</li>
|
||||
</ol>
|
||||
<p>Annotations are used primarily to customize behaviour and add custom logic to your classes. If, instead, you're looking to customize appearance of the web UI, it's most likley through the Settings panel.</p>
|
||||
|
||||
<h3 id="supported-annotations">3.1 Supported annotations</h3>
|
||||
|
||||
<h4 id="display-name">3.1.1 @DisplayName</h4>
|
||||
<pre>
|
||||
<code class="language-java">@DisplayName
|
||||
public String getFullName() {
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>When displaying a reference to an item, by default we show its primary key. If a class has a <code>@DisplayName</code>, this method will be used in addition to the primary key whenever possible, giving the user a more readable option. <p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 id="display-format">3.1.2 @DisplayFormat</h4>
|
||||
<pre>
|
||||
<code class="language-java">@DisplayFormat(format = "$%.2f")
|
||||
private Double price;
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>Specify a format string to apply when displaying the field.</p>
|
||||
|
||||
|
||||
<h4 id="computed-column">3.1.3 @ComputedColumn</h4>
|
||||
<pre>
|
||||
<code class="language-java">@ComputedColumn
|
||||
public double totalSpent() {
|
||||
double total = 0;
|
||||
for (Order o : orders) {
|
||||
total += o.total();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>This annotation can be used to add values computed at runtime that are shown like additional columns.</p>
|
||||
|
||||
<h4 id="filterable">3.1.4 @Filterable</h4>
|
||||
|
||||
<pre>
|
||||
<code class="language-java">@Filterable
|
||||
private LocalDate createdAt;
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>Place on one or more fields in a class to activate the faceted search feature. This will allow you to easily combine all these filters when operating on the table. Can only be placed on fields that correspond to physical columns on the table (e.g. no `@ManyToMany`/`@OneToMany`) and that are not binary (`byte[]`).</p>
|
||||
|
||||
<h4 id="display-image">3.1.5 @DisplayImage</h4>
|
||||
|
||||
<pre>
|
||||
<code class="language-java">@DisplayImage
|
||||
private byte[] image;
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>This annotation can be placed on binary fields to declare they are storing an image and that we want it displayed when possible. The image will be shown as a small thumbnail.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="separator"></div>
|
||||
<h2>4. Security</h2>
|
||||
<p>Spring Boot Database Admin does not implement authentication and/or authorization mechanisms. However, you can use a standard Spring security configuration in order to limit access to the web UI or specific parts of it.</p>
|
||||
<p>All Spring Boot Database Admin routes start with the value of <code>dbadmin.baseUrl</code> property, and all write operations (edit, create, delete) are implemented as <code>POST</code> calls.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</body>
|
||||
</html>
|
63
docs/style.css
Normal file
63
docs/style.css
Normal file
@ -0,0 +1,63 @@
|
||||
.main-content {
|
||||
border-left: 1px solid #EEE;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
code pre {
|
||||
background-color: #DFDFDF;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: bold;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
}
|
||||
|
||||
ol.toc {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-top: 1px solid #DDD;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
}
|
||||
|
||||
p.tip {
|
||||
border-radius: 5px;
|
||||
background-color: #D0DCF0;
|
||||
padding: 1rem;
|
||||
padding-top: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
p.tip span.title {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 20;
|
||||
background-color: lightsalmon;
|
||||
color: white;
|
||||
}
|
2
pom.xml
2
pom.xml
@ -11,7 +11,7 @@
|
||||
</parent>
|
||||
<groupId>tech.ailef</groupId>
|
||||
<artifactId>spring-boot-db-admin</artifactId>
|
||||
<version>0.0.4</version>
|
||||
<version>0.1.0</version>
|
||||
<name>spring-boot-db-admin</name>
|
||||
<description>Srping Boot DB Admin Dashboard</description>
|
||||
<properties>
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user