[Core] [Source] [Metadata] Refactor metadata (#746)

This commit is contained in:
qianmoQ 2024-04-18 19:47:15 +08:00 committed by GitHub
commit 398928c521
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1132 additions and 910 deletions

View File

@ -0,0 +1,23 @@
package io.edurt.datacap.common.utils;
import java.util.UUID;
public class CodeUtils
{
private CodeUtils() {}
/**
* Generate code
*
* @param dash if dash is true, the code will be generated with dash
* @return code
*/
public static String generateCode(boolean dash)
{
String code = UUID.randomUUID().toString();
if (dash) {
return code;
}
return code.replace("-", "");
}
}

View File

@ -21,7 +21,7 @@ public class ReflectionUtils
return true;
}
catch (NoSuchFieldException e) {
log.warn("Has field exception", e);
log.debug("Has field exception", e);
return false;
}
}

View File

@ -26,9 +26,9 @@ public class ColumnController
this.service = service;
}
@PostMapping(value = "table/{id}")
public CommonResponse<List<ColumnEntity>> fetchByTable(@PathVariable Long id)
@PostMapping(value = "table/{code}")
public CommonResponse<List<ColumnEntity>> fetchByTable(@PathVariable String code)
{
return this.service.getAllByTable(id);
return this.service.getAllByTable(code);
}
}

View File

@ -32,16 +32,16 @@ public class TableController
this.service = service;
}
@PostMapping(value = "database/{id}")
public CommonResponse<List<TableEntity>> fetchByDatabase(@PathVariable Long id)
@PostMapping(value = "database/{code}")
public CommonResponse<List<TableEntity>> fetchByDatabase(@PathVariable String code)
{
return this.service.getAllByDatabase(id);
return this.service.getAllByDatabase(code);
}
@RequestMapping(value = "{id}", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
public CommonResponse<Object> fetchDataById(@PathVariable Long id, @RequestBody TableFilter configure)
@RequestMapping(value = "{code}", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
public CommonResponse<Object> fetchDataById(@PathVariable String code, @RequestBody TableFilter configure)
{
return this.service.fetchDataById(id, configure);
return this.service.fetchData(code, configure);
}
@PostMapping(value = "export/{id}")
@ -62,9 +62,9 @@ public class TableController
return this.service.createTable(id, configure);
}
@PostMapping(value = "manageColumn/{id}")
public CommonResponse<Object> manageColumn(@PathVariable Long id, @RequestBody TableBody configure)
@PostMapping(value = "manageColumn/{code}")
public CommonResponse<Object> manageColumn(@PathVariable String code, @RequestBody TableBody configure)
{
return this.service.manageColumn(id, configure);
return this.service.manageColumn(code, configure);
}
}

View File

@ -2,6 +2,7 @@ package io.edurt.datacap.service.service;
import io.edurt.datacap.common.enums.ServiceState;
import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.common.utils.CodeUtils;
import io.edurt.datacap.common.utils.NullAwareBeanUtils;
import io.edurt.datacap.common.utils.ReflectionUtils;
import io.edurt.datacap.service.SelfException;
@ -12,6 +13,7 @@ import io.edurt.datacap.service.entity.PageEntity;
import io.edurt.datacap.service.entity.UserEntity;
import io.edurt.datacap.service.repository.BaseRepository;
import io.edurt.datacap.service.security.UserDetailsService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Pageable;
public interface BaseService<T extends BaseEntity>
@ -36,6 +38,9 @@ public interface BaseService<T extends BaseEntity>
if (ReflectionUtils.hasField(configure, "user")) {
ReflectionUtils.setFieldValue(configure, "user", UserDetailsService.getUser());
}
if (StringUtils.isEmpty(configure.getCode())) {
configure.setCode(CodeUtils.generateCode(false));
}
return CommonResponse.success(repository.save(configure));
}

View File

@ -11,8 +11,8 @@ public interface ColumnService
/**
* Retrieves all the column entities associated with a specific table.
*
* @param id the ID of the table
* @param code the code of the table
* @return a common response containing a list of column entities
*/
CommonResponse<List<ColumnEntity>> getAllByTable(Long id);
CommonResponse<List<ColumnEntity>> getAllByTable(String code);
}

View File

@ -14,19 +14,19 @@ public interface TableService
/**
* Retrieves all records from the specified database.
*
* @param id the ID of the database
* @param code the code of the database
* @return a common response containing a list of table entities
*/
CommonResponse<List<TableEntity>> getAllByDatabase(Long id);
CommonResponse<List<TableEntity>> getAllByDatabase(String code);
/**
* Retrieves data from the database based on the provided ID and table filter.
*
* @param id the ID of the data to retrieve
* @param code the code of the data to retrieve
* @param configure the table filter to apply to the data retrieval
* @return a common response object containing the retrieved data
*/
CommonResponse<Object> fetchDataById(Long id, TableFilter configure);
CommonResponse<Object> fetchData(String code, TableFilter configure);
/**
* Generates a function comment for the given function body in a markdown code block with the correct language syntax.
@ -41,5 +41,5 @@ public interface TableService
CommonResponse<Object> createTable(Long databaseId, TableBody configure);
CommonResponse<Object> manageColumn(Long tableId, TableBody configure);
CommonResponse<Object> manageColumn(String code, TableBody configure);
}

View File

@ -2,8 +2,8 @@ package io.edurt.datacap.service.service.impl;
import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.service.entity.ColumnEntity;
import io.edurt.datacap.service.entity.TableEntity;
import io.edurt.datacap.service.repository.metadata.ColumnRepository;
import io.edurt.datacap.service.repository.metadata.TableRepository;
import io.edurt.datacap.service.service.ColumnService;
import org.springframework.stereotype.Service;
@ -13,19 +13,20 @@ import java.util.List;
public class ColumnServiceImpl
implements ColumnService
{
private final TableRepository tableRepository;
private final ColumnRepository repository;
public ColumnServiceImpl(ColumnRepository repository)
public ColumnServiceImpl(TableRepository tableRepository, ColumnRepository repository)
{
this.tableRepository = tableRepository;
this.repository = repository;
}
@Override
public CommonResponse<List<ColumnEntity>> getAllByTable(Long id)
public CommonResponse<List<ColumnEntity>> getAllByTable(String code)
{
TableEntity table = TableEntity.builder()
.id(id)
.build();
return CommonResponse.success(this.repository.findAllByTable(table));
return tableRepository.findByCode(code)
.map(value -> CommonResponse.success(repository.findAllByTable(value)))
.orElseGet(() -> CommonResponse.failure(String.format("Table [ %s ] not found", code)));
}
}

View File

@ -12,6 +12,7 @@ import io.edurt.datacap.common.enums.NodeType;
import io.edurt.datacap.common.enums.ServiceState;
import io.edurt.datacap.common.enums.Type;
import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.common.utils.CodeUtils;
import io.edurt.datacap.common.utils.JsonUtils;
import io.edurt.datacap.executor.common.RunState;
import io.edurt.datacap.service.adapter.PageRequestAdapter;
@ -378,10 +379,13 @@ public class SourceServiceImpl
@Override
public CommonResponse<SourceEntity> syncMetadata(Long id)
{
return this.sourceRepository.findById(id).map(entity -> {
Executors.newSingleThreadExecutor().submit(() -> startSyncMetadata(entity, null));
return CommonResponse.success(entity);
}).orElseGet(() -> CommonResponse.failure(String.format("Source [ %s ] not found", id)));
return this.sourceRepository.findById(id)
.map(entity -> {
Executors.newSingleThreadExecutor()
.submit(() -> startSyncMetadata(entity, null));
return CommonResponse.success(entity);
})
.orElseGet(() -> CommonResponse.failure(String.format("Source [ %s ] not found", id)));
}
private void startSyncMetadata(SourceEntity entity, ScheduledEntity scheduled)
@ -544,19 +548,25 @@ public class SourceServiceImpl
}
else {
List<DatabaseEntity> origin = databaseHandler.findAllBySource(entity);
List<DatabaseEntity> entities = response.getColumns().stream().map(item -> {
DatabaseEntity database = DatabaseEntity.builder().name(getNodeText(item, NodeType.SCHEMA)).catalog(getNodeText(item, NodeType.CATALOG)).description(String.format("[ %s ] of [ %s ]", getNodeText(item, NodeType.SCHEMA), getNodeText(item, NodeType.CATALOG))).source(entity).build();
Optional<DatabaseEntity> optionalDatabase = origin.stream().filter(node -> node.getName().equals(database.getName())).findAny();
if (optionalDatabase.isPresent()) {
database.setId(optionalDatabase.get().getId());
database.setCreateTime(optionalDatabase.get().getCreateTime());
databaseUpdatedCount.addAndGet(1);
}
else {
databaseAddedCount.addAndGet(1);
}
return database;
}).collect(Collectors.toList());
List<DatabaseEntity> entities = response.getColumns()
.stream()
.map(item -> {
DatabaseEntity database = DatabaseEntity.builder().name(getNodeText(item, NodeType.SCHEMA)).catalog(getNodeText(item, NodeType.CATALOG)).description(String.format("[ %s ] of [ %s ]", getNodeText(item, NodeType.SCHEMA), getNodeText(item, NodeType.CATALOG))).source(entity).build();
Optional<DatabaseEntity> optionalDatabase = origin.stream().filter(node -> node.getName().equals(database.getName())).findAny();
if (optionalDatabase.isPresent()) {
database.setId(optionalDatabase.get().getId());
database.setCreateTime(optionalDatabase.get().getCreateTime());
if (StringUtils.isEmpty(database.getCode())) {
database.setCode(CodeUtils.generateCode(false));
}
databaseUpdatedCount.addAndGet(1);
}
else {
databaseAddedCount.addAndGet(1);
}
return database;
})
.collect(Collectors.toList());
// Write the new data retrieved to the database
log.info("Added database size [ {} ] to source [ {} ]", entities.size(), entity.getName());
databaseHandler.saveAll(entities);
@ -566,7 +576,9 @@ public class SourceServiceImpl
databaseTableCache.put(key, this.tableHandler.findSimpleAllByDatabase(item));
});
// Delete invalid data that no longer exists
List<DatabaseEntity> deleteEntities = origin.stream().filter(node -> entities.stream().noneMatch(item -> node.getName().equals(item.getName()))).collect(Collectors.toList());
List<DatabaseEntity> deleteEntities = origin.stream()
.filter(node -> entities.stream().noneMatch(item -> node.getName().equals(item.getName())))
.collect(Collectors.toList());
log.info("Removed database size [ {} ] from source [ {} ]", deleteEntities.size(), entity.getName());
databaseHandler.deleteAll(deleteEntities);
databaseRemovedCount.addAndGet(deleteEntities.size());
@ -605,12 +617,34 @@ public class SourceServiceImpl
log.error("The source [ {} ] protocol [ {} ] sync metadata tables [ {} ] failed", entity.getName(), entity.getProtocol(), response.getMessage());
}
else {
List<TableEntity> entities = response.getColumns().stream().map(item -> {
String key = String.format("%s_%s", getNodeText(item, NodeType.CATALOG), getNodeText(item, NodeType.SCHEMA));
DatabaseEntity database = databaseCache.get(key);
String name = getNodeText(item, NodeType.TABLE);
return TableEntity.builder().name(name).description(String.format("Table [ %s ] of database [ %s ] ", name, getNodeText(item, NodeType.SCHEMA))).type(getNodeText(item, NodeType.TYPE)).engine(getNodeText(item, NodeType.ENGINE)).format(getNodeText(item, NodeType.FORMAT)).inCreateTime(getNodeText(item, NodeType.CREATE_TIME)).inUpdateTime(getNodeText(item, NodeType.UPDATE_TIME)).collation(getNodeText(item, NodeType.COLLATION)).rows(getNodeText(item, NodeType.ROWS)).comment(getNodeText(item, NodeType.COMMENT)).avgRowLength(getNodeText(item, NodeType.AVG_ROW)).dataLength(getNodeText(item, NodeType.DATA)).indexLength(getNodeText(item, NodeType.INDEX)).autoIncrement(getNodeText(item, NodeType.AUTO_INCREMENT)).database(database).build();
}).collect(Collectors.toList());
List<TableEntity> entities = response.getColumns()
.stream()
.map(item -> {
String key = String.format("%s_%s", getNodeText(item, NodeType.CATALOG), getNodeText(item, NodeType.SCHEMA));
DatabaseEntity database = databaseCache.get(key);
String name = getNodeText(item, NodeType.TABLE);
TableEntity configure = TableEntity.builder()
.name(name)
.description(String.format("Table [ %s ] of database [ %s ] ", name, getNodeText(item, NodeType.SCHEMA)))
.type(getNodeText(item, NodeType.TYPE)).engine(getNodeText(item, NodeType.ENGINE))
.format(getNodeText(item, NodeType.FORMAT))
.inCreateTime(getNodeText(item, NodeType.CREATE_TIME))
.inUpdateTime(getNodeText(item, NodeType.UPDATE_TIME))
.collation(getNodeText(item, NodeType.COLLATION))
.rows(getNodeText(item, NodeType.ROWS))
.comment(getNodeText(item, NodeType.COMMENT))
.avgRowLength(getNodeText(item, NodeType.AVG_ROW))
.dataLength(getNodeText(item, NodeType.DATA))
.indexLength(getNodeText(item, NodeType.INDEX))
.autoIncrement(getNodeText(item, NodeType.AUTO_INCREMENT))
.database(database)
.build();
if (StringUtils.isEmpty(configure.getCode())) {
configure.setCode(CodeUtils.generateCode(false));
}
return configure;
})
.collect(Collectors.toList());
Map<String, List<TableEntity>> groupEntities = entities.stream().collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getDatabase().getCatalog(), item.getDatabase().getName())));
@ -674,13 +708,34 @@ public class SourceServiceImpl
log.error("The source [ {} ] protocol [ {} ] sync metadata columns [ {} ] failed", entity.getName(), entity.getProtocol(), response.getMessage());
}
else {
List<ColumnEntity> entities = response.getColumns().stream().map(item -> {
String key = String.format("%s_%s", getNodeText(item, NodeType.CATALOG), getNodeText(item, NodeType.SCHEMA));
TableEntity table = tableCache.get(key);
String name = getNodeText(item, NodeType.COLUMN);
ColumnEntity column = ColumnEntity.builder().name(name).description(String.format("Table [ %s ] of column [ %s ] ", table.getName(), name)).type(getNodeText(item, NodeType.COLUMN_TYPE)).comment(getNodeText(item, NodeType.COMMENT)).defaultValue(getNodeText(item, NodeType.DEFAULT)).position(getNodeText(item, NodeType.POSITION)).maximumLength(getNodeText(item, NodeType.MAXIMUM_LENGTH)).collation(getNodeText(item, NodeType.COLLATION)).isKey(getNodeText(item, NodeType.KEY)).privileges(getNodeText(item, NodeType.FORMAT)).dataType(getNodeText(item, NodeType.DATA_TYPE)).extra(getNodeText(item, NodeType.EXTRA)).isNullable(getNodeText(item, NodeType.NULLABLE)).table(table).build();
return column;
}).collect(Collectors.toList());
List<ColumnEntity> entities = response.getColumns()
.stream()
.map(item -> {
String key = String.format("%s_%s", getNodeText(item, NodeType.CATALOG), getNodeText(item, NodeType.SCHEMA));
TableEntity table = tableCache.get(key);
String name = getNodeText(item, NodeType.COLUMN);
ColumnEntity configure = ColumnEntity.builder()
.name(name)
.description(String.format("Table [ %s ] of column [ %s ] ", table.getName(), name))
.type(getNodeText(item, NodeType.COLUMN_TYPE))
.comment(getNodeText(item, NodeType.COMMENT))
.defaultValue(getNodeText(item, NodeType.DEFAULT))
.position(getNodeText(item, NodeType.POSITION))
.maximumLength(getNodeText(item, NodeType.MAXIMUM_LENGTH))
.collation(getNodeText(item, NodeType.COLLATION))
.isKey(getNodeText(item, NodeType.KEY))
.privileges(getNodeText(item, NodeType.FORMAT))
.dataType(getNodeText(item, NodeType.DATA_TYPE))
.extra(getNodeText(item, NodeType.EXTRA))
.isNullable(getNodeText(item, NodeType.NULLABLE))
.table(table)
.build();
if (StringUtils.isEmpty(configure.getCode())) {
configure.setCode(CodeUtils.generateCode(false));
}
return configure;
})
.collect(Collectors.toList());
Map<String, List<ColumnEntity>> groupEntities = entities.stream().collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getTable().getDatabase().getName(), item.getTable().getName())));

View File

@ -86,54 +86,51 @@ public class TableServiceImpl
}
@Override
public CommonResponse<List<TableEntity>> getAllByDatabase(Long id)
public CommonResponse<List<TableEntity>> getAllByDatabase(String code)
{
DatabaseEntity database = DatabaseEntity.builder()
.id(id)
.build();
return CommonResponse.success(this.repository.findAllByDatabase(database));
return databaseRepository.findByCode(code)
.map(value -> CommonResponse.success(this.repository.findAllByDatabase(value)))
.orElseGet(() -> CommonResponse.failure(String.format("Database [ %s ] not found", code)));
}
@Override
public CommonResponse<Object> fetchDataById(Long id, TableFilter configure)
public CommonResponse<Object> fetchData(String code, TableFilter configure)
{
TableEntity table = this.repository.findById(id)
.orElse(null);
if (table == null) {
return CommonResponse.failure(String.format("Table [ %s ] not found", id));
}
SourceEntity source = table.getDatabase().getSource();
Optional<Plugin> pluginOptional = PluginUtils.getPluginByNameAndType(this.injector, source.getType(), source.getProtocol());
if (!pluginOptional.isPresent()) {
return CommonResponse.failure(ServiceState.PLUGIN_NOT_FOUND);
}
Plugin plugin = pluginOptional.get();
if (configure.getType().equals(SqlType.SELECT)) {
return this.fetchSelect(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.INSERT)) {
return this.fetchInsert(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.UPDATE)) {
return this.fetchUpdate(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.DELETE)) {
return this.fetchDelete(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.ALTER)) {
return this.fetchAlter(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.SHOW)) {
return this.fetchShowCreateTable(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.TRUNCATE)) {
return this.fetchTruncateTable(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.DROP)) {
return this.fetchDropTable(plugin, table, source, configure);
}
return CommonResponse.failure(String.format("Not implemented yet [ %s ]", configure.getType()));
return repository.findByCode(code)
.map(table -> {
SourceEntity source = table.getDatabase().getSource();
Optional<Plugin> pluginOptional = PluginUtils.getPluginByNameAndType(this.injector, source.getType(), source.getProtocol());
if (!pluginOptional.isPresent()) {
return CommonResponse.failure(ServiceState.PLUGIN_NOT_FOUND);
}
Plugin plugin = pluginOptional.get();
if (configure.getType().equals(SqlType.SELECT)) {
return this.fetchSelect(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.INSERT)) {
return this.fetchInsert(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.UPDATE)) {
return this.fetchUpdate(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.DELETE)) {
return this.fetchDelete(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.ALTER)) {
return this.fetchAlter(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.SHOW)) {
return this.fetchShowCreateTable(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.TRUNCATE)) {
return this.fetchTruncateTable(plugin, table, source, configure);
}
else if (configure.getType().equals(SqlType.DROP)) {
return this.fetchDropTable(plugin, table, source, configure);
}
return CommonResponse.failure(String.format("Not implemented yet [ %s ]", configure.getType()));
})
.orElse(CommonResponse.failure(String.format("Table [ %s ] not found", code)));
}
@Override
@ -259,59 +256,57 @@ public class TableServiceImpl
}
@Override
public CommonResponse<Object> manageColumn(Long tableId, TableBody configure)
public CommonResponse<Object> manageColumn(String code, TableBody configure)
{
Optional<TableEntity> optionalTable = this.repository.findById(tableId);
if (!optionalTable.isPresent()) {
return CommonResponse.failure(String.format("Table [ %s ] not found", tableId));
}
TableEntity table = optionalTable.get();
SourceEntity source = table.getDatabase().getSource();
Plugin plugin = PluginUtils.getPluginByNameAndType(this.injector, source.getType(), source.getProtocol()).get();
AtomicReference<String> atomicReference = new AtomicReference<>(null);
if (configure.getType().equals(SqlType.CREATE)) {
ColumnBuilder.Companion.BEGIN();
ColumnBuilder.Companion.CREATE_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(configure.getColumns().stream().map(Column::toColumnVar).collect(Collectors.toList()));
atomicReference.set(ColumnBuilder.Companion.SQL());
log.info("Create column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
else if (configure.getType().equals(SqlType.DROP)) {
columnRepository.findById(configure.getColumnId())
.ifPresent(column -> {
return repository.findByCode(code)
.map(table -> {
SourceEntity source = table.getDatabase().getSource();
Plugin plugin = PluginUtils.getPluginByNameAndType(this.injector, source.getType(), source.getProtocol()).get();
AtomicReference<String> atomicReference = new AtomicReference<>(null);
if (configure.getType().equals(SqlType.CREATE)) {
ColumnBuilder.Companion.BEGIN();
ColumnBuilder.Companion.DROP_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(Lists.newArrayList(column.getName()));
ColumnBuilder.Companion.CREATE_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(configure.getColumns().stream().map(Column::toColumnVar).collect(Collectors.toList()));
atomicReference.set(ColumnBuilder.Companion.SQL());
});
log.info("Drop column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
else if (configure.getType().equals(SqlType.MODIFY)) {
ColumnBuilder.Companion.BEGIN();
ColumnBuilder.Companion.MODIFY_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(configure.getColumns().stream().map(Column::toColumnVar).collect(Collectors.toList()));
atomicReference.set(ColumnBuilder.Companion.SQL());
log.info("Modify column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
Response response;
if (configure.isPreview()) {
response = Response.builder()
.isSuccessful(true)
.isConnected(true)
.headers(Lists.newArrayList())
.columns(Lists.newArrayList())
.types(Lists.newArrayList())
.content(atomicReference.get())
.build();
}
else {
plugin.connect(source.toConfigure());
response = plugin.execute(atomicReference.get());
response.setContent(atomicReference.get());
plugin.destroy();
}
return CommonResponse.success(response);
log.info("Create column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
else if (configure.getType().equals(SqlType.DROP)) {
columnRepository.findById(configure.getColumnId())
.ifPresent(column -> {
ColumnBuilder.Companion.BEGIN();
ColumnBuilder.Companion.DROP_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(Lists.newArrayList(column.getName()));
atomicReference.set(ColumnBuilder.Companion.SQL());
});
log.info("Drop column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
else if (configure.getType().equals(SqlType.MODIFY)) {
ColumnBuilder.Companion.BEGIN();
ColumnBuilder.Companion.MODIFY_COLUMN(String.format("`%s`.`%s`", table.getDatabase().getName(), table.getName()));
ColumnBuilder.Companion.COLUMNS(configure.getColumns().stream().map(Column::toColumnVar).collect(Collectors.toList()));
atomicReference.set(ColumnBuilder.Companion.SQL());
log.info("Modify column sql \n {} \n on table [ {} ]", atomicReference.get(), table.getName());
}
Response response;
if (configure.isPreview()) {
response = Response.builder()
.isSuccessful(true)
.isConnected(true)
.headers(Lists.newArrayList())
.columns(Lists.newArrayList())
.types(Lists.newArrayList())
.content(atomicReference.get())
.build();
}
else {
plugin.connect(source.toConfigure());
response = plugin.execute(atomicReference.get());
response.setContent(atomicReference.get());
plugin.destroy();
}
return CommonResponse.success(response);
})
.orElse(CommonResponse.failure(String.format("Table [ %s ] not found", code)));
}
/**

View File

@ -5,7 +5,7 @@ export interface StructureModel
database?: null | any
databaseId?: string
table?: null
tableId?: null
tableId?: string
applyId?: null | number
type?: null
dataType?: null
@ -20,6 +20,7 @@ export interface StructureModel
origin?: any
selected?: boolean
contextmenu?: true
code?: string
children?: StructureModel[]
}

View File

@ -21,6 +21,7 @@ export interface TableModel
autoIncrement?: string
database?: DatabaseModel
columns?: Array<ColumnModel>
code?: string
}
export class TableRequest

View File

@ -1,6 +1,7 @@
import LayoutContainer from '@/views/layouts/common/LayoutContainer.vue'
import ProfileContainer from '@/views/layouts/profile/LayoutContainer.vue'
import { TokenUtils } from '@/utils/token'
import MetadataContainer from '@/views/layouts/metadata/MetadataContainer.vue'
/**
* Create a default router
@ -215,12 +216,67 @@ const createAdminRouter = (router: any) => {
component: () => import('@/views/pages/admin/source/SourceHome.vue')
},
{
path: 'source/manager/:code',
path: 'source/:source',
component: MetadataContainer,
meta: {
title: 'common.source',
isRoot: false
},
component: () => import('@/views/pages/admin/source/SourceManager.vue')
children: [
{
path: 'd/:database/',
meta: {
title: 'common.source',
isRoot: false
},
component: () => import('@/views/pages/admin/source/SourceDatabase.vue')
},
{
path: 'd/:database/t/info/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'info'
},
component: () => import('@/views/pages/admin/source/SourceTableInfo.vue')
},
{
path: 'd/:database/t/structure/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'structure'
},
component: () => import('@/views/pages/admin/source/SourceTableStructure.vue')
},
{
path: 'd/:database/t/data/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'data'
},
component: () => import('@/views/pages/admin/source/SourceTableData.vue')
},
{
path: 'd/:database/t/statement/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'statement'
},
component: () => import('@/views/pages/admin/source/SourceTableStatement.vue')
},
{
path: 'd/:database/t/erDiagram/:table',
meta: {
title: 'common.source',
isRoot: false,
type: 'erDiagram'
},
component: () => import('@/views/pages/admin/source/SourceTableErDiagram.vue')
}
]
},
{
path: 'history',

View File

@ -15,12 +15,12 @@ class ColumnService
/**
* Fetches all items from a table based on the provided ID.
*
* @param {number} id - The ID of the table.
* @param {number} code - The code of the table.
* @return {Promise<ResponseModel>} A Promise that resolves with the response from the API.
*/
getAllByTable(id: number): Promise<ResponseModel>
getAllByTable(code: string): Promise<ResponseModel>
{
return new HttpUtils().post(`${DEFAULT_PATH}/table/${id}`)
return new HttpUtils().post(`${DEFAULT_PATH}/table/${code}`)
}
}

View File

@ -16,39 +16,39 @@ class TableService
/**
* Retrieves all data from the database by the specified ID.
*
* @param {number} id - The ID of the database.
* @param {number} code - The code of the database.
* @return {Promise<ResponseModel>} A promise that resolves to a ResponseModel object.
*/
getAllByDatabase(id: number): Promise<ResponseModel>
getAllByDatabase(code: string): Promise<ResponseModel>
{
return new HttpUtils().post(`${ DEFAULT_PATH }/database/${ id }`)
return new HttpUtils().post(`${ DEFAULT_PATH }/database/${ code }`)
}
/**
* Retrieves data for a specific ID using the provided table filter configuration.
*
* @param {number} id - The ID of the data to retrieve.
* @param {number} code - The code of the data to retrieve.
* @param {TableFilter} configure - The table filter configuration.
* @return {Promise<ResponseModel>} - A promise that resolves to the response model.
*/
getData(id: number, configure: TableFilter): Promise<ResponseModel>
getData(code: string, configure: TableFilter): Promise<ResponseModel>
{
if (!configure) {
configure = <TableFilter>{}
}
return new HttpUtils().post(`${ DEFAULT_PATH }/${ id }`, configure)
return new HttpUtils().post(`${ DEFAULT_PATH }/${ code }`, configure)
}
/**
* A description of the entire function.
*
* @param {number} id - The identifier of the data.
* @param {string} code - The identifier of the data.
* @param {any} configure - The configuration object.
* @return {Promise<ResponseModel>} A promise that resolves to the response model.
*/
putData(id: number, configure: TableFilter): Promise<ResponseModel>
putData(code: string, configure: TableFilter): Promise<ResponseModel>
{
return new HttpUtils().put(`${ DEFAULT_PATH }/${ id }`, configure)
return new HttpUtils().put(`${ DEFAULT_PATH }/${ code }`, configure)
}
/**
@ -68,9 +68,9 @@ class TableService
return new HttpUtils().post(`${ DEFAULT_PATH }/createTable/${ databaseId }`, configure)
}
manageColumn(tableId: number, configure: any): Promise<ResponseModel>
manageColumn(code: string, configure: any): Promise<ResponseModel>
{
return new HttpUtils().post(`${ DEFAULT_PATH }/manageColumn/${ tableId }`, configure)
return new HttpUtils().post(`${ DEFAULT_PATH }/manageColumn/${ code }`, configure)
}
}

View File

@ -23,8 +23,8 @@ export default defineComponent({
Tree
},
props: {
id: {
type: String,
code: {
type: String
}
},
data()
@ -37,109 +37,120 @@ export default defineComponent({
created()
{
this.handlerInitialize()
watch(() => this.id, () => this.handlerInitialize())
watch(() => this.code, () => this.handlerInitialize())
},
methods: {
handlerInitialize()
{
this.dataTreeArray = []
this.loading = true
DatabaseService.getAllBySource(this.id as string)
.then(response => {
if (response.status) {
response.data.forEach((item: { name: null; catalog: null; id: null }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
applyId: item.id,
level: StructureEnum.DATABASE,
loading: false,
children: [] as StructureModel[],
render: (h: any, {data}: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'database',
style: {marginRight: '6px'}
}),
h('span', data.title)
])
])
}
}
this.dataTreeArray.push(structure)
})
}
})
.finally(() => this.loading = false)
DatabaseService.getAllBySource(this.code as string)
.then(response => {
if (response.status) {
response.data
.forEach((item: { name: null; catalog: null; code: undefined }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
code: item.code,
level: StructureEnum.DATABASE,
loading: false,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'database',
style: { marginRight: '6px' }
}),
h('span', data.title)
])
])
}
}
this.dataTreeArray.push(structure)
})
}
})
.finally(() => this.loading = false)
},
handlerLoadChildData(item: StructureModel, callback: any)
{
const dataChildArray = [] as StructureModel[]
if (item.level === StructureEnum.DATABASE) {
TableService.getAllByDatabase(item.applyId as number)
.then(response => {
if (response.status) {
response.data.forEach((item: { name: null; title: null; catalog: null; id: null; type: null; engine: null; database: { name: null }; }) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
catalog: item.catalog,
applyId: item.id,
level: StructureEnum.TABLE,
type: item.type,
engine: item.engine,
loading: false,
children: [] as StructureModel[],
render: (h: any, {data}: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: {marginRight: '6px'}
}),
h('span', data.title)
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
TableService.getAllByDatabase(item.code as string)
.then(response => {
if (response.status) {
response.data
.forEach((item: { name: null; title: null; catalog: null; code: undefined; type: null; engine: null; database: { name: null }; }) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
catalog: item.catalog,
code: item.code,
level: StructureEnum.TABLE,
type: item.type,
engine: item.engine,
loading: false,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: { marginRight: '6px' }
}),
h('span', data.title)
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
}
else if (item.level === StructureEnum.TABLE) {
ColumnService.getAllByTable(item.applyId as number)
.then(response => {
if (response.status) {
response.data.forEach((item: { name: null; title: null; catalog: null; id: null; type: null; engine: null; table: { name: null, database: { name: null } }; }) => {
const structure: StructureModel = {
title: item.name,
database: item.table.database.name,
table: item.table.name,
catalog: item.catalog,
applyId: item.id,
level: StructureEnum.COLUMN,
type: item.type,
engine: item.engine,
render: (h: any, {data}: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'columns',
style: {marginRight: '6px'}
}),
h('span', data.title)
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
ColumnService.getAllByTable(item.code as string)
.then(response => {
if (response.status) {
response.data
.forEach((item: {
name: null;
title: null;
catalog: null;
code: undefined;
type: null;
engine: null;
table: { name: null, database: { name: null } };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.table.database.name,
table: item.table.name,
catalog: item.catalog,
code: item.code,
level: StructureEnum.COLUMN,
type: item.type,
engine: item.engine,
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'columns',
style: { marginRight: '6px' }
}),
h('span', data.title)
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
}
else {
callback(dataChildArray)
@ -151,7 +162,7 @@ export default defineComponent({
let text: string = target.title as string
switch (target.level) {
case StructureEnum.TABLE:
text = target.database + '.' + text;
text = target.database + '.' + text
break
case StructureEnum.COLUMN:
text = target.database + '.' + target.table + '.' + text
@ -160,5 +171,5 @@ export default defineComponent({
ObjectUtils.copy(text)
}
}
});
})
</script>

View File

@ -0,0 +1,28 @@
<template>
<div class="w-full h-full">
<div class="hidden space-y-6 w-full md:block">
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-6 lg:space-y-0">
<aside class="-mx-4 w-[200px]">
<MetadataSidebar/>
</aside>
<div class="flex-1">
<MetadataContent/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import MetadataSidebar from '@/views/layouts/metadata/components/MetadataSidebar.vue'
import MetadataContent from '@/views/layouts/metadata/components/MetadataContent.vue'
export default defineComponent({
name: 'MetadataContainer',
components: {
MetadataContent,
MetadataSidebar
}
})
</script>

View File

@ -0,0 +1,99 @@
<template>
<Tabs v-model="selectTab as string" :default-value="selectTab as string" class="w-full">
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<TabsList>
<TabsTrigger value="info" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<Info :size="18"/>
<span>{{ $t('source.common.info') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="structure" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<LayoutPanelTop :size="18"/>
<span>{{ $t('source.common.structure') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="data" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<Table :size="18"/>
<span>{{ $t('source.common.tableData') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="statement" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<SatelliteDish :size="18"/>
<span>{{ $t('source.common.statement') }}</span>
</div>
</TabsTrigger>
<TabsTrigger value="erDiagram" class="cursor-pointer" @click="handlerChange">
<div class="flex space-x-2">
<Wind :size="18"/>
<span>{{ $t('source.common.erDiagram') }}</span>
</div>
</TabsTrigger>
</TabsList>
</template>
<TabsContent :value="selectTab as string">
<div class="h-[695px] overflow-x-auto overflow-y-auto">
<RouterView/>
</div>
</TabsContent>
</Card>
</Tabs>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue'
import Card from '@/views/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Info, LayoutPanelTop, SatelliteDish, Table, Wind } from 'lucide-vue-next'
export default defineComponent({
name: 'MetadataContent',
components: {
Tabs, TabsContent, TabsList, TabsTrigger,
Card,
Info, LayoutPanelTop, Table, SatelliteDish, Wind
},
data()
{
return {
selectTab: null as string | null,
originalSource: null as string | null,
originalDatabase: null as string | null,
originalTable: null as string | null
}
},
created()
{
this.handlerInitialize()
this.watchChange()
},
methods: {
handlerInitialize()
{
const source = this.$route.params?.source as string
const database = this.$route.params?.database as string
const table = this.$route.params?.table as string
const type = this.$route.meta.type as string
this.originalSource = source
this.originalDatabase = database
this.originalTable = table
this.selectTab = type
},
handlerChange()
{
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.originalDatabase }/t/${ this.selectTab }/${ this.originalTable }`)
},
watchChange()
{
watch(
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}
})
</script>

View File

@ -0,0 +1,451 @@
<template>
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<Select v-model="selectDatabase" :default-value="originalDatabase ? originalDatabase : selectDatabase" @update:modelValue="handlerChangeDatabase">
<SelectTrigger class="border-0 w-[200px]">
<SelectValue :placeholder="$t('source.tip.selectDatabase')"/>
</SelectTrigger>
<SelectContent class="w-full">
<SelectGroup class="w-full">
<SelectItem v-for="item in databaseArray" :value="item.code as any" :key="item.title as string" class="cursor-pointer">
{{ item.title }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</template>
<div class="h-[700px] overflow-x-auto overflow-y-auto">
<CircularLoading v-if="loading" :show="loading"/>
<div v-else>
<Tree :data="dataTreeArray" :empty-text="$t('source.tip.selectDatabase')" :load-data="handlerLoadChildData" @on-select-change="handlerSelectNode"
@on-contextmenu="handlerContextMenu">
<template #contextMenu>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<span id="contextMenu"></span>
</DropdownMenuTrigger>
<DropdownMenuContent class="-mt-3">
<DropdownMenuLabel>{{ $t('common.action') }}</DropdownMenuLabel>
<DropdownMenuSeparator/>
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger class="cursor-pointer">{{ $t('source.common.menuNew') }}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerCreateTable(true)">
<Table :size="18" class="mr-2"/>
{{ $t('source.common.menuNewTable') }}
</DropdownMenuItem>
<DropdownMenuItem class="cursor-pointer" @click="handlerCreateColumn(true)">
<Columns :size="18" class="mr-2"/>
{{ $t('source.common.newColumn') }}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuGroup v-if="dataInfo?.level === StructureEnum.TABLE">
<DropdownMenuSub>
<DropdownMenuSubTrigger class="cursor-pointer">{{ $t('source.common.menuExport') }}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerExportData(true)">
<ArrowUpFromLine :size="18" class="mr-2"/>
{{ $t('source.common.exportData') }}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator/>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerTruncateTable(true)">
<Trash :size="18" class="mr-2"/>
{{ $t('source.common.truncateTable') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerDropTable(true)">
<Delete :size="18" class="mr-2"/>
{{ $t('source.common.dropTable') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.COLUMN" class="cursor-pointer" @click="handlerChangeColumn(true)">
<Pencil :size="18" class="mr-2"/>
{{ $t('source.common.changeColumn') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.COLUMN" class="cursor-pointer" @click="handlerDropColumn(true)">
<Delete :size="18" class="mr-2"/>
{{ $t('source.common.dropColumn') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
</Tree>
</div>
</div>
</Card>
<TableCreate v-if="tableCreateVisible" :isVisible="tableCreateVisible" :info="dataInfo" @close="handlerCreateTable(false)"/>
<TableExport v-if="tableExportVisible" :isVisible="tableExportVisible" :info="dataInfo" @close="handlerExportData(false)"/>
<TableTruncate v-if="tableTruncateVisible" :isVisible="tableTruncateVisible" :info="dataInfo" @close="handlerTruncateTable(false)"/>
<TableDrop v-if="tableDropVisible" :isVisible="tableDropVisible" :info="dataInfo" @close="handlerDropTable(false)"/>
<ColumnCreate v-if="columnCreateVisible" :isVisible="columnCreateVisible" :info="dataInfo" @close="handlerCreateColumn(false)"/>
<ColumnChange v-if="columnChangeVisible" :isVisible="columnChangeVisible" :info="dataInfo" @close="handlerChangeColumn(false)"/>
<ColumnDrop v-if="columnDropVisible" :isVisible="columnDropVisible" :info="dataInfo" @close="handlerDropColumn(false)"/>
</template>
<script lang="ts">
import { defineComponent, resolveComponent } from 'vue'
import { ArrowUpFromLine, Columns, Delete, Pencil, Table, Trash } from 'lucide-vue-next'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import DatabaseService from '@/services/database.ts'
import { StructureEnum, StructureModel } from '@/model/structure.ts'
import { Tree } from 'view-ui-plus'
import '@/views/components/tree/style.css'
import Card from '@/views/ui/card'
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select'
import TableService from '@/services/table.ts'
import ColumnService from '@/services/column.ts'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import ColumnCreate from '@/views/pages/admin/source/components/ColumnCreate.vue'
import ColumnDrop from '@/views/pages/admin/source/components/ColumnDrop.vue'
import TableExport from '@/views/pages/admin/source/components/TableExport.vue'
import ColumnChange from '@/views/pages/admin/source/components/ColumnChange.vue'
import TableTruncate from '@/views/pages/admin/source/components/TableTruncate.vue'
import TableDrop from '@/views/pages/admin/source/components/TableDrop.vue'
import TableCreate from '@/views/pages/admin/source/components/TableCreate.vue'
import { ToastUtils } from '@/utils/toast.ts'
export default defineComponent({
name: 'MetadataSidebar',
components: {
TableCreate, TableDrop, TableTruncate, ColumnChange, TableExport, ColumnDrop, ColumnCreate,
Card,
Tree,
CircularLoading,
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue,
Columns, Pencil, ArrowUpFromLine, Delete, Trash, Table,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger
},
computed: {
StructureEnum()
{
return StructureEnum
}
},
data()
{
return {
loading: false,
selectDatabase: undefined,
originalSource: null as string | null,
originalDatabase: null as string | null,
originalTable: null as string | null,
selectNode: null as StructureModel | null,
databaseArray: Array<StructureModel>(),
dataTreeArray: Array<StructureModel>(),
dataInfo: null as StructureModel | null,
tableCreateVisible: false,
tableExportVisible: false,
tableTruncateVisible: false,
tableDropVisible: false,
columnCreateVisible: false,
columnChangeVisible: false,
columnDropVisible: false
}
},
created()
{
this.handlerInitialize()
},
methods: {
handlerInitialize()
{
const source = this.$route.params?.source as string
const database = this.$route.params?.database as string
if (source) {
this.originalSource = source
this.loading = true
DatabaseService.getAllBySource(source)
.then(response => {
if (response.status) {
response.data
.forEach((item: { name: null; catalog: null; code: undefined }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
code: item.code
}
this.databaseArray.push(structure)
})
if (database) {
this.originalDatabase = database
this.selectDatabase = database as any
this.handlerChangeDatabase()
}
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => this.loading = false)
}
},
handlerChangeDatabase()
{
this.loading = true
this.dataTreeArray = []
TableService.getAllByDatabase(this.selectDatabase as any)
.then(response => {
if (response.status) {
response.data
.forEach((item: {
name: null;
title: null;
catalog: null;
code: undefined;
type: null;
engine: null;
comment: null;
database: { name: null, id: string };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
databaseId: item.database.id,
catalog: item.catalog,
code: item.code,
type: item.type,
level: StructureEnum.TABLE,
engine: item.engine,
comment: item.comment,
origin: item,
loading: false,
contextmenu: true,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: { marginRight: '6px' }
}),
this.resolveTableComponent(h, data)
])
])
}
}
this.dataTreeArray.push(structure)
})
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => {
this.loading = false
const table = this.$route.params?.table as string
if (table) {
const node = this.dataTreeArray.find(item => item.code === table)
if (node) {
node.selected = true
this.handlerSelectNode([node])
}
}
else {
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.selectDatabase }`)
}
})
},
handlerSelectNode(node: Array<StructureModel>)
{
if (node.length === 0 && this.selectNode) {
// Prevent selection clearing after repeated clicks
this.selectNode.selected = true
return
}
const currentNode = node[0]
if (currentNode.level === StructureEnum.COLUMN) {
if (this.selectNode) {
this.selectNode.selected = true
}
currentNode.selected = false
return
}
this.selectNode = currentNode
const type = this.$route.meta.type as string
this.$router.push(`/admin/source/${ this.originalSource }/d/${ this.selectDatabase }/t/${ type ? type : 'info' }/${ currentNode.code }`)
},
handlerLoadChildData(item: StructureModel, callback: any)
{
const dataChildArray = [] as StructureModel[]
if (item.level === StructureEnum.COLUMN) {
callback(dataChildArray)
return
}
ColumnService.getAllByTable(item.code as string)
.then(response => {
if (response.status) {
response.data.forEach((item: {
name: null;
title: null;
catalog: null;
code: undefined;
type: null;
dataType: null;
extra: null;
engine: null;
isKey: null;
defaultValue: null;
table: { name: null, code: undefined, database: { name: null, id: string } };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.table.database.name,
databaseId: item.table.database.id,
table: item.table.name,
tableId: item.table.code,
catalog: item.catalog,
code: item.code,
level: StructureEnum.COLUMN,
type: item.type,
extra: item.extra,
dataType: item.dataType,
engine: item.engine,
isKey: item.isKey,
defaultValue: item.defaultValue,
contextmenu: true,
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: this.getColumnIcon(data.isKey as unknown as string),
style: { marginRight: '6px' }
}),
h('span', data.title),
h('span', {
style: {
marginLeft: '6px',
color: '#c5c8ce'
}
},
this.getColumnTitle(data.type as unknown as string,
data.extra as unknown as string,
data.isKey as unknown as string,
data.defaultValue as unknown as string))
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
},
handlerContextMenu(node: StructureModel)
{
console.log(node)
this.dataInfo = node
// Simulate right-click to trigger right-click menu
const element = document.getElementById('contextMenu') as HTMLElement
if (element) {
element.click()
}
},
handlerCreateTable(opened: boolean)
{
this.tableCreateVisible = opened
},
handlerCreateColumn(opened: boolean)
{
this.columnCreateVisible = opened
},
handlerExportData(opened: boolean)
{
this.tableExportVisible = opened
},
handlerTruncateTable(opened: boolean)
{
this.tableTruncateVisible = opened
},
handlerDropTable(opened: boolean)
{
this.tableDropVisible = opened
},
handlerChangeColumn(opened: boolean)
{
this.columnChangeVisible = opened
},
handlerDropColumn(opened: boolean)
{
this.columnDropVisible = opened
},
getColumnIcon(type: string)
{
if (type === 'PRI') {
return 'key'
}
else if (type === 'MUL') {
return 'repeat'
}
else if (type === 'UNI') {
return 'circle'
}
else {
return 'columns'
}
},
getColumnTitle(dataType: string, extra: string, isKey: string, defaultValue: string)
{
let title = dataType
if (isKey === 'PRI') {
if (extra) {
title = `${ title } (${ extra.replace('_', ' ') })`
}
else {
title = `${ title }`
}
}
if (defaultValue && defaultValue !== 'null') {
title = `${ title } = ${ defaultValue }`
}
return title
},
resolveTableComponent(h: any, data: StructureModel)
{
if (data.comment) {
return h(resolveComponent('Tooltip'), {
content: data.comment,
placement: 'bottom-start',
delay: 1000
},
h('span', data.title))
}
else {
return h('span', data.title)
}
}
}
})
</script>

View File

@ -5,7 +5,7 @@
<Card>
<CardHeader class="p-0">
<SourceSelect :value="selectSource.full as string" @changeValue="handlerChangeValue($event)"/>
<DataStructureLazyTree v-if="selectSource.id" :id="selectSource.id as string"/>
<DataStructureLazyTree v-if="selectSource.code" :code="selectSource.code as string"/>
</CardHeader>
</Card>
</aside>

View File

@ -0,0 +1,19 @@
<template>
<Card :body-class="'p-8'" :hidden-title="true">
<Alert :description="$t('source.tip.notSelectedNode')"/>
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Card from '@/views/ui/card'
import Alert from '@/views/ui/alert'
export default defineComponent({
name: 'SourceDatabase',
components: {
Card,
Alert
}
})
</script>

View File

@ -42,7 +42,7 @@
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem :disabled="(loginUserId !== row.user.id) || !row.available" class="cursor-pointer">
<RouterLink :to="`/admin/source/manager/${row?.code}`" target="_blank" class="flex items-center">
<RouterLink :to="`/admin/source/${row?.code}`" target="_blank" class="flex items-center">
<Cog class="mr-2 h-4 w-4"/>
<span>{{ $t('source.common.manager') }}</span>
</RouterLink>

View File

@ -1,498 +0,0 @@
<template>
<div class="w-full">
<CircularLoading v-if="loading" :show="loading"/>
<div v-else class="hidden space-y-6 pb-16 w-full md:block">
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-6 lg:space-y-0">
<aside class="-mx-4 w-[200px]">
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<Select v-model="applyValue.database" @update:modelValue="handlerChangeDatabase">
<SelectTrigger class="border-0 w-[200px]">
<SelectValue :placeholder="$t('source.tip.selectDatabase')"/>
</SelectTrigger>
<SelectContent class="w-full">
<SelectGroup class="w-full">
<SelectItem v-for="item in databaseArray" :value="item.applyId as any" :key="item.title as string" class="cursor-pointer">
{{ item.title }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</template>
<div class="h-[500px] overflow-x-auto overflow-y-auto">
<CircularLoading v-if="dataTreeLoading" :show="dataTreeLoading"/>
<div v-else>
<Tree :data="dataTreeArray" :empty-text="$t('source.tip.selectDatabase')" :load-data="handlerLoadChildData" @on-select-change="handlerSelectNode"
@on-contextmenu="handlerContextMenu">
<template #contextMenu>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<span id="contextMenu"></span>
</DropdownMenuTrigger>
<DropdownMenuContent class="-mt-3">
<DropdownMenuLabel>{{ $t('common.action') }}</DropdownMenuLabel>
<DropdownMenuSeparator/>
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger class="cursor-pointer">{{ $t('source.common.menuNew') }}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerCreateTable(true)">
<Table :size="18" class="mr-2"/>
{{ $t('source.common.menuNewTable') }}
</DropdownMenuItem>
<DropdownMenuItem class="cursor-pointer" @click="handlerCreateColumn(true)">
<Columns :size="18" class="mr-2"/>
{{ $t('source.common.newColumn') }}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuGroup v-if="dataInfo?.level === StructureEnum.TABLE">
<DropdownMenuSub>
<DropdownMenuSubTrigger class="cursor-pointer">{{ $t('source.common.menuExport') }}</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerExportData(true)">
<ArrowUpFromLine :size="18" class="mr-2"/>
{{ $t('source.common.exportData') }}
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator/>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerTruncateTable(true)">
<Trash :size="18" class="mr-2"/>
{{ $t('source.common.truncateTable') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.TABLE" class="cursor-pointer" @click="handlerDropTable(true)">
<Delete :size="18" class="mr-2"/>
{{ $t('source.common.dropTable') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.COLUMN" class="cursor-pointer" @click="handlerChangeColumn(true)">
<Pencil :size="18" class="mr-2"/>
{{ $t('source.common.changeColumn') }}
</DropdownMenuItem>
<DropdownMenuItem v-if="dataInfo?.level === StructureEnum.COLUMN" class="cursor-pointer" @click="handlerDropColumn(true)">
<Delete :size="18" class="mr-2"/>
{{ $t('source.common.dropColumn') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
</Tree>
</div>
</div>
</Card>
</aside>
<div class="flex-1">
<Card v-if="!applyValue.node" :body-class="'p-8'" :hidden-title="true">
<Alert :description="$t('source.tip.notSelectedNode')"/>
</Card>
<Tabs v-else v-model="applyValue.tabType" :default-value="applyValue.tabType" class="w-full">
<Card :title-class="'p-0'" :body-class="'p-0'">
<template #title>
<TabsList>
<TabsTrigger value="info" class="cursor-pointer">
<Info :size="18" class="mr-2"/>
{{ $t('source.common.info') }}
</TabsTrigger>
<TabsTrigger value="structure" class="cursor-pointer">
<LayoutPanelTop :size="18" class="mr-2"/>
{{ $t('source.common.structure') }}
</TabsTrigger>
<TabsTrigger value="data" class="cursor-pointer">
<Table :size="18" class="mr-2"/>
{{ $t('source.common.tableData') }}
</TabsTrigger>
<TabsTrigger value="statement" class="cursor-pointer">
<SatelliteDish :size="18" class="mr-2"/>
{{ $t('source.common.statement') }}
</TabsTrigger>
<TabsTrigger value="erDiagram" class="cursor-pointer">
<Wind :size="18" class="mr-2"/>
{{ $t('source.common.erDiagram') }}
</TabsTrigger>
</TabsList>
</template>
<TabsContent value="info" class="p-3">
<TableInfo v-if="applyValue.node" :info="applyValue.node"/>
</TabsContent>
<TabsContent value="structure">
<TableStructure v-if="applyValue.node" :info="applyValue.node"/>
</TabsContent>
<TabsContent value="data" class="mt-0">
<TableData v-if="applyValue.node" :info="applyValue.node"/>
</TabsContent>
<TabsContent value="statement">
<TableStatement v-if="applyValue.node" :info="applyValue.node"/>
</TabsContent>
<TabsContent value="erDiagram">
<TableErDiagram v-if="applyValue.node" :info="applyValue.node"/>
</TabsContent>
</Card>
</Tabs>
</div>
</div>
</div>
</div>
<ColumnCreate v-if="columnCreateVisible" :isVisible="columnCreateVisible" :info="dataInfo" @close="handlerCreateColumn(false)"/>
<ColumnChange v-if="columnChangeVisible" :isVisible="columnChangeVisible" :info="dataInfo" @close="handlerChangeColumn(false)"/>
<ColumnDrop v-if="columnDropVisible" :isVisible="columnDropVisible" :info="dataInfo" @close="handlerDropColumn(false)"/>
<TableCreate v-if="tableCreateVisible" :isVisible="tableCreateVisible" :info="dataInfo" @close="handlerCreateTable(false)"/>
<TableExport v-if="tableExportVisible" :isVisible="tableExportVisible" :info="dataInfo" @close="handlerExportData(false)"/>
<TableTruncate v-if="tableTruncateVisible" :isVisible="tableTruncateVisible" :info="dataInfo" @close="handlerTruncateTable(false)"/>
<TableDrop v-if="tableDropVisible" :isVisible="tableDropVisible" :info="dataInfo" @close="handlerDropTable(false)"/>
</template>
<script lang="ts">
import { defineComponent, resolveComponent } from 'vue'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import { StructureEnum, StructureModel } from '@/model/structure'
import { useRouter } from 'vue-router'
import DatabaseService from '@/services/database'
import Card from '@/views/ui/card'
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/components/ui/select'
import TableService from '@/services/table'
import { Tree } from 'view-ui-plus'
import '@/views/components/tree/style.css'
import ColumnService from '@/services/column'
import Alert from '@/views/ui/alert'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ArrowUpFromLine, Columns, Delete, Info, LayoutPanelTop, Pencil, SatelliteDish, Table, Trash, Wind } from 'lucide-vue-next'
import TableInfo from '@/views/pages/admin/source/components/TableInfo.vue'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { toNumber } from 'lodash'
import TableCreate from '@/views/pages/admin/source/components/TableCreate.vue'
import ColumnCreate from '@/views/pages/admin/source/components/ColumnCreate.vue'
import TableExport from '@/views/pages/admin/source/components/TableExport.vue'
import TableTruncate from '@/views/pages/admin/source/components/TableTruncate.vue'
import TableDrop from '@/views/pages/admin/source/components/TableDrop.vue'
import TableStructure from '@/views/pages/admin/source/components/TableStructure.vue'
import ColumnChange from '@/views/pages/admin/source/components/ColumnChange.vue'
import ColumnDrop from '@/views/pages/admin/source/components/ColumnDrop.vue'
import TableData from '@/views/pages/admin/source/components/TableData.vue'
import TableStatement from '@/views/pages/admin/source/components/TableStatement.vue'
import TableErDiagram from '@/views/pages/admin/source/components/TableErDiagram.vue'
export default defineComponent({
name: 'SourceManager',
components: {
TableErDiagram,
TableStatement,
TableData,
ColumnDrop,
ColumnChange,
TableStructure,
TableDrop,
TableTruncate,
TableExport,
ColumnCreate,
TableCreate,
TableInfo,
Alert,
Card,
CircularLoading,
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue,
Tree,
Tabs, TabsContent, TabsList, TabsTrigger,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
Info, Table, Columns, ArrowUpFromLine, Trash, Delete, LayoutPanelTop, Pencil, SatelliteDish, Wind
},
computed: {
StructureEnum()
{
return StructureEnum
}
},
data()
{
return {
loading: false,
databaseArray: Array<StructureModel>(),
applyValue: {
database: undefined,
node: null as StructureModel | null,
tabType: 'info'
},
dataTreeLoading: false,
dataTreeArray: Array<StructureModel>(),
dataInfo: null as StructureModel | null,
tableCreateVisible: false,
tableExportVisible: false,
tableTruncateVisible: false,
tableDropVisible: false,
columnCreateVisible: false,
columnChangeVisible: false,
columnDropVisible: false
}
},
created()
{
this.handlerInitialize()
},
methods: {
handlerInitialize()
{
this.loading = true
const router = useRouter()
const code = router.currentRoute?.value?.params['code'] as string
DatabaseService.getAllBySource(code)
.then(response => {
if (response.status) {
response.data.forEach((item: { name: null; catalog: null; id: null }) => {
const structure: StructureModel = {
title: item.name,
catalog: item.catalog,
applyId: item.id
}
this.databaseArray.push(structure)
})
}
})
.finally(() => this.loading = false)
},
handlerChangeDatabase()
{
this.dataTreeLoading = true
this.dataTreeArray = []
TableService.getAllByDatabase(toNumber(this.applyValue.database))
.then(response => {
if (response.status) {
response.data.forEach((item: {
name: null;
title: null;
catalog: null;
id: null;
type: null;
engine: null;
comment: null;
database: { name: null, id: string };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.database.name,
databaseId: item.database.id,
catalog: item.catalog,
applyId: item.id,
type: item.type,
level: StructureEnum.TABLE,
engine: item.engine,
comment: item.comment,
origin: item,
loading: false,
contextmenu: true,
children: [] as StructureModel[],
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: 'table',
style: { marginRight: '6px' }
}),
this.resolveTableComponent(h, data)
])
])
}
}
this.dataTreeArray.push(structure)
})
}
})
.finally(() => this.dataTreeLoading = false)
},
handlerSelectNode(node: Array<StructureModel>)
{
if (node.length === 0 && this.applyValue.node) {
// Prevent selection clearing after repeated clicks
this.applyValue.node.selected = true
return
}
const currentNode = node[0]
if (currentNode.level === StructureEnum.COLUMN) {
if (this.applyValue.node) {
this.applyValue.node.selected = true
}
currentNode.selected = false
return
}
this.applyValue.node = currentNode
},
handlerLoadChildData(item: StructureModel, callback: any)
{
const dataChildArray = [] as StructureModel[]
if (item.level === StructureEnum.COLUMN) {
callback(dataChildArray)
return
}
ColumnService.getAllByTable(item.applyId as number)
.then(response => {
if (response.status) {
response.data.forEach((item: {
name: null;
title: null;
catalog: null;
id: null;
type: null;
dataType: null;
extra: null;
engine: null;
isKey: null;
defaultValue: null;
table: { name: null, id: null, database: { name: null, id: string } };
}) => {
const structure: StructureModel = {
title: item.name,
database: item.table.database.name,
databaseId: item.table.database.id,
table: item.table.name,
tableId: item.table.id,
catalog: item.catalog,
applyId: item.id,
level: StructureEnum.COLUMN,
type: item.type,
extra: item.extra,
dataType: item.dataType,
engine: item.engine,
isKey: item.isKey,
defaultValue: item.defaultValue,
contextmenu: true,
render: (h: any, { data }: { data: StructureModel }) => {
return h('div', [
h('span', [
h(resolveComponent('FontAwesomeIcon'), {
icon: this.getColumnIcon(data.isKey as unknown as string),
style: { marginRight: '6px' }
}),
h('span', data.title),
h('span', {
style: {
marginLeft: '6px',
color: '#c5c8ce'
}
},
this.getColumnTitle(data.type as unknown as string,
data.extra as unknown as string,
data.isKey as unknown as string,
data.defaultValue as unknown as string))
])
])
}
}
dataChildArray.push(structure)
})
}
})
.finally(() => callback(dataChildArray))
},
handlerContextMenu(node: StructureModel)
{
this.dataInfo = node
// Simulate right-click to trigger right-click menu
const element = document.getElementById('contextMenu') as HTMLElement
if (element) {
element.click()
}
},
handlerCreateTable(opened: boolean)
{
this.tableCreateVisible = opened
},
handlerCreateColumn(opened: boolean)
{
this.columnCreateVisible = opened
},
handlerExportData(opened: boolean)
{
this.tableExportVisible = opened
},
handlerTruncateTable(opened: boolean)
{
this.tableTruncateVisible = opened
},
handlerDropTable(opened: boolean)
{
this.tableDropVisible = opened
},
handlerChangeColumn(opened: boolean)
{
this.columnChangeVisible = opened
},
handlerDropColumn(opened: boolean)
{
this.columnDropVisible = opened
},
getColumnIcon(type: string)
{
if (type === 'PRI') {
return 'key'
}
else if (type === 'MUL') {
return 'repeat'
}
else if (type === 'UNI') {
return 'circle'
}
else {
return 'columns'
}
},
getColumnTitle(dataType: string, extra: string, isKey: string, defaultValue: string)
{
let title = dataType
if (isKey === 'PRI') {
if (extra) {
title = `${ title } (${ extra.replace('_', ' ') })`
}
else {
title = `${ title }`
}
}
if (defaultValue && defaultValue !== 'null') {
title = `${ title } = ${ defaultValue }`
}
return title
},
resolveTableComponent(h: any, data: StructureModel)
{
if (data.comment) {
return h(resolveComponent('Tooltip'), {
content: data.comment,
placement: 'bottom-start',
delay: 1000
},
h('span', data.title))
}
else {
return h('span', data.title)
}
}
}
})
</script>

View File

@ -109,14 +109,14 @@
</div>
</template>
<CircularLoading v-if="refererLoading" :show="refererLoading"/>
<AgGridVue class="ag-theme-datacap" style="width: 100%; min-height: 460px; height: 460px;" :gridOptions="gridOptions" :columnDefs="configure.headers"
<AgGridVue class="ag-theme-datacap h-[650px]" :gridOptions="gridOptions" :columnDefs="configure.headers"
:rowData="configure.datasets" :tooltipShowDelay="100" :sortingOrder="['desc', 'asc', null]" :rowSelection="'multiple'" @grid-ready="handlerGridReady"
@sortChanged="handlerSortChanged" @cellValueChanged="handlerCellValueChanged" @selectionChanged="handlerSelectionChanged" @columnVisible="handlerColumnVisible"
@columnMoved="handlerColumnMoved"/>
</Card>
<TableCellInfo v-if="dataCellChanged.pending && info" :isVisible="dataCellChanged.pending" :columns="dataCellChanged.columns" :tableId="info.applyId as number"
:type="dataCellChanged.type" @close="handlerCellChangedPreview(false)"/>
<TableRowDelete v-if="dataSelectedChanged.pending && info" :isVisible="dataSelectedChanged.pending" :tableId="info.applyId as number" :columns="dataSelectedChanged.columns"
<TableCellInfo v-if="dataCellChanged.pending" :isVisible="dataCellChanged.pending" :columns="dataCellChanged.columns" :type="dataCellChanged.type"
@close="handlerCellChangedPreview(false)"/>
<TableRowDelete v-if="dataSelectedChanged.pending" :isVisible="dataSelectedChanged.pending" :columns="dataSelectedChanged.columns"
@close="handlerSelectedChangedPreview(false)"/>
<TableColumn v-if="visibleColumn.show" :isVisible="visibleColumn.show" :columns="visibleColumn.columns" @close="handlerVisibleColumn($event, false)"
@change="handlerVisibleColumn($event, false)"/>
@ -128,7 +128,6 @@
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { StructureModel } from '@/model/structure'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import { useI18n } from 'vue-i18n'
import { AgGridVue } from 'ag-grid-vue3'
@ -136,12 +135,12 @@ import 'ag-grid-community/styles/ag-grid.css'
import '@/views/components/grid/ag-theme-datacap.css'
import { ColumnApi, ColumnState, GridApi } from 'ag-grid-community'
import Card from '@/views/ui/card'
import { PaginationEnum, PaginationModel } from '@/model/pagination'
import { PaginationEnum, PaginationModel } from '@/model/pagination.ts'
import { createColumnDefs, createDataEditorOptions } from '@/views/pages/admin/source/components/TableUtils.ts'
import { OrderFilter, SqlColumn, SqlType, TableFilter } from '@/model/table'
import TableService from '@/services/table'
import { cloneDeep, toNumber } from 'lodash'
import { ToastUtils } from '@/utils/toast'
import { OrderFilter, SqlColumn, SqlType, TableFilter } from '@/model/table.ts'
import TableService from '@/services/table.ts'
import { cloneDeep } from 'lodash'
import { ToastUtils } from '@/utils/toast.ts'
import Button from '@/views/ui/button'
import Tooltip from '@/views/ui/tooltip'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
@ -154,7 +153,7 @@ import TableColumn from '@/views/pages/admin/source/components/TableColumn.vue'
import TableRowFilter from '@/views/pages/admin/source/components/TableRowFilter.vue'
export default defineComponent({
name: 'TableData',
name: 'SourceTableData',
components: {
TableRowFilter,
TableColumn,
@ -170,16 +169,11 @@ export default defineComponent({
Popover, PopoverContent, PopoverTrigger,
ArrowLeftToLine, ArrowLeft, ArrowRight, ArrowRightToLine, Cog, Plus, RectangleEllipsis, Copy, Minus, Eye, RefreshCw, Columns, Filter
},
props: {
info: {
type: Object as () => StructureModel | null
}
},
created()
{
this.i18n = useI18n()
this.handlerInitialize()
this.watchId()
this.watchChange()
},
data()
{
@ -242,9 +236,10 @@ export default defineComponent({
}
this.configure.pagination = pagination
}
if (this.info) {
const code = this.$route?.params.table as string
if (code) {
this.loading = true
TableService.getData(toNumber(this.info.applyId), this.configure)
TableService.getData(code, this.configure)
.then(response => {
if (response.status && response.data) {
this.configure.headers = createColumnDefs(response.data.headers, response.data.types)
@ -272,9 +267,10 @@ export default defineComponent({
{
this.configure.datasets = []
this.gridOptions.overlayNoRowsTemplate = '<span></span>'
if (this.info) {
const code = this.$route?.params.table as string
if (code) {
this.refererLoading = true
TableService.getData(toNumber(this.info.applyId), configure)
TableService.getData(code, configure)
.then(response => {
if (response.status && response.data) {
this.configure.headers = createColumnDefs(response.data.headers, response.data.types)
@ -466,10 +462,10 @@ export default defineComponent({
{
this.newRows = []
},
watchId()
watchChange()
{
watch(
() => this.info,
() => this.$route?.params.table,
() => {
this.configure.pagination = null as unknown as PaginationModel
this.handlerInitialize()

View File

@ -8,26 +8,14 @@
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue'
import ColumnService from '@/services/column'
import { StructureModel } from '@/model/structure.ts'
import { toNumber } from 'lodash'
import ColumnService from '@/services/column.ts'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import ErDiagram from '@/views/components/diagram/ErDiagram.vue'
import { ErDiagramOptions } from '@/views/components/diagram/ErDiagramOptions'
import { ErDiagramOptions } from '@/views/components/diagram/ErDiagramOptions.ts'
export default defineComponent({
name: 'TableErDiagram',
name: 'SourceTableErDiagram',
components: { ErDiagram, CircularLoading },
props: {
info: {
type: Object as () => StructureModel | null
}
},
created()
{
this.handlerInitialize()
this.watchId()
},
data()
{
return {
@ -35,29 +23,34 @@ export default defineComponent({
options: null as unknown as ErDiagramOptions
}
},
created()
{
this.handlerInitialize()
this.watchChange()
},
methods: {
handlerInitialize()
{
this.loading = true
if (this.info) {
ColumnService.getAllByTable(toNumber(this.info.applyId))
const code = this.$route?.params.table as string
if (code) {
this.loading = true
ColumnService.getAllByTable(code)
.then(response => {
if (response.status) {
if (response.status && response.data?.length > 0) {
const table = response.data[0]
this.options = new ErDiagramOptions()
this.options.table = { id: toNumber(this.info?.applyId), name: this.info?.title as string }
this.options.table = { id: table.id, name: table.name }
this.options.columns = response.data
}
})
.finally(() => this.loading = false)
}
},
watchId()
watchChange()
{
watch(
() => this.info,
() => {
this.handlerInitialize()
}
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="pl-3 pr-3">
<CircularLoading v-if="loading" :show="loading"/>
<div v-else-if="dataInfo">
<div class="grid w-full grid-cols-3 gap-6 pt-2">
@ -93,7 +93,7 @@
<div class="grid grid-cols-3 items-center gap-4">
<Label for="autoIncrement">{{ $t('source.common.resetTo') }}</Label>
<Input v-model="dataInfo.autoIncrement" id="autoIncrement" type="number" :default-value="dataInfo.autoIncrement"/>
<Button :loading="loading" @click="handlerApply">
<Button size="sm" :loading="submitting" :disabled="submitting" @click="handlerApply">
{{ $t('common.apply') }}
</Button>
</div>
@ -127,11 +127,9 @@ import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { ToastUtils } from '@/utils/toast'
import { Textarea } from '@/components/ui/textarea'
import { StructureModel } from '@/model/structure'
import { cloneDeep, toNumber } from 'lodash'
export default defineComponent({
name: 'TableInfo',
name: 'SourceTableInfo',
components: {
Textarea,
Input,
@ -144,39 +142,46 @@ export default defineComponent({
Database, Table, Clock, CalendarHeart, ArrowUpDown, TableCellsMerge, RemoveFormatting, ArrowUp10, Search, Cog,
Popover, PopoverContent, PopoverTrigger
},
props: {
info: {
type: Object as () => StructureModel | null
}
},
created()
{
this.handlerInitialize()
this.watchId()
this.watchChange()
},
data()
{
return {
loading: false,
submitting: false,
dataInfo: null as TableModel | null
}
},
methods: {
handlerInitialize()
{
if (this.info) {
this.dataInfo = cloneDeep(this.info.origin)
const code = this.$route?.params.table as string
if (code) {
this.loading = true
TableService.getByCode(code)
.then(response => {
if (response.status) {
this.dataInfo = response.data
}
else {
ToastUtils.error(response.message)
}
})
.finally(() => this.loading = false)
}
},
handlerApply()
{
if (this.dataInfo) {
this.loading = true
this.submitting = true
const configure: TableFilter = {
type: SqlType.ALTER,
value: this.dataInfo.autoIncrement
}
TableService.getData(toNumber(this.dataInfo.id), configure)
TableService.getData(this.dataInfo.code as string, configure)
.then(response => {
if (response.status) {
ToastUtils.success(this.$t('source.tip.resetAutoIncrementSuccess').replace('$VALUE', this.dataInfo?.autoIncrement as string))
@ -186,16 +191,14 @@ export default defineComponent({
ToastUtils.error(response.message)
}
})
.finally(() => this.loading = false)
.finally(() => this.submitting = false)
}
},
watchId()
watchChange()
{
watch(
() => this.info,
() => {
this.handlerInitialize()
}
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}

View File

@ -5,24 +5,17 @@
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { StructureModel } from '@/model/structure'
import { toNumber } from 'lodash'
import { SqlType, TableFilter, TableFilterRequest } from '@/model/table'
import { SqlType, TableFilter, TableFilterRequest } from '@/model/table.ts'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import AceEditor from '@/views/components/editor/AceEditor.vue'
import TableService from '@/services/table'
import TableService from '@/services/table.ts'
export default defineComponent({
name: 'TableStatement',
name: 'SourceTableStatement',
components: {
AceEditor,
CircularLoading
},
props: {
info: {
type: Object as () => StructureModel | null
}
},
data()
{
return {
@ -34,16 +27,17 @@ export default defineComponent({
created()
{
this.handlerInitialize()
this.watchId()
this.watchChange()
},
methods: {
handlerInitialize()
{
if (this.info) {
const code = this.$route?.params.table as string
if (code) {
this.formState = TableFilterRequest.of()
this.formState.type = SqlType.SHOW
this.loading = true
TableService.getData(toNumber(this.info.applyId), this.formState)
TableService.getData(code, this.formState)
.then(response => {
if (response.status) {
const content = response.data?.columns[0]
@ -58,13 +52,11 @@ export default defineComponent({
.finally(() => this.loading = false)
}
},
watchId()
watchChange()
{
watch(
() => this.info,
() => {
this.handlerInitialize()
}
() => this.$route?.params.table,
() => this.handlerInitialize()
)
}
}

View File

@ -1,34 +1,28 @@
<template>
<TableCommon :loading="loading" :columns="headers" :data="data">
<template #isNullable="{row}">
<Switch :value="row.isNullable" disabled="disabled"/>
</template>
</TableCommon>
<div class="h-full">
<TableCommon :loading="loading" :columns="headers" :data="data">
<template #isNullable="{row}">
<Switch :value="row.isNullable" disabled="disabled"/>
</template>
</TableCommon>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { StructureModel } from '@/model/structure'
import { useI18n } from 'vue-i18n'
import { createHeaders } from '@/views/pages/admin/source/components/TableUtils'
import { createHeaders } from '@/views/pages/admin/source/components/TableUtils.ts'
import TableCommon from '@/views/components/table/TableCommon.vue'
import ColumnService from '@/services/column'
import { cloneDeep, toNumber } from 'lodash'
import { TableModel } from '@/model/table'
import { ColumnModel } from '@/model/column'
import ColumnService from '@/services/column.ts'
import { ColumnModel } from '@/model/column.ts'
import Switch from '@/views/ui/switch'
export default defineComponent({
name: 'TableStructure',
name: 'SourceTableStructure',
components: {
TableCommon,
Switch
},
props: {
info: {
type: Object as () => StructureModel | null
}
},
setup()
{
const headers = createHeaders(useI18n())
@ -41,22 +35,21 @@ export default defineComponent({
{
return {
loading: false,
dataInfo: null as TableModel | null,
data: Array<ColumnModel>
}
},
created()
{
this.handlerInitialize()
this.watchId()
this.watchChange()
},
methods: {
handlerInitialize()
{
if (this.info) {
this.dataInfo = cloneDeep(this.info.origin)
const code = this.$route?.params.table as string
if (code) {
this.loading = true
ColumnService.getAllByTable(toNumber(this.dataInfo?.id))
ColumnService.getAllByTable(code)
.then(response => {
if (response.status) {
this.data = response.data
@ -65,10 +58,10 @@ export default defineComponent({
.finally(() => this.loading = false)
}
},
watchId()
watchChange()
{
watch(
() => this.info,
() => this.$route?.params.table,
() => {
this.handlerInitialize()
}

View File

@ -103,7 +103,6 @@ import { Minus, Plus } from 'lucide-vue-next'
import { Input } from '@/components/ui/input'
import Switch from '@/views/ui/switch'
import { ToastUtils } from '@/utils/toast'
import { toNumber } from 'lodash'
import TableService from '@/services/table'
import ColumnService from '@/services/column'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
@ -160,7 +159,7 @@ export default defineComponent({
{
if (this.info) {
this.loading = true
ColumnService.getById(toNumber(this.info.applyId))
ColumnService.getByCode(this.info.code as string)
.then(response => {
if (response.status) {
const data = response.data
@ -187,11 +186,14 @@ export default defineComponent({
{
if (this.info) {
this.submitting = true
TableService.manageColumn(toNumber(this.info.tableId), this.formState)
TableService.manageColumn(this.info.code as string, this.formState)
.then(response => {
if (response.data) {
if (response.data.isSuccessful) {
const columns = this.formState?.columns?.map(item => item.name).join(', ') as string
const columns = this.formState
?.columns
?.map(item => item.name)
.join(', ') as string
ToastUtils.success(this.$t('source.tip.changeColumnSuccess').replace('$VALUE', columns))
this.handlerCancel()
}

View File

@ -110,7 +110,6 @@ import { Minus, Plus } from 'lucide-vue-next'
import { Input } from '@/components/ui/input'
import Switch from '@/views/ui/switch'
import { ToastUtils } from '@/utils/toast'
import { toNumber } from 'lodash'
import TableService from '@/services/table'
export default defineComponent({
@ -163,7 +162,7 @@ export default defineComponent({
{
if (this.info) {
this.loading = true
TableService.manageColumn(toNumber(this.info.applyId), this.formState)
TableService.manageColumn(this.info.code as string, this.formState)
.then(response => {
if (response.data) {
if (response.data.isSuccessful) {

View File

@ -99,7 +99,7 @@ export default defineComponent({
this.submitting = true
}
this.formState.preview = preview
TableService.getData(toNumber(this.info.tableId), this.formState)
TableService.getData(this.info.tableId as string, this.formState)
.then(response => {
if (response.status) {
if (preview) {

View File

@ -19,7 +19,6 @@
import { defineComponent } from 'vue'
import { SqlColumn, SqlType, TableFilter, TableFilterRequest } from '@/model/table'
import TableService from '@/services/table'
import { toNumber } from 'lodash'
import { ToastUtils } from '@/utils/toast'
import Dialog from '@/views/ui/dialog'
import Button from '@/views/ui/button'
@ -38,9 +37,6 @@ export default defineComponent({
isVisible: {
type: Boolean
},
tableId: {
type: Number
},
columns: {
type: Array<SqlColumn>
},
@ -60,19 +56,24 @@ export default defineComponent({
}
}
},
created()
{
this.handlerInitialize()
},
data()
{
return {
loading: false,
submitting: false,
contentDML: null as string | null,
configure: null as unknown as TableFilter
configure: null as unknown as TableFilter,
code: null as string | null
}
},
created()
{
const code = this.$route?.params.table as string
if (code) {
this.code = code
}
this.handlerInitialize()
},
methods: {
handlerInitialize()
{
@ -86,7 +87,7 @@ export default defineComponent({
}
this.configure.type = this.type
this.configure.preview = true
TableService.putData(toNumber(this.tableId), this.configure)
TableService.putData(this.code as string, this.configure)
.then(response => {
if (response.status && response.data && response.data.isSuccessful) {
this.contentDML = response.data.content
@ -101,7 +102,7 @@ export default defineComponent({
{
this.submitting = false
this.configure.preview = false
TableService.putData(toNumber(this.tableId), this.configure)
TableService.putData(this.code as string, this.configure)
.then(response => {
if (response.status && response.data && response.data.isSuccessful) {
ToastUtils.success(this.$t('source.tip.updateSuccess'))

View File

@ -35,7 +35,6 @@ import { SqlType, TableFilter, TableFilterRequest } from '@/model/table'
import Button from '@/views/ui/button'
import Alert from '@/views/ui/alert'
import Divider from '@/views/ui/divider'
import { toNumber } from 'lodash'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import AceEditor from '@/views/components/editor/AceEditor.vue'
@ -98,7 +97,7 @@ export default defineComponent({
this.submitting = true
}
this.formState.preview = preview
TableService.getData(toNumber(this.info.applyId), this.formState)
TableService.getData(this.info.code as string, this.formState)
.then(response => {
if (response.status) {
if (preview) {

View File

@ -23,7 +23,6 @@ import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import TableService from '@/services/table'
import { SqlColumn, SqlType, TableFilter, TableFilterRequest } from '@/model/table'
import { ToastUtils } from '@/utils/toast'
import { toNumber } from 'lodash'
import Button from '@/views/ui/button'
export default defineComponent({
@ -38,9 +37,6 @@ export default defineComponent({
isVisible: {
type: Boolean
},
tableId: {
type: Number
},
columns: {
type: Array<SqlColumn>
}
@ -57,19 +53,24 @@ export default defineComponent({
}
}
},
created()
{
this.handlerInitialize()
},
data()
{
return {
loading: false,
submitting: false,
contentDML: null as string | null,
configure: null as unknown as TableFilter
configure: null as unknown as TableFilter,
code: null as string | null
}
},
created()
{
const code = this.$route?.params.table as string
if (code) {
this.code = code
}
this.handlerInitialize()
},
methods: {
handlerInitialize()
{
@ -80,7 +81,7 @@ export default defineComponent({
this.configure.columns = originalColumns
this.configure.type = SqlType.DELETE
this.configure.preview = true
TableService.putData(toNumber(this.tableId), this.configure)
TableService.putData(this.code as string, this.configure)
.then(response => {
if (response.status && response.data && response.data.isSuccessful) {
this.contentDML = response.data.content
@ -95,7 +96,7 @@ export default defineComponent({
{
this.submitting = false
this.configure.preview = false
TableService.putData(toNumber(this.tableId), this.configure)
TableService.putData(this.code as string, this.configure)
.then(response => {
if (response.status && response.data && response.data.isSuccessful) {
ToastUtils.success(this.$t('source.tip.deleteSuccess'))

View File

@ -35,7 +35,6 @@ import { SqlType, TableFilter, TableFilterRequest } from '@/model/table'
import Button from '@/views/ui/button'
import Alert from '@/views/ui/alert'
import Divider from '@/views/ui/divider'
import { toNumber } from 'lodash'
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
import AceEditor from '@/views/components/editor/AceEditor.vue'
@ -98,7 +97,7 @@ export default defineComponent({
this.submitting = true
}
this.formState.preview = preview
TableService.getData(toNumber(this.info.applyId), this.formState)
TableService.getData(this.info.code as string, this.formState)
.then(response => {
if (response.status) {
if (preview) {

View File

@ -14,7 +14,7 @@
<CardContent :class="`${bodyClass}`">
<slot/>
</CardContent>
<CardFooter v-if="$slots.footer" :class="`border-t ${footerClass}`">
<CardFooter v-if="$slots.footer">
<slot name="footer"/>
</CardFooter>
</Card>
@ -39,9 +39,6 @@ export default defineComponent({
bodyClass: {
type: String
},
footerClass: {
type: String
},
hiddenTitle: {
type: Boolean,
default: false