mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-08-12 14:51:13 +00:00
Add debugBackend module with database and security setup
Introduced a new `debugBackend` module to support debugging and development tasks. This includes configuration for PostgreSQL, Redis, Hibernate, Spring Boot, and a basic security setup. Also added required entities, repositories, and runtime hints for SnapAdmin integration.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package space.mori.dalbodeule.snapadmin.aot;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import space.mori.dalbodeule.snapadmin.external.SnapAdmin;
|
||||
import space.mori.dalbodeule.snapadmin.external.SnapAdminProperties;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.Disable;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.DisableEditField;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.DisplayFormat;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.DisplayImage; // Assuming this is used
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.HiddenEditForm; // Assuming this is used
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.CustomJpaRepository;
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.DbObjectSchema;
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.fields.*;
|
||||
import space.mori.dalbodeule.snapadmin.external.dto.MappingError;
|
||||
import space.mori.dalbodeule.snapadmin.external.misc.Utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.aot.hint.MemberCategory.*;
|
||||
|
||||
public class SnapAdminRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private static final Set<Class<?>> dbFieldTypes = new HashSet<>(Arrays.asList(
|
||||
BooleanFieldType.class, LongFieldType.class, IntegerFieldType.class,
|
||||
BigIntegerFieldType.class, ShortFieldType.class, StringFieldType.class,
|
||||
LocalDateFieldType.class, DateFieldType.class, LocalDateTimeFieldType.class,
|
||||
InstantFieldType.class, FloatFieldType.class, DoubleFieldType.class,
|
||||
BigDecimalFieldType.class, ByteArrayFieldType.class, OffsetDateTimeFieldType.class,
|
||||
ByteFieldType.class, UUIDFieldType.class, CharFieldType.class,
|
||||
EnumFieldType.class, TextFieldType.class
|
||||
// Add any other concrete DbFieldType implementations here
|
||||
));
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
// Register SnapAdmin's own classes
|
||||
hints.reflection().registerType(SnapAdmin.class, INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS);
|
||||
hints.reflection().registerType(SnapAdminProperties.class, INVOKE_DECLARED_CONSTRUCTORS, INVOKE_PUBLIC_METHODS); // For Spring binding
|
||||
|
||||
hints.reflection().registerType(DbObjectSchema.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS);
|
||||
// CustomJpaRepository 인터페이스 자체는 생성자 호출 힌트가 불필요할 수 있음
|
||||
hints.reflection().registerType(DbField.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(MappingError.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
// hints.reflection().registerType(Utils.class); // 사용 패턴 확인 후 필요하면 활성화
|
||||
|
||||
// Register DbFieldType and its subclasses for default constructor invocation
|
||||
hints.reflection().registerType(DbFieldType.class);
|
||||
for (Class<?> dbFieldTypeClass : dbFieldTypes) {
|
||||
hints.reflection().registerType(dbFieldTypeClass, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
}
|
||||
// EnumFieldType has a special constructor too
|
||||
hints.reflection().registerType(EnumFieldType.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
|
||||
|
||||
// Register SnapAdmin's custom annotations (and assume their attributes might be read)
|
||||
registerAnnotation(hints, Disable.class);
|
||||
registerAnnotation(hints, DisableEditField.class);
|
||||
registerAnnotation(hints, DisplayFormat.class);
|
||||
registerAnnotation(hints, DisplayImage.class);
|
||||
registerAnnotation(hints, HiddenEditForm.class);
|
||||
|
||||
// Register Jakarta Persistence annotations (and assume their attributes might be read)
|
||||
registerAnnotation(hints, jakarta.persistence.Entity.class);
|
||||
registerAnnotation(hints, jakarta.persistence.Id.class);
|
||||
registerAnnotation(hints, jakarta.persistence.Column.class);
|
||||
registerAnnotation(hints, jakarta.persistence.Lob.class);
|
||||
registerAnnotation(hints, jakarta.persistence.Enumerated.class);
|
||||
registerAnnotation(hints, jakarta.persistence.EnumType.class); // TYPE_VISIBLE 제거
|
||||
registerAnnotation(hints, jakarta.persistence.OneToMany.class);
|
||||
registerAnnotation(hints, jakarta.persistence.ManyToMany.class);
|
||||
registerAnnotation(hints, jakarta.persistence.ManyToOne.class);
|
||||
registerAnnotation(hints, jakarta.persistence.OneToOne.class);
|
||||
registerAnnotation(hints, jakarta.persistence.JoinColumn.class);
|
||||
// Add other JPA annotations if used, e.g. @Table, @Transient
|
||||
|
||||
// Hints for operations on arbitrary (user-defined) @Entity classes
|
||||
// 가능하면 스캔 범위를 제한하거나, 필요한 메서드만 등록
|
||||
// 예시: 특정 패키지 내의 @Entity 클래스 스캔
|
||||
// ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
// scanner.addIncludeFilter(new AnnotationTypeFilter(jakarta.persistence.Entity.class));
|
||||
// for (BeanDefinition bd : scanner.findCandidateComponents("com.example.entities")) {
|
||||
// try {
|
||||
// Class<?> entityClass = Class.forName(bd.getBeanClassName());
|
||||
// hints.reflection().registerType(entityClass, INTROSPECT_DECLARED_FIELDS, INVOKE_DECLARED_METHODS);
|
||||
// } catch (ClassNotFoundException e) {
|
||||
// // Handle exception
|
||||
// }
|
||||
// }
|
||||
// Register SnapAdmin's own classes
|
||||
hints.reflection().registerType(SnapAdmin.class, INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS);
|
||||
hints.reflection().registerType(SnapAdminProperties.class, INVOKE_DECLARED_CONSTRUCTORS, INVOKE_PUBLIC_METHODS); // For Spring binding
|
||||
|
||||
hints.reflection().registerType(DbObjectSchema.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(CustomJpaRepository.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
hints.reflection().registerType(DbField.class, INVOKE_DECLARED_CONSTRUCTORS, INTROSPECT_DECLARED_METHODS, INVOKE_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(MappingError.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
hints.reflection().registerType(Utils.class); // If it contains static methods called, or if instantiated
|
||||
|
||||
|
||||
// For Class.forName(className) on unknown classes (typically user entities)
|
||||
// and subsequent operations like getDeclaredFields(), getAnnotation(), newInstance()
|
||||
// This is a general hint. Users should still ensure their entities are hinted.
|
||||
// Consider making this more specific if possible, e.g., by scanning packages if configured.
|
||||
hints.reflection().registerType(Object.class,
|
||||
INTROSPECT_DECLARED_CONSTRUCTORS, INVOKE_DECLARED_CONSTRUCTORS,
|
||||
INTROSPECT_DECLARED_METHODS, INVOKE_DECLARED_METHODS, // For getters/setters if library invokes them
|
||||
DECLARED_FIELDS // For field access
|
||||
);
|
||||
|
||||
// For ClassPathScanningCandidateComponentProvider
|
||||
hints.reflection().registerType(org.springframework.beans.factory.config.BeanDefinition.class, INVOKE_PUBLIC_METHODS); // For getBeanClassName()
|
||||
hints.reflection().registerType(org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
hints.reflection().registerType(org.springframework.core.type.filter.AnnotationTypeFilter.class, INVOKE_DECLARED_CONSTRUCTORS);
|
||||
|
||||
|
||||
// Resource hints if any .properties or .xml files are loaded from classpath by the library
|
||||
// hints.resources().registerPattern("my-library-config.xml");
|
||||
|
||||
// Proxy hints if JDK proxies are created for library interfaces
|
||||
// hints.proxies().registerJdkProxy(MyLibraryInterface.class);
|
||||
|
||||
// Serialization hints if objects are serialized by the library
|
||||
// hints.serialization().registerType(MySerializableObject.class);
|
||||
}
|
||||
|
||||
private void registerAnnotation(RuntimeHints hints, Class<?> annotationType) {
|
||||
hints.reflection().registerType(annotationType, INVOKE_DECLARED_METHODS);
|
||||
}
|
||||
private void registerAnnotation(RuntimeHints hints, Class<?> annotationType, org.springframework.aot.hint.MemberCategory... categories) {
|
||||
hints.reflection().registerType(annotationType, categories);
|
||||
}
|
||||
}
|
@@ -424,4 +424,4 @@ public class SnapAdmin {
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,118 +1,55 @@
|
||||
/*
|
||||
* SnapAdmin - An automatically generated CRUD admin UI for Spring Boot apps
|
||||
* Copyright (C) 2023 Ailef (http://ailef.tech)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package space.mori.dalbodeule.snapadmin.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.autoconfigure.domain.EntityScan;
|
||||
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 org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import space.mori.dalbodeule.snapadmin.external.controller.DataExportController;
|
||||
import space.mori.dalbodeule.snapadmin.external.controller.FileDownloadController;
|
||||
import space.mori.dalbodeule.snapadmin.external.controller.GlobalController;
|
||||
import space.mori.dalbodeule.snapadmin.external.controller.SnapAdminController;
|
||||
import space.mori.dalbodeule.snapadmin.external.controller.rest.AutocompleteController;
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.CustomJpaRepository;
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.DbObjectSchema;
|
||||
import space.mori.dalbodeule.snapadmin.external.dbmapping.SnapAdminRepository;
|
||||
import space.mori.dalbodeule.snapadmin.internal.InternalSnapAdminConfiguration;
|
||||
import space.mori.dalbodeule.snapadmin.internal.UserConfiguration;
|
||||
import space.mori.dalbodeule.snapadmin.internal.service.ConsoleQueryService;
|
||||
import space.mori.dalbodeule.snapadmin.internal.service.UserActionService;
|
||||
import space.mori.dalbodeule.snapadmin.internal.service.UserSettingsService;
|
||||
|
||||
/**
|
||||
* The configuration class for "internal" data source. This is not the
|
||||
* source connected to the user's data/entities, but rather an internal
|
||||
* H2 database which is used by SnapAdmin to store user
|
||||
* settings and other information like operations history.
|
||||
* SnapAdmin 자동 설정 클래스. 메인 애플리케이션의 JPA 설정을 재사용합니다.
|
||||
*/
|
||||
@ConditionalOnProperty(name = "snapadmin.enabled", matchIfMissing = false)
|
||||
@ComponentScan
|
||||
@EnableConfigurationProperties(SnapAdminProperties.class)
|
||||
@Configuration
|
||||
@EnableJpaRepositories(
|
||||
entityManagerFactoryRef = "internalEntityManagerFactory",
|
||||
basePackages = { "space.mori.dalbodeule.snapadmin.internal.repository" }
|
||||
)
|
||||
@EnableTransactionManagement
|
||||
@Import(InternalSnapAdminConfiguration.class)
|
||||
@ConditionalOnProperty(name = "snapadmin.enabled", havingValue = "true", matchIfMissing = false)
|
||||
@EnableConfigurationProperties(SnapAdminProperties.class)
|
||||
@EnableJpaRepositories(basePackages = "space.mori.dalbodeule.snapadmin.internal.repository")
|
||||
@EntityScan(basePackages = "space.mori.dalbodeule.snapadmin.internal.model")
|
||||
@Import({
|
||||
SnapAdmin.class,
|
||||
SnapAdminMvcConfig.class,
|
||||
StartupAuthCheckRunner.class,
|
||||
ThymeleafUtils.class,
|
||||
|
||||
// controllers
|
||||
SnapAdminController.class,
|
||||
DataExportController.class,
|
||||
FileDownloadController.class,
|
||||
GlobalController.class,
|
||||
AutocompleteController.class,
|
||||
|
||||
// dbmapping
|
||||
SnapAdminRepository.class,
|
||||
|
||||
// internals
|
||||
ConsoleQueryService.class,
|
||||
UserActionService.class,
|
||||
UserSettingsService.class,
|
||||
InternalSnapAdminConfiguration.class,
|
||||
UserConfiguration.class
|
||||
})
|
||||
public class SnapAdminAutoConfiguration {
|
||||
@Autowired
|
||||
private SnapAdminProperties props;
|
||||
|
||||
/**
|
||||
* Builds and returns the internal data source.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
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:./snapadmin_internal");
|
||||
}
|
||||
|
||||
dataSourceBuilder.username("sa");
|
||||
dataSourceBuilder.password("password");
|
||||
return dataSourceBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean internalEntityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(internalDataSource());
|
||||
factoryBean.setPersistenceUnitName("internal");
|
||||
factoryBean.setPackagesToScan("space.mori.dalbodeule.snapadmin.internal.model");
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal transaction manager. It is not defined as a bean
|
||||
* in order to avoid "colliding" with the default transactionManager
|
||||
* registered by the user. Internally, we use this to instantiate a
|
||||
* TransactionTemplate and run all transactions manually instead of
|
||||
* relying on the @link {@link Transactional} annotation.
|
||||
* @return
|
||||
*/
|
||||
PlatformTransactionManager internalTransactionManager() {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setEntityManagerFactory(internalEntityManagerFactory().getObject());
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
TransactionTemplate internalTransactionTemplate() {
|
||||
return new TransactionTemplate(internalTransactionManager());
|
||||
}
|
||||
|
||||
}
|
14
src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java
vendored
Normal file
14
src/main/java/space/mori/dalbodeule/snapadmin/external/annotations/SnapAdminEnabled.java
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package space.mori.dalbodeule.snapadmin.external.annotations;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import space.mori.dalbodeule.snapadmin.external.SnapAdminAutoConfiguration;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(SnapAdminAutoConfiguration.class) // SnapAdmin 설정 클래스를 Import
|
||||
public @interface SnapAdminEnabled {
|
||||
// 필요한 속성이 있다면 정의
|
||||
}
|
@@ -35,6 +35,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
@@ -99,6 +100,10 @@ public class DbObjectSchema {
|
||||
* @param snapAdmin the SnapAdmin instance
|
||||
*/
|
||||
public DbObjectSchema(Class<?> klass, SnapAdmin snapAdmin) {
|
||||
if (klass.getAnnotation(Entity.class) == null) {
|
||||
throw new SnapAdminException("Class " + klass.getName() + " is not an @Entity");
|
||||
}
|
||||
|
||||
this.snapAdmin = snapAdmin;
|
||||
this.entityClass = klass;
|
||||
|
||||
|
@@ -22,19 +22,20 @@ package space.mori.dalbodeule.snapadmin.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Lob;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.Disable;
|
||||
|
||||
@Entity
|
||||
@Disable
|
||||
@Table(name="snapadmin_console_query")
|
||||
public class ConsoleQuery {
|
||||
@Id
|
||||
@UuidGenerator
|
||||
private String id;
|
||||
|
||||
@Lob
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String sql;
|
||||
|
||||
private String title;
|
||||
|
@@ -21,14 +21,10 @@ package space.mori.dalbodeule.snapadmin.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
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;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.Disable;
|
||||
|
||||
/**
|
||||
* An write operation executed by a user from the web UI. This class
|
||||
@@ -36,6 +32,8 @@ import jakarta.persistence.Lob;
|
||||
* concrete yet (e.g. a diff or SQL query) about what change was performed.
|
||||
*/
|
||||
@Entity
|
||||
@Disable
|
||||
@Table(name="snapadmin_user_action")
|
||||
public class UserAction {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -51,8 +49,7 @@ public class UserAction {
|
||||
* The SQL query generated by the operation.
|
||||
* This field is here but it's NOT currently supported
|
||||
*/
|
||||
@Lob
|
||||
@Column(nullable = false)
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String sql;
|
||||
|
||||
/**
|
||||
|
@@ -21,11 +21,15 @@ package space.mori.dalbodeule.snapadmin.internal.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import space.mori.dalbodeule.snapadmin.external.annotations.Disable;
|
||||
|
||||
/**
|
||||
* A single variable in the user settings.
|
||||
*/
|
||||
@Entity
|
||||
@Disable
|
||||
@Table(name="snapadmin_user_setting")
|
||||
public class UserSetting {
|
||||
/**
|
||||
* The id of the variable (its name)
|
||||
|
@@ -39,8 +39,6 @@ import space.mori.dalbodeule.snapadmin.internal.model.UserAction;
|
||||
*/
|
||||
@Component
|
||||
public class CustomActionRepositoryImpl implements CustomActionRepository {
|
||||
|
||||
@PersistenceContext(unitName = "internal")
|
||||
private EntityManager entityManager;
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1 @@
|
||||
space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints=space.mori.dalbodeule.snapadmin.aot.SnapAdminRuntimeHints
|
Reference in New Issue
Block a user