[Core] Support some fc (#732)
@ -53,6 +53,12 @@ public class SourceV2Controller
|
||||
return this.sourceService.getByIdV2(id);
|
||||
}
|
||||
|
||||
@GetMapping(value = "code/{code}")
|
||||
public CommonResponse<SourceEntity> getByCode(@PathVariable(value = "code") String code)
|
||||
{
|
||||
return this.sourceService.getByCode(code);
|
||||
}
|
||||
|
||||
@PostMapping(value = "getHistory/{id}")
|
||||
public CommonResponse<PageEntity<ScheduledHistoryEntity>> getHistory(@PathVariable(value = "id") Long id,
|
||||
@RequestBody FilterBody filter)
|
||||
|
@ -16,3 +16,10 @@ WHERE `id` = 16;
|
||||
UPDATE `menus`
|
||||
SET `url` = '/admin/query'
|
||||
WHERE `id` = 2;
|
||||
|
||||
ALTER TABLE `datacap_source`
|
||||
ADD COLUMN `code` VARCHAR(100);
|
||||
|
||||
UPDATE `datacap_source`
|
||||
SET `code` = REPLACE(UUID(), '-', '')
|
||||
WHERE `code` IS NULL;
|
||||
|
@ -122,6 +122,9 @@ public class SourceEntity
|
||||
@Column(name = "message")
|
||||
private String message;
|
||||
|
||||
@Column(name = "code")
|
||||
private String code;
|
||||
|
||||
@Column(name = "create_time")
|
||||
@CreatedDate
|
||||
private Timestamp createTime;
|
||||
|
@ -6,5 +6,6 @@ public enum QueryMode
|
||||
HISTORY,
|
||||
REPORT,
|
||||
SNIPPET,
|
||||
SYNC
|
||||
SYNC,
|
||||
DATASET
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SourceRepository
|
||||
extends PagingAndSortingRepository<SourceEntity, Long>
|
||||
@ -15,6 +16,8 @@ public interface SourceRepository
|
||||
|
||||
SourceEntity findByName(String name);
|
||||
|
||||
Optional<SourceEntity> findByCode(String code);
|
||||
|
||||
Page<SourceEntity> findAllByUserOrPublishIsTrue(UserEntity user, Pageable pageable);
|
||||
|
||||
Long countByUserOrPublishIsTrue(UserEntity user);
|
||||
|
@ -26,6 +26,8 @@ public interface SourceService
|
||||
|
||||
CommonResponse<SourceEntity> getById(Long id);
|
||||
|
||||
CommonResponse<SourceEntity> getByCode(String code);
|
||||
|
||||
CommonResponse<Map<String, List<PluginEntity>>> getPlugins();
|
||||
|
||||
CommonResponse<Long> count();
|
||||
|
@ -35,4 +35,11 @@ public class ChatServiceImpl
|
||||
Pageable pageable = PageRequestAdapter.of(filter);
|
||||
return CommonResponse.success(PageEntity.build(repository.findAllByUser(UserDetailsService.getUser(), pageable)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResponse<ChatEntity> saveOrUpdate(PagingAndSortingRepository repository, ChatEntity configure)
|
||||
{
|
||||
configure.setUser(UserDetailsService.getUser());
|
||||
return CommonResponse.success(repository.save(configure));
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package io.edurt.datacap.service.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import io.edurt.datacap.common.enums.ServiceState;
|
||||
import io.edurt.datacap.common.response.CommonResponse;
|
||||
import io.edurt.datacap.common.utils.AiSupportUtils;
|
||||
import io.edurt.datacap.common.utils.JsonUtils;
|
||||
@ -34,7 +33,6 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -60,16 +58,12 @@ public class MessageServiceImpl
|
||||
@Override
|
||||
public CommonResponse<MessageEntity> saveOrUpdate(PagingAndSortingRepository repository, MessageEntity configure)
|
||||
{
|
||||
Optional<UserEntity> userOptional = this.userRepository.findById(configure.getUser().getId());
|
||||
if (!userOptional.isPresent()) {
|
||||
return CommonResponse.failure(ServiceState.USER_NOT_FOUND);
|
||||
}
|
||||
String openApiHost = environment.getProperty("datacap.openai.backend");
|
||||
String openApiToken = environment.getProperty("datacap.openai.token");
|
||||
String openApiModel = environment.getProperty("datacap.openai.model");
|
||||
long openApiTimeout = Long.parseLong(environment.getProperty("datacap.openai.timeout"));
|
||||
|
||||
UserEntity user = userOptional.get();
|
||||
UserEntity user = UserDetailsService.getUser();
|
||||
MessageEntity questionMessage = MessageEntity.builder()
|
||||
.user(user)
|
||||
.chat(configure.getChat())
|
||||
|
@ -14,12 +14,15 @@ import io.edurt.datacap.executor.common.RunWay;
|
||||
import io.edurt.datacap.executor.configure.ExecutorConfigure;
|
||||
import io.edurt.datacap.executor.configure.ExecutorRequest;
|
||||
import io.edurt.datacap.executor.configure.ExecutorResponse;
|
||||
import io.edurt.datacap.service.adapter.PageRequestAdapter;
|
||||
import io.edurt.datacap.service.body.FilterBody;
|
||||
import io.edurt.datacap.service.body.PipelineBody;
|
||||
import io.edurt.datacap.service.common.ConfigureUtils;
|
||||
import io.edurt.datacap.service.common.FolderUtils;
|
||||
import io.edurt.datacap.service.configure.FieldType;
|
||||
import io.edurt.datacap.service.configure.IConfigureExecutorField;
|
||||
import io.edurt.datacap.service.configure.IConfigurePipelineType;
|
||||
import io.edurt.datacap.service.entity.PageEntity;
|
||||
import io.edurt.datacap.service.entity.PipelineEntity;
|
||||
import io.edurt.datacap.service.entity.SourceEntity;
|
||||
import io.edurt.datacap.service.initializer.InitializerConfigure;
|
||||
@ -68,6 +71,12 @@ public class PipelineServiceImpl
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResponse<PageEntity> getAll(PagingAndSortingRepository repository1, FilterBody filter)
|
||||
{
|
||||
return CommonResponse.success(repository.findAllByUser(UserDetailsService.getUser(), PageRequestAdapter.of(filter)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResponse<Object> submit(PipelineBody configure)
|
||||
{
|
||||
|
@ -65,6 +65,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -107,6 +108,7 @@ public class SourceServiceImpl
|
||||
{
|
||||
configure.setConfigure(JsonUtils.toJSON(configure.getConfigures()));
|
||||
configure.setUser(UserDetailsService.getUser());
|
||||
configure.setCode(UUID.randomUUID().toString().replace("-", ""));
|
||||
return CommonResponse.success(this.sourceRepository.save(configure));
|
||||
}
|
||||
|
||||
@ -117,14 +119,12 @@ public class SourceServiceImpl
|
||||
UserEntity user = UserDetailsService.getUser();
|
||||
Page<SourceEntity> page = this.sourceRepository.findAllByUserOrPublishIsTrue(user, pageable);
|
||||
// Populate pipeline configuration information
|
||||
page.getContent()
|
||||
.stream()
|
||||
.forEach(item -> {
|
||||
IConfigure fromConfigure = PluginUtils.loadYamlConfigure(item.getProtocol(), item.getType(), item.getType(), environment);
|
||||
if (fromConfigure != null) {
|
||||
item.setPipelines(fromConfigure.getPipelines());
|
||||
}
|
||||
});
|
||||
page.getContent().stream().forEach(item -> {
|
||||
IConfigure fromConfigure = PluginUtils.loadYamlConfigure(item.getProtocol(), item.getType(), item.getType(), environment);
|
||||
if (fromConfigure != null) {
|
||||
item.setPipelines(fromConfigure.getPipelines());
|
||||
}
|
||||
});
|
||||
return CommonResponse.success(PageEntity.build(page));
|
||||
}
|
||||
|
||||
@ -183,6 +183,14 @@ public class SourceServiceImpl
|
||||
return CommonResponse.success(this.sourceRepository.findById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResponse<SourceEntity> getByCode(String code)
|
||||
{
|
||||
return this.sourceRepository.findByCode(code)
|
||||
.map(item -> CommonResponse.success(item))
|
||||
.orElseGet(() -> CommonResponse.failure(String.format("Source [ %s ] not found", code)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResponse<Map<String, List<PluginEntity>>> getPlugins()
|
||||
{
|
||||
@ -361,22 +369,17 @@ public class SourceServiceImpl
|
||||
public CommonResponse<PageEntity<ScheduledHistoryEntity>> getHistory(Long id, FilterBody filter)
|
||||
{
|
||||
Pageable pageable = PageRequestAdapter.of(filter);
|
||||
SourceEntity entity = SourceEntity.builder()
|
||||
.id(id)
|
||||
.build();
|
||||
SourceEntity entity = SourceEntity.builder().id(id).build();
|
||||
return CommonResponse.success(PageEntity.build(this.scheduledHistoryRepository.findAllBySource(entity, pageable)));
|
||||
}
|
||||
|
||||
@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)
|
||||
@ -394,12 +397,7 @@ public class SourceServiceImpl
|
||||
Map<String, List<TableEntity>> databaseTableCache = Maps.newHashMap();
|
||||
Map<String, TableEntity> tableCache = Maps.newHashMap();
|
||||
Map<String, List<ColumnEntity>> tableColumnCache = Maps.newHashMap();
|
||||
ScheduledHistoryEntity scheduledHistory = ScheduledHistoryEntity.builder()
|
||||
.name(String.format("Sync source [ %s ]", entity.getName()))
|
||||
.scheduled(scheduled)
|
||||
.source(entity)
|
||||
.state(RunState.RUNNING)
|
||||
.build();
|
||||
ScheduledHistoryEntity scheduledHistory = ScheduledHistoryEntity.builder().name(String.format("Sync source [ %s ]", entity.getName())).scheduled(scheduled).source(entity).state(RunState.RUNNING).build();
|
||||
scheduledHistoryHandler.save(scheduledHistory);
|
||||
log.info("==================== Sync metadata [ {} ] started =================", entity.getName());
|
||||
Optional<Plugin> pluginOptional = PluginUtils.getPluginByNameAndType(this.injector, entity.getType(), entity.getProtocol());
|
||||
@ -415,21 +413,7 @@ public class SourceServiceImpl
|
||||
log.error("The source [ {} ] not available", entity.getName());
|
||||
}
|
||||
else {
|
||||
this.startSyncDatabase(entity,
|
||||
plugin,
|
||||
databaseCache,
|
||||
databaseTableCache,
|
||||
tableCache,
|
||||
tableColumnCache,
|
||||
databaseAddedCount,
|
||||
databaseUpdatedCount,
|
||||
databaseRemovedCount,
|
||||
tableAddedCount,
|
||||
tableUpdatedCount,
|
||||
tableRemovedCount,
|
||||
columnAddedCount,
|
||||
columnUpdatedCount,
|
||||
columnRemovedCount);
|
||||
this.startSyncDatabase(entity, plugin, databaseCache, databaseTableCache, tableCache, tableColumnCache, databaseAddedCount, databaseUpdatedCount, databaseRemovedCount, tableAddedCount, tableUpdatedCount, tableRemovedCount, columnAddedCount, columnUpdatedCount, columnRemovedCount);
|
||||
}
|
||||
scheduledHistory.setState(RunState.SUCCESS);
|
||||
}
|
||||
@ -471,22 +455,18 @@ public class SourceServiceImpl
|
||||
if (ObjectUtils.isNotEmpty(entity.getConfigure())) {
|
||||
final String[] content = {entity.getContent()};
|
||||
List<LinkedHashMap> configures = JsonUtils.objectmapper.readValue(entity.getConfigure(), List.class);
|
||||
map.entrySet()
|
||||
.forEach(value -> {
|
||||
Optional<SqlConfigure> sqlConfigure = configures.stream()
|
||||
.filter(v -> String.valueOf(v.get("column")).equalsIgnoreCase(value.getKey()))
|
||||
.map(v -> {
|
||||
SqlConfigure configure = new SqlConfigure();
|
||||
configure.setColumn(String.valueOf(v.get("column")));
|
||||
configure.setType(Type.valueOf(String.valueOf(v.get("type"))));
|
||||
configure.setExpression(String.valueOf(v.get("expression")));
|
||||
return configure;
|
||||
})
|
||||
.findFirst();
|
||||
if (sqlConfigure.isPresent()) {
|
||||
content[0] = content[0].replace(sqlConfigure.get().getExpression(), String.valueOf(value.getValue()));
|
||||
}
|
||||
});
|
||||
map.entrySet().forEach(value -> {
|
||||
Optional<SqlConfigure> sqlConfigure = configures.stream().filter(v -> String.valueOf(v.get("column")).equalsIgnoreCase(value.getKey())).map(v -> {
|
||||
SqlConfigure configure = new SqlConfigure();
|
||||
configure.setColumn(String.valueOf(v.get("column")));
|
||||
configure.setType(Type.valueOf(String.valueOf(v.get("type"))));
|
||||
configure.setExpression(String.valueOf(v.get("expression")));
|
||||
return configure;
|
||||
}).findFirst();
|
||||
if (sqlConfigure.isPresent()) {
|
||||
content[0] = content[0].replace(sqlConfigure.get().getExpression(), String.valueOf(value.getValue()));
|
||||
}
|
||||
});
|
||||
return content[0];
|
||||
}
|
||||
}
|
||||
@ -547,21 +527,7 @@ public class SourceServiceImpl
|
||||
* @param columnUpdatedCount the AtomicInteger object representing the count of updated columns
|
||||
* @param columnRemovedCount the AtomicInteger object representing the count of removed columns
|
||||
*/
|
||||
private void startSyncDatabase(SourceEntity entity,
|
||||
Plugin plugin,
|
||||
Map<String, DatabaseEntity> databaseCache,
|
||||
Map<String, List<TableEntity>> databaseTableCache,
|
||||
Map<String, TableEntity> tableCache,
|
||||
Map<String, List<ColumnEntity>> tableColumnCache,
|
||||
AtomicInteger databaseAddedCount,
|
||||
AtomicInteger databaseUpdatedCount,
|
||||
AtomicInteger databaseRemovedCount,
|
||||
AtomicInteger tableAddedCount,
|
||||
AtomicInteger tableUpdatedCount,
|
||||
AtomicInteger tableRemovedCount,
|
||||
AtomicInteger columnAddedCount,
|
||||
AtomicInteger columnUpdatedCount,
|
||||
AtomicInteger columnRemovedCount)
|
||||
private void startSyncDatabase(SourceEntity entity, Plugin plugin, Map<String, DatabaseEntity> databaseCache, Map<String, List<TableEntity>> databaseTableCache, Map<String, TableEntity> tableCache, Map<String, List<ColumnEntity>> tableColumnCache, AtomicInteger databaseAddedCount, AtomicInteger databaseUpdatedCount, AtomicInteger databaseRemovedCount, AtomicInteger tableAddedCount, AtomicInteger tableUpdatedCount, AtomicInteger tableRemovedCount, AtomicInteger columnAddedCount, AtomicInteger columnUpdatedCount, AtomicInteger columnRemovedCount)
|
||||
{
|
||||
String templateName = "SYSTEM_FOR_GET_ALL_DATABASES";
|
||||
TemplateSqlEntity template = getTemplate(templateName, entity);
|
||||
@ -576,29 +542,19 @@ 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());
|
||||
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);
|
||||
@ -608,25 +564,12 @@ 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());
|
||||
}
|
||||
this.startSyncTable(entity,
|
||||
plugin,
|
||||
databaseCache,
|
||||
databaseTableCache,
|
||||
tableCache,
|
||||
tableColumnCache,
|
||||
tableAddedCount,
|
||||
tableUpdatedCount,
|
||||
tableRemovedCount,
|
||||
columnAddedCount,
|
||||
columnUpdatedCount,
|
||||
columnRemovedCount);
|
||||
this.startSyncTable(entity, plugin, databaseCache, databaseTableCache, tableCache, tableColumnCache, tableAddedCount, tableUpdatedCount, tableRemovedCount, columnAddedCount, columnUpdatedCount, columnRemovedCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,18 +589,7 @@ public class SourceServiceImpl
|
||||
* @param columnUpdatedCount the column updated count
|
||||
* @param columnRemovedCount the column removed count
|
||||
*/
|
||||
private void startSyncTable(SourceEntity entity,
|
||||
Plugin plugin,
|
||||
Map<String, DatabaseEntity> databaseCache,
|
||||
Map<String, List<TableEntity>> databaseTableCache,
|
||||
Map<String, TableEntity> tableCache,
|
||||
Map<String, List<ColumnEntity>> tableColumnCache,
|
||||
AtomicInteger tableAddedCount,
|
||||
AtomicInteger tableUpdatedCount,
|
||||
AtomicInteger tableRemovedCount,
|
||||
AtomicInteger columnAddedCount,
|
||||
AtomicInteger columnUpdatedCount,
|
||||
AtomicInteger columnRemovedCount)
|
||||
private void startSyncTable(SourceEntity entity, Plugin plugin, Map<String, DatabaseEntity> databaseCache, Map<String, List<TableEntity>> databaseTableCache, Map<String, TableEntity> tableCache, Map<String, List<ColumnEntity>> tableColumnCache, AtomicInteger tableAddedCount, AtomicInteger tableUpdatedCount, AtomicInteger tableRemovedCount, AtomicInteger columnAddedCount, AtomicInteger columnUpdatedCount, AtomicInteger columnRemovedCount)
|
||||
{
|
||||
String templateName = "SYSTEM_FOR_GET_ALL_TABLES";
|
||||
TemplateSqlEntity template = getTemplate(templateName, entity);
|
||||
@ -671,43 +603,20 @@ 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);
|
||||
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());
|
||||
|
||||
Map<String, List<TableEntity>> groupEntities = entities
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getDatabase().getCatalog(), item.getDatabase().getName())));
|
||||
Map<String, List<TableEntity>> groupEntities = entities.stream().collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getDatabase().getCatalog(), item.getDatabase().getName())));
|
||||
|
||||
groupEntities.forEach((key, groupItem) -> {
|
||||
// Detect data that needs to be updated
|
||||
List<TableEntity> origin = databaseTableCache.get(key);
|
||||
groupItem.forEach(item -> {
|
||||
Optional<TableEntity> optionalTable = origin.stream()
|
||||
.filter(node -> node.getName().equals(item.getName()))
|
||||
.findAny();
|
||||
Optional<TableEntity> optionalTable = origin.stream().filter(node -> node.getName().equals(item.getName())).findAny();
|
||||
if (optionalTable.isPresent()) {
|
||||
TableEntity node = optionalTable.get();
|
||||
item.setId(node.getId());
|
||||
@ -727,9 +636,7 @@ public class SourceServiceImpl
|
||||
tableColumnCache.put(tableCacheKey, this.columnHandler.findSimpleAllByTable(item));
|
||||
});
|
||||
|
||||
List<TableEntity> deleteEntities = origin.stream()
|
||||
.filter(node -> groupItem.stream().noneMatch(item -> node.getName().equals(item.getName())))
|
||||
.collect(Collectors.toList());
|
||||
List<TableEntity> deleteEntities = origin.stream().filter(node -> groupItem.stream().noneMatch(item -> node.getName().equals(item.getName()))).collect(Collectors.toList());
|
||||
log.info("Removed table size [ {} ] from database [ {} ]", deleteEntities.size(), key);
|
||||
tableHandler.deleteAll(deleteEntities);
|
||||
tableRemovedCount.addAndGet(deleteEntities.size());
|
||||
@ -751,13 +658,7 @@ public class SourceServiceImpl
|
||||
* @param columnUpdatedCount an atomic counter for tracking the number of columns updated
|
||||
* @param columnRemovedCount an atomic counter for tracking the number of columns removed
|
||||
*/
|
||||
private void startSyncColumn(SourceEntity entity,
|
||||
Plugin plugin,
|
||||
Map<String, TableEntity> tableCache,
|
||||
Map<String, List<ColumnEntity>> tableColumnCache,
|
||||
AtomicInteger columnAddedCount,
|
||||
AtomicInteger columnUpdatedCount,
|
||||
AtomicInteger columnRemovedCount)
|
||||
private void startSyncColumn(SourceEntity entity, Plugin plugin, Map<String, TableEntity> tableCache, Map<String, List<ColumnEntity>> tableColumnCache, AtomicInteger columnAddedCount, AtomicInteger columnUpdatedCount, AtomicInteger columnRemovedCount)
|
||||
{
|
||||
String templateName = "SYSTEM_FOR_GET_ALL_COLUMNS";
|
||||
TemplateSqlEntity template = getTemplate(templateName, entity);
|
||||
@ -771,43 +672,21 @@ 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 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());
|
||||
|
||||
Map<String, List<ColumnEntity>> groupEntities = entities
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getTable().getDatabase().getName(), item.getTable().getName())));
|
||||
Map<String, List<ColumnEntity>> groupEntities = entities.stream().collect(Collectors.groupingBy(item -> String.format("%s_%s", item.getTable().getDatabase().getName(), item.getTable().getName())));
|
||||
|
||||
groupEntities.forEach((key, groupItem) -> {
|
||||
// Detect data that needs to be updated
|
||||
List<ColumnEntity> origin = tableColumnCache.get(key);
|
||||
groupItem.forEach(item -> {
|
||||
Optional<ColumnEntity> optionalColumn = origin.stream()
|
||||
.filter(node -> node.getName().equals(item.getName()))
|
||||
.findAny();
|
||||
Optional<ColumnEntity> optionalColumn = origin.stream().filter(node -> node.getName().equals(item.getName())).findAny();
|
||||
if (optionalColumn.isPresent()) {
|
||||
ColumnEntity node = optionalColumn.get();
|
||||
item.setId(node.getId());
|
||||
@ -822,9 +701,7 @@ public class SourceServiceImpl
|
||||
log.info("Added column size [ {} ] to table [ {} ]", groupItem.size(), key);
|
||||
columnHandler.saveAll(groupItem);
|
||||
|
||||
List<ColumnEntity> deleteEntities = origin.stream()
|
||||
.filter(node -> groupItem.stream().noneMatch(item -> node.getName().equals(item.getName())))
|
||||
.collect(Collectors.toList());
|
||||
List<ColumnEntity> deleteEntities = origin.stream().filter(node -> groupItem.stream().noneMatch(item -> node.getName().equals(item.getName()))).collect(Collectors.toList());
|
||||
log.info("Removed column size [ {} ] from table [ {} ]", deleteEntities.size(), key);
|
||||
columnHandler.deleteAll(deleteEntities);
|
||||
columnRemovedCount.addAndGet(deleteEntities.size());
|
||||
|
@ -31,6 +31,9 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"echarts": "^5.5.0",
|
||||
"embla-carousel": "^8.0.2",
|
||||
"embla-carousel-autoplay": "^8.0.2",
|
||||
"embla-carousel-vue": "^8.0.2",
|
||||
"export-to-csv": "^1.2.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-vue-next": "^0.356.0",
|
||||
|
44
core/datacap-ui/src/components/ui/carousel/Carousel.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { useProvideCarousel } from './useCarousel'
|
||||
import type { CarouselEmits, CarouselProps, WithClassAsProps } from './interface'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<CarouselProps & WithClassAsProps>(), {
|
||||
orientation: 'horizontal',
|
||||
})
|
||||
|
||||
const emits = defineEmits<CarouselEmits>()
|
||||
|
||||
const carouselArgs = useProvideCarousel(props, emits)
|
||||
|
||||
defineExpose(carouselArgs)
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
const prevKey = props.orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft'
|
||||
const nextKey = props.orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight'
|
||||
|
||||
if (event.key === prevKey) {
|
||||
event.preventDefault()
|
||||
carouselArgs.scrollPrev()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === nextKey) {
|
||||
event.preventDefault()
|
||||
carouselArgs.scrollNext()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="cn('relative', props.class)"
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
tabindex="0"
|
||||
@keydown="onKeyDown"
|
||||
>
|
||||
<slot v-bind="carouselArgs" />
|
||||
</div>
|
||||
</template>
|
@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { useCarousel } from './useCarousel'
|
||||
import type { WithClassAsProps } from './interface'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps<WithClassAsProps>()
|
||||
|
||||
const { carouselRef, orientation } = useCarousel()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="carouselRef" class="overflow-hidden">
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex',
|
||||
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
|
||||
props.class,
|
||||
)"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
23
core/datacap-ui/src/components/ui/carousel/CarouselItem.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { useCarousel } from './useCarousel'
|
||||
import type { WithClassAsProps } from './interface'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<WithClassAsProps>()
|
||||
|
||||
const { orientation } = useCarousel()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
:class="cn(
|
||||
'min-w-0 shrink-0 grow-0 basis-full',
|
||||
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
30
core/datacap-ui/src/components/ui/carousel/CarouselNext.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowRightIcon } from '@radix-icons/vue'
|
||||
import { useCarousel } from './useCarousel'
|
||||
import type { WithClassAsProps } from './interface'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<WithClassAsProps>()
|
||||
|
||||
const { orientation, canScrollNext, scrollNext } = useCarousel()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:disabled="!canScrollNext"
|
||||
:class="cn(
|
||||
'touch-manipulation absolute h-8 w-8 rounded-full p-0',
|
||||
orientation === 'horizontal'
|
||||
? '-right-12 top-1/2 -translate-y-1/2'
|
||||
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||
props.class,
|
||||
)"
|
||||
variant="outline"
|
||||
@click="scrollNext"
|
||||
>
|
||||
<slot>
|
||||
<ArrowRightIcon class="h-4 w-4 text-current" />
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeftIcon } from '@radix-icons/vue'
|
||||
import { useCarousel } from './useCarousel'
|
||||
import type { WithClassAsProps } from './interface'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<WithClassAsProps>()
|
||||
|
||||
const { orientation, canScrollPrev, scrollPrev } = useCarousel()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
:disabled="!canScrollPrev"
|
||||
:class="cn(
|
||||
'touch-manipulation absolute h-8 w-8 rounded-full p-0',
|
||||
orientation === 'horizontal'
|
||||
? '-left-12 top-1/2 -translate-y-1/2'
|
||||
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
|
||||
props.class,
|
||||
)"
|
||||
variant="outline"
|
||||
@click="scrollPrev"
|
||||
>
|
||||
<slot>
|
||||
<ArrowLeftIcon class="h-4 w-4 text-current" />
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
10
core/datacap-ui/src/components/ui/carousel/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export { default as Carousel } from './Carousel.vue'
|
||||
export { default as CarouselContent } from './CarouselContent.vue'
|
||||
export { default as CarouselItem } from './CarouselItem.vue'
|
||||
export { default as CarouselPrevious } from './CarouselPrevious.vue'
|
||||
export { default as CarouselNext } from './CarouselNext.vue'
|
||||
export { useCarousel } from './useCarousel'
|
||||
|
||||
export type {
|
||||
EmblaCarouselType as CarouselApi,
|
||||
} from 'embla-carousel'
|
20
core/datacap-ui/src/components/ui/carousel/interface.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type {
|
||||
EmblaCarouselType as CarouselApi,
|
||||
EmblaOptionsType as CarouselOptions,
|
||||
EmblaPluginType as CarouselPlugin,
|
||||
} from 'embla-carousel'
|
||||
import type { HTMLAttributes, Ref } from 'vue'
|
||||
|
||||
export interface CarouselProps {
|
||||
opts?: CarouselOptions | Ref<CarouselOptions>
|
||||
plugins?: CarouselPlugin[] | Ref<CarouselPlugin[]>
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
}
|
||||
|
||||
export interface CarouselEmits {
|
||||
(e: 'init-api', payload: CarouselApi): void
|
||||
}
|
||||
|
||||
export interface WithClassAsProps {
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
59
core/datacap-ui/src/components/ui/carousel/useCarousel.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { createInjectionState } from '@vueuse/core'
|
||||
import emblaCarouselVue from 'embla-carousel-vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type {
|
||||
EmblaCarouselType as CarouselApi,
|
||||
} from 'embla-carousel'
|
||||
import type { CarouselEmits, CarouselProps } from './interface'
|
||||
|
||||
const [useProvideCarousel, useInjectCarousel] = createInjectionState(
|
||||
({
|
||||
opts,
|
||||
orientation,
|
||||
plugins,
|
||||
}: CarouselProps, emits: CarouselEmits) => {
|
||||
const [emblaNode, emblaApi] = emblaCarouselVue({
|
||||
...opts,
|
||||
axis: orientation === 'horizontal' ? 'x' : 'y',
|
||||
}, plugins)
|
||||
|
||||
function scrollPrev() {
|
||||
emblaApi.value?.scrollPrev()
|
||||
}
|
||||
function scrollNext() {
|
||||
emblaApi.value?.scrollNext()
|
||||
}
|
||||
|
||||
const canScrollNext = ref(true)
|
||||
const canScrollPrev = ref(true)
|
||||
|
||||
function onSelect(api: CarouselApi) {
|
||||
canScrollNext.value = api.canScrollNext()
|
||||
canScrollPrev.value = api.canScrollPrev()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!emblaApi.value)
|
||||
return
|
||||
|
||||
emblaApi.value?.on('init', onSelect)
|
||||
emblaApi.value?.on('reInit', onSelect)
|
||||
emblaApi.value?.on('select', onSelect)
|
||||
|
||||
emits('init-api', emblaApi.value)
|
||||
})
|
||||
|
||||
return { carouselRef: emblaNode, carouselApi: emblaApi, canScrollPrev, canScrollNext, scrollPrev, scrollNext, orientation }
|
||||
},
|
||||
)
|
||||
|
||||
function useCarousel() {
|
||||
const carouselState = useInjectCarousel()
|
||||
|
||||
if (!carouselState)
|
||||
throw new Error('useCarousel must be used within a <Carousel />')
|
||||
|
||||
return carouselState
|
||||
}
|
||||
|
||||
export { useCarousel, useProvideCarousel }
|
@ -90,6 +90,8 @@ export default {
|
||||
realtime: 'Realtime',
|
||||
to: 'To',
|
||||
work: 'Work Home',
|
||||
chat: 'Chat',
|
||||
avatar: 'Avatar',
|
||||
tip: {
|
||||
pageNotNetwork: 'Oops! Unable to connect to the network, please check if the network is normal!'
|
||||
}
|
||||
|
@ -106,6 +106,12 @@ export default {
|
||||
info: 'View Info',
|
||||
lifeCycleColumn: 'Lifecycle columns',
|
||||
lifeCycleNumber: 'Lifecycle number',
|
||||
continuousBuild: 'Continuous Build',
|
||||
},
|
||||
validator: {
|
||||
duplicateColumn: 'Column name [ $VALUE ] already exists',
|
||||
specifiedColumn: 'Sort key or primary key must be specified',
|
||||
specifiedName: 'Name must be specified',
|
||||
},
|
||||
tip: {
|
||||
selectExpression: 'Please select the expression',
|
||||
@ -117,5 +123,6 @@ export default {
|
||||
rebuildProgress: 'Rebuilding will only progress unfinished',
|
||||
lifeCycleMustDateColumn: 'The lifecycle must contain a date column',
|
||||
modifyNotSupportDataPreview: 'Data preview is not supported to modify',
|
||||
publishSuccess: 'Dataset [ $VALUE ] published successfully',
|
||||
}
|
||||
}
|
@ -11,5 +11,6 @@ export default {
|
||||
deleteAlert1: 'You are deleting a report. This action permanently deletes the report. Please be sure to confirm your actions before proceeding.',
|
||||
deleteAlert2: 'Warning: This cannot be undone.',
|
||||
deleteAlert3: 'To confirm, type [ $VALUE ] in the box below',
|
||||
publishSuccess: 'Report [ $VALUE ] published successfully',
|
||||
}
|
||||
}
|
@ -90,6 +90,8 @@ export default {
|
||||
realtime: '实时',
|
||||
to: '目标',
|
||||
work: '工作目录',
|
||||
chat: '聊天室',
|
||||
avatar: '头像',
|
||||
tip: {
|
||||
pageNotNetwork: '哎呀!无法连接到网络,请检查网络是否正常!'
|
||||
}
|
||||
|
@ -106,6 +106,12 @@ export default {
|
||||
info: '查看详情',
|
||||
lifeCycleColumn: '生命周期列',
|
||||
lifeCycleNumber: '生命周期数',
|
||||
continuousBuild: '连续构建',
|
||||
},
|
||||
validator: {
|
||||
duplicateColumn: '列名 [ $VALUE ] 已存在',
|
||||
specifiedColumn: '排序键或主键必须指定',
|
||||
specifiedName: '数据集名必须指定',
|
||||
},
|
||||
tip: {
|
||||
selectExpression: '请选择表达式',
|
||||
@ -117,5 +123,6 @@ export default {
|
||||
rebuildProgress: '重建只会进行未完成进度',
|
||||
lifeCycleMustDateColumn: '生命周期必须包含一个日期列',
|
||||
modifyNotSupportDataPreview: '修改暂不支持数据预览',
|
||||
publishSuccess: '数据集 [ $VALUE ] 发布成功',
|
||||
}
|
||||
}
|
@ -11,5 +11,6 @@ export default {
|
||||
deleteAlert1: '您正在删除报表。此操作将永久删除报表。在继续操作之前,请务必确认您的操作。',
|
||||
deleteAlert2: '警告:此操作无法撤销。',
|
||||
deleteAlert3: '要确认,请在下面的框中键入 [ $VALUE ]',
|
||||
publishSuccess: '报表 [ $VALUE ] 发布成功',
|
||||
}
|
||||
}
|
21
core/datacap-ui/src/model/chat.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { BaseModel } from '@/model/base.ts'
|
||||
import { UserModel } from '@/model/user.ts'
|
||||
|
||||
export interface ChatModel
|
||||
extends BaseModel
|
||||
{
|
||||
avatar?: string
|
||||
description?: string
|
||||
user?: UserModel
|
||||
}
|
||||
|
||||
export class ChatRequest
|
||||
{
|
||||
public static of(): ChatModel
|
||||
{
|
||||
return {
|
||||
avatar: undefined,
|
||||
description: undefined
|
||||
}
|
||||
}
|
||||
}
|
12
core/datacap-ui/src/model/message.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { BaseModel } from '@/model/base.ts'
|
||||
|
||||
export interface MessageModel
|
||||
extends BaseModel
|
||||
{
|
||||
content?: string
|
||||
model?: string
|
||||
type?: string
|
||||
promptTokens?: number
|
||||
completionTokens?: number
|
||||
totalTokens?: number
|
||||
}
|
@ -22,6 +22,7 @@ export interface SourceModel
|
||||
updateTime?: string
|
||||
configures?: Map<string, string>
|
||||
schema?: any
|
||||
code?: string
|
||||
}
|
||||
|
||||
export class SourceRequest
|
||||
|
@ -171,6 +171,14 @@ const createAdminRouter = (router: any) => {
|
||||
},
|
||||
component: () => import('@/views/pages/admin/dataset/DatasetInfo.vue')
|
||||
},
|
||||
{
|
||||
path: 'dataset/info/source/:sourceCode?',
|
||||
meta: {
|
||||
title: 'common.dataset',
|
||||
isRoot: false
|
||||
},
|
||||
component: () => import('@/views/pages/admin/dataset/DatasetInfo.vue')
|
||||
},
|
||||
{
|
||||
path: 'dataset/adhoc/:code',
|
||||
layout: LayoutContainer,
|
||||
@ -244,6 +252,14 @@ const createAdminRouter = (router: any) => {
|
||||
isRoot: false
|
||||
},
|
||||
component: () => import('@/views/pages/admin/pipeline/PipelineInfo.vue')
|
||||
},
|
||||
{
|
||||
path: 'chat',
|
||||
meta: {
|
||||
title: 'common.chat',
|
||||
isRoot: false
|
||||
},
|
||||
component: () => import('@/views/pages/admin/chat/ChatHome.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
21
core/datacap-ui/src/services/chat.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { BaseService } from '@/services/base'
|
||||
import { ResponseModel } from '@/model/response'
|
||||
import { HttpUtils } from '@/utils/http'
|
||||
|
||||
const DEFAULT_PATH = '/api/v1/chat'
|
||||
|
||||
class ChatService
|
||||
extends BaseService
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super(DEFAULT_PATH)
|
||||
}
|
||||
|
||||
getMessages(id: number): Promise<ResponseModel>
|
||||
{
|
||||
return new HttpUtils().get(`${ DEFAULT_PATH }/${ id }/messages`)
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChatService()
|
@ -21,6 +21,11 @@ class SourceService
|
||||
return new HttpUtils().get(DEFAULT_PATH_V1, { page: page, size: size })
|
||||
}
|
||||
|
||||
getByCode(code: string): Promise<ResponseModel>
|
||||
{
|
||||
return new HttpUtils().get(`${ DEFAULT_PATH_V2 }/code/${ code }`)
|
||||
}
|
||||
|
||||
getPlugins(): Promise<ResponseModel>
|
||||
{
|
||||
return new HttpUtils().get(`${ DEFAULT_PATH_V1 }/plugins`)
|
||||
|
15
core/datacap-ui/src/utils/array.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export class ArrayUtils
|
||||
{
|
||||
static findDuplicates(array: any[]): any[]
|
||||
{
|
||||
const counts: { [key: string]: number } = {}
|
||||
const duplicates: string[] = []
|
||||
array.forEach(column => {
|
||||
counts[column.name] = (counts[column.name] || 0) + 1
|
||||
if (counts[column.name] === 2) {
|
||||
duplicates.push(column.name)
|
||||
}
|
||||
})
|
||||
return duplicates
|
||||
}
|
||||
}
|
17
core/datacap-ui/src/utils/package.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import packageJson from '../../package.json'
|
||||
|
||||
interface PackageJson
|
||||
{
|
||||
name: string
|
||||
description: string
|
||||
version: string
|
||||
}
|
||||
|
||||
export class PackageUtils
|
||||
{
|
||||
public static get(key: keyof PackageJson): string
|
||||
{
|
||||
const pg = packageJson as PackageJson
|
||||
return pg[key]
|
||||
}
|
||||
}
|
@ -10,4 +10,5 @@ export interface GridConfigure
|
||||
context?: string
|
||||
sourceId?: number
|
||||
query?: string
|
||||
code?: string
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<CardHeader class="p-0">
|
||||
<CardTitle class="pt-1">
|
||||
<Button size="sm" class="ml-2">
|
||||
<RouterLink to="/admin/dataset/info" target="_blank">
|
||||
<RouterLink :to="`/admin/dataset/info/source/${configure.code}`" target="_blank">
|
||||
<span class="flex items-center">
|
||||
<Plus :size="20"/> {{ $t('common.dataset') }}
|
||||
</span>
|
||||
@ -101,7 +101,7 @@ export default defineComponent({
|
||||
if (this.configure) {
|
||||
this.updateData(this.configure)
|
||||
this.configure.headers!.forEach((header: string) => {
|
||||
const columnDef: GridColumn = {headerName: header, field: header}
|
||||
const columnDef: GridColumn = { headerName: header, field: header }
|
||||
this.columnDefs.push(columnDef)
|
||||
})
|
||||
}
|
||||
@ -117,5 +117,5 @@ export default defineComponent({
|
||||
this.isPage = value
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
28
core/datacap-ui/src/views/components/link/DcLink.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<a v-if="external" :href="link as string" :target="target">
|
||||
<slot></slot>
|
||||
</a>
|
||||
<RouterLink v-else :to="link as string" :target="target">
|
||||
<slot></slot>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DcLink',
|
||||
props: {
|
||||
external: {
|
||||
type: Boolean
|
||||
},
|
||||
link: {
|
||||
type: String
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: '_blank'
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
@ -7,7 +7,7 @@
|
||||
<SelectContent>
|
||||
<Loader2 v-if="loading" class="w-full justify-center animate-spin"/>
|
||||
<SelectGroup v-else>
|
||||
<SelectItem v-for="item in items" :value="`${item.id}:${item.type}`" :disabled="!item.available">
|
||||
<SelectItem v-for="item in items" :value="`${item.id}:${item.type}:${item.code}`" :disabled="!item.available">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{{ `${item.name} (${item.protocol})` }}</TooltipTrigger>
|
||||
|
@ -3,9 +3,10 @@
|
||||
<div class="hidden flex-col md:flex">
|
||||
<LayoutHeader/>
|
||||
<LayoutBreadcrumb/>
|
||||
<div class="flex-1 space-y-4 pl-8 pr-8">
|
||||
<RouterView />
|
||||
<div class="flex-1 space-y-4 pl-8 pr-8 min-h-[700px]">
|
||||
<RouterView/>
|
||||
</div>
|
||||
<LayoutFooter :data="footers"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -16,15 +17,23 @@ import LayoutHeader from '@/views/layouts/common/components/LayoutHeader.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import LayoutBreadcrumb from '@/views/layouts/common/components/LayoutBreadcrumb.vue'
|
||||
import LayoutFooter from '@/views/layouts/common/components/LayoutFooter.vue'
|
||||
import { TokenUtils } from '@/utils/token'
|
||||
import { ObjectUtils } from '@/utils/object'
|
||||
import { HttpUtils } from '@/utils/http'
|
||||
import UserService from '@/services/user'
|
||||
import CommonUtils from '@/utils/common'
|
||||
import { FooterModel } from '@/views/layouts/common/components/model/footer.ts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutContainer',
|
||||
components: {LayoutBreadcrumb, AvatarFallback, AvatarImage, Avatar, Button, LayoutHeader},
|
||||
components: {
|
||||
LayoutBreadcrumb,
|
||||
AvatarFallback, AvatarImage, Avatar,
|
||||
Button,
|
||||
LayoutHeader,
|
||||
LayoutFooter
|
||||
},
|
||||
beforeUnmount()
|
||||
{
|
||||
clearInterval(this.timer)
|
||||
@ -32,12 +41,14 @@ export default defineComponent({
|
||||
data()
|
||||
{
|
||||
return {
|
||||
timer: null as any
|
||||
timer: null as any,
|
||||
footers: [] as Array<FooterModel>
|
||||
}
|
||||
},
|
||||
created()
|
||||
{
|
||||
this.handlerInitialize()
|
||||
this.handlerInitializeFooter()
|
||||
},
|
||||
methods: {
|
||||
handlerInitialize()
|
||||
@ -46,18 +57,109 @@ export default defineComponent({
|
||||
if (ObjectUtils.isNotEmpty(user)) {
|
||||
this.timer = setInterval(() => {
|
||||
const runTime = new Date().toLocaleTimeString()
|
||||
console.log(`[DataCap] refresh on time ${runTime}`)
|
||||
console.log(`[DataCap] refresh on time ${ runTime }`)
|
||||
const client = new HttpUtils().getAxios()
|
||||
client.all([UserService.getMenus(), UserService.getInfo()])
|
||||
.then(client.spread((fetchMenu, fetchInfo) => {
|
||||
if (fetchMenu.status && fetchInfo.status) {
|
||||
localStorage.setItem(CommonUtils.menu, JSON.stringify(fetchMenu.data))
|
||||
localStorage.setItem(CommonUtils.userEditorConfigure, JSON.stringify(fetchInfo.data.editorConfigure))
|
||||
}
|
||||
}))
|
||||
.then(client.spread((fetchMenu, fetchInfo) => {
|
||||
if (fetchMenu.status && fetchInfo.status) {
|
||||
localStorage.setItem(CommonUtils.menu, JSON.stringify(fetchMenu.data))
|
||||
localStorage.setItem(CommonUtils.userEditorConfigure, JSON.stringify(fetchInfo.data.editorConfigure))
|
||||
}
|
||||
}))
|
||||
}, 1000 * 60)
|
||||
}
|
||||
},
|
||||
handlerInitializeFooter()
|
||||
{
|
||||
const footers = new Array<FooterModel>()
|
||||
footers.push({
|
||||
title: 'Resources',
|
||||
children: [
|
||||
{
|
||||
title: 'Blog',
|
||||
link: 'https://datacap.devlive.org/',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Gitee',
|
||||
link: 'https://gitee.com/devlive-community/datacap',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Github',
|
||||
link: 'https://github.com/devlive-community/datacap',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
link: 'https://datacap.devlive.org/',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
}
|
||||
]
|
||||
})
|
||||
footers.push({
|
||||
title: 'Community',
|
||||
children: [
|
||||
{
|
||||
title: 'Website',
|
||||
link: 'https://datacap.devlive.org/',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Issues',
|
||||
link: 'https://github.com/devlive-community/datacap/issues',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Discussions',
|
||||
link: 'https://github.com/devlive-community/datacap/discussions',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
}
|
||||
]
|
||||
})
|
||||
footers.push({
|
||||
title: 'About',
|
||||
children: [
|
||||
{
|
||||
title: 'DataCap',
|
||||
link: 'https://datacap.devlive.org/',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
}
|
||||
]
|
||||
})
|
||||
footers.push({
|
||||
title: 'Projects',
|
||||
children: [
|
||||
{
|
||||
title: 'Database Tools',
|
||||
link: 'https://github.com/devlive-community/dbm',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Open AI Java SDK',
|
||||
link: 'https://github.com/devlive-community/openai-java-sdk',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
},
|
||||
{
|
||||
title: 'Shadcn UI Vue Admin',
|
||||
link: 'https://github.com/devlive-community/shadcn-ui-vue-admin',
|
||||
external: true,
|
||||
blank: '_blank'
|
||||
}
|
||||
]
|
||||
})
|
||||
this.footers = footers
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<footer class="font-sans py-8 px-10 mt-5">
|
||||
<div :class="`grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-${data.length} gap-8`">
|
||||
<div v-for="item in data" :key="item.title">
|
||||
<h4 class="text-[#808695] font-bold text-lg mb-5">{{ item.title }}</h4>
|
||||
<ul class="space-y-4">
|
||||
<li v-for="children in item.children" :key="children.title">
|
||||
<DcLink :external="children.external" :link="children.link" :target="children.blank"
|
||||
class="hover:text-[#5cadff] text-[#808695] text-[15px] font-semibold transition-all">
|
||||
{{ children.title }}
|
||||
</DcLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t text-center border-[#dcdee2] pt-8 mt-8 space-y-2">
|
||||
<p class="text-gray-300 text-[15px] font-semibold">
|
||||
Copyright © 2022 - {{ new Date().getFullYear() }} Devlive Community All Rights Reserved
|
||||
</p>
|
||||
<p>{{ $t('common.version') }}:
|
||||
<Text type="danger"
|
||||
strong>
|
||||
{{ version }}
|
||||
</Text>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { FooterModel } from '@/views/layouts/common/components/model/footer.ts'
|
||||
import DcLink from '@/views/components/link/DcLink.vue'
|
||||
import { PackageUtils } from '@/utils/package.ts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutFooter',
|
||||
components: { DcLink },
|
||||
props: {
|
||||
data: {
|
||||
type: Array as () => Array<FooterModel>,
|
||||
default: () => new Array<FooterModel>()
|
||||
}
|
||||
},
|
||||
setup()
|
||||
{
|
||||
const version = PackageUtils.get('version')
|
||||
return { version }
|
||||
}
|
||||
})
|
||||
</script>
|
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<Carousel :items="carouselItems" :delay="3000"/>
|
||||
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
|
||||
<div class="container flex h-14 max-w-screen-2xl items-center">
|
||||
<div class="container flex h-14 items-center justify-between">
|
||||
@ -136,6 +137,7 @@ import NavigationMenuListItem from '@/views/layouts/common/components/components
|
||||
import { CircleHelp, LogOut, Settings } from 'lucide-vue-next'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import LanguageSwitcher from '@/views/layouts/common/components/components/LanguageSwitcher.vue'
|
||||
import Carousel from '@/views/ui/carousel'
|
||||
|
||||
interface NavigationItem
|
||||
{
|
||||
@ -171,14 +173,25 @@ export default defineComponent({
|
||||
router.push('/auth/signin')
|
||||
}
|
||||
|
||||
const carouselItems = [
|
||||
{
|
||||
title: 'Support ChatGPT',
|
||||
link: '/admin/chat',
|
||||
external: false,
|
||||
isAlert: true
|
||||
}
|
||||
]
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
isLoggedIn,
|
||||
activeMenus,
|
||||
logout
|
||||
logout,
|
||||
carouselItems
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Carousel,
|
||||
LanguageSwitcher,
|
||||
TooltipContent, Tooltip, TooltipTrigger, TooltipProvider,
|
||||
NavigationMenuLink, NavigationMenuContent, NavigationMenuTrigger, NavigationMenuItem, NavigationMenuList, NavigationMenu,
|
||||
@ -196,6 +209,6 @@ export default defineComponent({
|
||||
this.$emit('changeLanguage', language)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
export interface FooterModel
|
||||
{
|
||||
title?: string
|
||||
icon?: string
|
||||
link?: string
|
||||
external?: boolean
|
||||
children?: FooterModel[],
|
||||
copyright?: string
|
||||
blank?: string
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
<aside class="-mx-4 lg:w-1/12">
|
||||
<LayoutSidebar/>
|
||||
</aside>
|
||||
<div class="flex-1 lg:max-w-3xl">
|
||||
<div class="flex-1 lg:max-w-5xl">
|
||||
<div class="space-y-6">
|
||||
<RouterView/>
|
||||
</div>
|
||||
|
245
core/datacap-ui/src/views/pages/admin/chat/ChatHome.vue
Normal file
@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div 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-2" body-class="p-2">
|
||||
<template #title>{{ $t('common.chat') }}</template>
|
||||
<template #extra>
|
||||
<Button size="icon" class="w-6 h-6 rounded-full" @click="handlerInfo(true)">
|
||||
<Plus :size="15"/>
|
||||
</Button>
|
||||
</template>
|
||||
<CircularLoading v-if="loading" :show="loading"/>
|
||||
<div v-else>
|
||||
<FormField type="radio" name="theme">
|
||||
<FormItem class="space-y-1">
|
||||
<RadioGroup class="grid gap-3 pt-2 cursor-pointer" @update:modelValue="handlerChange">
|
||||
<FormItem v-for="item of data" :key="item.id">
|
||||
<FormLabel class="[&:has([data-state=checked])>div]:border-primary cursor-pointer">
|
||||
<FormControl>
|
||||
<RadioGroupItem :value="item.id as unknown as string" class="sr-only cursor-pointer"/>
|
||||
</FormControl>
|
||||
<div class="items-center rounded-md border-4 border-muted p-1 hover:border-accent">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<Avatar :src="item.avatar" :alt="item.name"/>
|
||||
<div>
|
||||
<p class="text-sm font-medium leading-none">{{ item.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="flex-1">
|
||||
<div v-if="dataInfo">
|
||||
<Card title-class="p-2" body-class="p-2">
|
||||
<template #title>
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<Avatar :src="dataInfo.avatar" :alt="dataInfo.name"/>
|
||||
<div>
|
||||
<p class="text-sm font-medium leading-none">{{ dataInfo.name }}</p>
|
||||
<p class="text-sm text-muted-foreground">{{ dataInfo.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<div class="flex h-5 items-center space-x-4 text-sm">
|
||||
<div>
|
||||
Prompt Tokens: {{ promptTokens }}
|
||||
</div>
|
||||
<Separator orientation="vertical"/>
|
||||
<div>
|
||||
Completion Tokens: {{ completionTokens }}
|
||||
</div>
|
||||
<Separator orientation="vertical"/>
|
||||
<div>
|
||||
Total Tokens: {{ totalTokens }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<CircularLoading v-if="loadingMessages" :show="loadingMessages"/>
|
||||
<div ref="scrollDiv" class="space-y-4 h-[500px] overflow-y-auto">
|
||||
<div v-for="(item, index) in messages" :key="index">
|
||||
<div
|
||||
:class="cn( 'flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm', item.type === 'question' ? 'ml-auto bg-primary text-primary-foreground' : 'bg-muted')">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<div v-if="item.type === 'answer'" class="flex text-sm text-muted-foreground mt-0.5 space-x-2">
|
||||
<div>Model: {{ item.model }}</div>
|
||||
<Separator orientation="vertical"/>
|
||||
<div>Prompt Tokens: {{ item.promptTokens }}</div>
|
||||
<Separator orientation="vertical"/>
|
||||
<div>Completion Tokens: {{ item.completionTokens }}</div>
|
||||
<Separator orientation="vertical"/>
|
||||
<div>Total Tokens: {{ item.totalTokens }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex w-full items-center space-x-2">
|
||||
<Input v-model="inputValue" :disabled="submitting" placeholder="Type a message ..." class="flex-1"/>
|
||||
<Button class="p-2.5 flex items-center justify-center" :loading="submitting" :disabled="!inputValue || submitting" @click="handlerSubmit">
|
||||
<Send v-if="!submitting" class="w-4 h-4"/>
|
||||
<span class="sr-only"></span>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChatInfo v-if="dataVisible" :is-visible="dataVisible" @close="handlerInfo($event)"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||
import { FilterModel } from '@/model/filter.ts'
|
||||
import ChatService from '@/services/chat.ts'
|
||||
import { ToastUtils } from '@/utils/toast.ts'
|
||||
import Card from '@/views/ui/card'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import Button from '@/views/ui/button'
|
||||
import { Plus, Send } from 'lucide-vue-next'
|
||||
import ChatInfo from '@/views/pages/admin/chat/ChatInfo.vue'
|
||||
import Avatar from '@/views/ui/avatar'
|
||||
import { ChatModel } from '@/model/chat.ts'
|
||||
import { toNumber } from 'lodash'
|
||||
import { cn } from '@/lib/utils.ts'
|
||||
import { MessageModel } from '@/model/message.ts'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import MessageService from '@/services/message.ts'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatHome',
|
||||
components: {
|
||||
Separator,
|
||||
Input,
|
||||
Avatar,
|
||||
ChatInfo,
|
||||
Button,
|
||||
CircularLoading,
|
||||
Card,
|
||||
FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,
|
||||
RadioGroup, RadioGroupItem,
|
||||
Plus, Send
|
||||
},
|
||||
setup()
|
||||
{
|
||||
const filter: FilterModel = new FilterModel()
|
||||
return {
|
||||
filter,
|
||||
cn
|
||||
}
|
||||
},
|
||||
data()
|
||||
{
|
||||
return {
|
||||
loading: true,
|
||||
data: [] as ChatModel[],
|
||||
dataInfo: null as ChatModel | null,
|
||||
dataVisible: false,
|
||||
loadingMessages: false,
|
||||
messages: [] as MessageModel[],
|
||||
promptTokens: 0,
|
||||
completionTokens: 0,
|
||||
totalTokens: 0,
|
||||
submitting: false,
|
||||
inputValue: ''
|
||||
}
|
||||
},
|
||||
created()
|
||||
{
|
||||
this.handlerInitialize()
|
||||
},
|
||||
methods: {
|
||||
handlerInitialize()
|
||||
{
|
||||
this.loading = true
|
||||
ChatService.getAll(this.filter)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.data = response.data.content
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
},
|
||||
handlerInfo(opened: boolean)
|
||||
{
|
||||
this.dataVisible = opened
|
||||
if (!opened) {
|
||||
this.handlerInitialize()
|
||||
}
|
||||
},
|
||||
handlerChange(value: string)
|
||||
{
|
||||
this.dataInfo = this.data.find(item => item.id === toNumber(value)) as unknown as ChatModel
|
||||
this.loadingMessages = true
|
||||
ChatService.getMessages(toNumber(value))
|
||||
.then(response => {
|
||||
this.messages = response.data
|
||||
})
|
||||
.finally(() => {
|
||||
this.loadingMessages = false
|
||||
this.counterToken()
|
||||
this.handlerGoBottom()
|
||||
})
|
||||
},
|
||||
handlerSubmit()
|
||||
{
|
||||
this.submitting = true
|
||||
const message = {
|
||||
content: this.inputValue,
|
||||
chat: this.dataInfo,
|
||||
type: 'question'
|
||||
}
|
||||
this.messages.push(message)
|
||||
this.handlerGoBottom()
|
||||
MessageService.saveOrUpdate(message)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.messages.push(response.data)
|
||||
this.inputValue = ''
|
||||
}
|
||||
else {
|
||||
this.$Message.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
this.counterToken()
|
||||
this.handlerGoBottom()
|
||||
})
|
||||
},
|
||||
handlerGoBottom()
|
||||
{
|
||||
const scrollElem = this.$refs.scrollDiv as any
|
||||
setTimeout(() => {
|
||||
scrollElem.scrollTo({ top: scrollElem.scrollHeight, behavior: 'smooth' })
|
||||
}, 0)
|
||||
},
|
||||
counterToken()
|
||||
{
|
||||
const answers = this.messages.filter(message => message.type === 'answer')
|
||||
this.promptTokens = answers.reduce((sum, message) => sum + toNumber(message.promptTokens), 0)
|
||||
this.completionTokens = answers.reduce((sum, message) => sum + toNumber(message.completionTokens), 0)
|
||||
this.totalTokens = answers.reduce((sum, message) => sum + toNumber(message.totalTokens), 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
113
core/datacap-ui/src/views/pages/admin/chat/ChatInfo.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<Dialog :is-visible="visible" :title="$t('common.chat')">
|
||||
<div class="space-y-2 pl-3 pr-3">
|
||||
<FormField name="name">
|
||||
<FormItem class="space-y-1">
|
||||
<FormLabel>{{ $t('common.name') }}</FormLabel>
|
||||
<FormMessage/>
|
||||
<Input v-model="formState.name"/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField name="avatar">
|
||||
<FormItem class="space-y-1">
|
||||
<FormLabel>{{ $t('common.avatar') }}</FormLabel>
|
||||
<FormMessage/>
|
||||
<Input v-model="formState.avatar"/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField name="description">
|
||||
<FormItem class="space-y-1">
|
||||
<FormLabel>{{ $t('common.description') }}</FormLabel>
|
||||
<FormMessage/>
|
||||
<Textarea v-model="formState.description"/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="space-x-5">
|
||||
<Button variant="outline" size="sm" @click="handlerCancel">
|
||||
{{ $t('common.cancel') }}
|
||||
</Button>
|
||||
<Button size="sm" :loading="loading" :disabled="loading" @click="handlerSave()">
|
||||
{{ $t('common.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import Dialog from '@/views/ui/dialog'
|
||||
import Button from '@/views/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ToastUtils } from '@/utils/toast'
|
||||
import ChatService from '@/services/chat.ts'
|
||||
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||
import { ChatModel, ChatRequest } from '@/model/chat.ts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatInfo',
|
||||
components: {
|
||||
CircularLoading,
|
||||
Input,
|
||||
FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,
|
||||
Textarea,
|
||||
Button,
|
||||
Dialog
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get(): boolean
|
||||
{
|
||||
return this.isVisible
|
||||
},
|
||||
set(value: boolean)
|
||||
{
|
||||
this.$emit('close', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isVisible: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data()
|
||||
{
|
||||
return {
|
||||
loading: false,
|
||||
formState: null as unknown as ChatModel
|
||||
}
|
||||
},
|
||||
created()
|
||||
{
|
||||
this.formState = ChatRequest.of()
|
||||
},
|
||||
methods: {
|
||||
handlerSave()
|
||||
{
|
||||
if (this.formState) {
|
||||
this.loading = true
|
||||
ChatService.saveOrUpdate(this.formState)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
ToastUtils.success(this.$t('common.success'))
|
||||
this.handlerCancel()
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
}
|
||||
},
|
||||
handlerCancel()
|
||||
{
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
@ -38,8 +38,11 @@
|
||||
<p class="text-xs text-muted-foreground mt-2">{{ item.createTime }}</p>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-if="data.length === 0" class="text-center">
|
||||
{{ $t('common.noData') }}
|
||||
</div>
|
||||
<div>
|
||||
<Pagination :pagination="pagination" @changePage="handlerChangePage"/>
|
||||
<Pagination v-if="pagination && !loading && data.length > 0" :pagination="pagination" @changePage="handlerChangePage"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,10 +63,12 @@ import DashboardDelete from '@/views/pages/admin/dashboard/DashboardDelete.vue'
|
||||
import Card from '@/views/ui/card'
|
||||
import Pagination from '@/views/ui/pagination'
|
||||
import Button from '@/views/ui/button'
|
||||
import { TableCaption } from '@/components/ui/table'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DashboardHome',
|
||||
components: {
|
||||
TableCaption,
|
||||
Pagination,
|
||||
DashboardDelete,
|
||||
DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuContent, DropdownMenuTrigger, DropdownMenu,
|
||||
|
@ -21,6 +21,10 @@
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<div v-if="data.length === 0" class="flex w-full items-center">
|
||||
{{ $t('common.noData') }}
|
||||
</div>
|
||||
<Pagination v-if="pagination && !loading && data.length > 0" :pagination="pagination" @changePage="handlerChangePage"/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="space-x-5">
|
||||
@ -47,10 +51,13 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import VisualView from '@/views/components/visual/VisualView.vue'
|
||||
import Button from '@/views/ui/button'
|
||||
import { toNumber } from 'lodash'
|
||||
import Pagination from '@/views/ui/pagination'
|
||||
import { PaginationModel, PaginationRequest } from '@/model/pagination.ts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChartContainer',
|
||||
components: {
|
||||
Pagination,
|
||||
VisualView,
|
||||
CircularLoading,
|
||||
Dialog,
|
||||
@ -78,6 +85,7 @@ export default defineComponent({
|
||||
setup()
|
||||
{
|
||||
const filter: FilterModel = new FilterModel()
|
||||
filter.size = 12
|
||||
|
||||
return {
|
||||
filter
|
||||
@ -92,7 +100,8 @@ export default defineComponent({
|
||||
return {
|
||||
loading: false,
|
||||
data: [] as ReportModel[],
|
||||
report: ''
|
||||
report: '',
|
||||
pagination: {} as PaginationModel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -103,10 +112,17 @@ export default defineComponent({
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.data = response.data.content
|
||||
this.pagination = PaginationRequest.of(response.data)
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
},
|
||||
handlerChangePage(value: PaginationModel)
|
||||
{
|
||||
this.filter.page = value.currentPage
|
||||
this.filter.size = value.pageSize
|
||||
this.handlerInitialize()
|
||||
},
|
||||
handlerSave()
|
||||
{
|
||||
const node = this.data.find(item => item.id === toNumber(this.report))
|
||||
|
@ -42,9 +42,14 @@
|
||||
</FormField>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button :loading="loading" @click="handlerSave">
|
||||
{{ $t('common.save') }}
|
||||
</Button>
|
||||
<div class="space-x-5">
|
||||
<Button variant="outline" size="sm" @click="configureVisible = false">
|
||||
{{ $t('common.cancel') }}
|
||||
</Button>
|
||||
<Button :loading="loading" size="sm" @click="handlerSave">
|
||||
{{ $t('common.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<div class="hidden space-y-6 pb-16 w-full h-full md:block">
|
||||
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||
<aside class="-mx-4 w-[200px]">
|
||||
<Card>
|
||||
<CardHeader class="p-3 border-b">
|
||||
<CardTitle>
|
||||
{{ $t('dataset.common.columnModeMetric') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="p-3">
|
||||
<aside class="-mx-4 w-[200px] space-y-5">
|
||||
<Card body-class="p-3" title-class="p-2">
|
||||
<template #title>{{ $t('dataset.common.columnModeMetric') }}</template>
|
||||
<CircularLoading v-if="initialize" :show="initialize"/>
|
||||
<div v-else>
|
||||
<Draggable item-key="id" :clone="handlerClone" :group="{ name: 'metrics', pull: 'clone', put: false }" :list="originalMetrics">
|
||||
<template #item="{ element }">
|
||||
<Badge variant="outline" class="cursor-pointer mr-1">
|
||||
@ -16,15 +13,12 @@
|
||||
</Badge>
|
||||
</template>
|
||||
</Draggable>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="mt-5">
|
||||
<CardHeader class="p-3 border-b">
|
||||
<CardTitle>
|
||||
{{ $t('dataset.common.columnModeDimension') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="p-3">
|
||||
<Card body-class="p-3" title-class="p-2">
|
||||
<template #title>{{ $t('dataset.common.columnModeDimension') }}</template>
|
||||
<CircularLoading v-if="initialize" :show="initialize"/>
|
||||
<div v-else>
|
||||
<Draggable item-key="id" :clone="handlerClone" :group="{ name: 'dimensions', pull: 'clone', put: false }" :list="originalDimensions">
|
||||
<template #item="{ element }">
|
||||
<Badge variant="outline" class="cursor-pointer mr-1 mt-1">
|
||||
@ -32,7 +26,7 @@
|
||||
</Badge>
|
||||
</template>
|
||||
</Draggable>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="flex-1">
|
||||
@ -297,6 +291,18 @@
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField class="flex items-center" name="build">
|
||||
<FormItem class="flex-1">
|
||||
<div class="flex items-center">
|
||||
<FormLabel class="mr-1 w-20 text-right">
|
||||
{{ $t('dataset.common.continuousBuild') }}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch :value="formState.build" @changeValue="formState.build = $event"/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<AlertDialogFooter class="-mb-4 border-t pt-2">
|
||||
<Button @click="formState.visible = false">{{ $t('common.cancel') }}</Button>
|
||||
<Button :disabled="!formState.name" @click="handlerPublish">
|
||||
@ -329,7 +335,7 @@ import DatasetVisualConfigureBar from '@/views/pages/admin/dataset/components/ad
|
||||
import DatasetVisualConfigureLine from '@/views/pages/admin/dataset/components/adhoc/DatasetVisualConfigureLine.vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import Card from '@/views/ui/card'
|
||||
import { AreaChart, BarChart4, BarChartHorizontal, Baseline, CirclePlay, Cog, Eye, LineChart, Loader2, PieChart, Table, Trash } from 'lucide-vue-next'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
@ -343,6 +349,7 @@ import SqlInfo from '@/views/components/sql/SqlInfo.vue'
|
||||
import { AlertDialog, AlertDialogContent, AlertDialogFooter, AlertDialogHeader } from '@/components/ui/alert-dialog'
|
||||
import { FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form'
|
||||
import { Select } from '@/components/ui/select'
|
||||
import Switch from '@/views/ui/switch'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DatasetAdhoc',
|
||||
@ -357,6 +364,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Switch,
|
||||
FormField,
|
||||
FormControl,
|
||||
FormLabel, Select, FormItem,
|
||||
@ -369,7 +377,7 @@ export default defineComponent({
|
||||
Input,
|
||||
Tooltip, TooltipContent, TooltipTrigger, TooltipProvider,
|
||||
Separator,
|
||||
CardTitle, CardHeader, CardContent, Card,
|
||||
Card,
|
||||
Badge,
|
||||
DatasetVisualConfigureWordCloud,
|
||||
DatasetVisualConfigureHistogram,
|
||||
@ -415,9 +423,11 @@ export default defineComponent({
|
||||
commitOptions: null,
|
||||
formState: {
|
||||
visible: false,
|
||||
name: ''
|
||||
name: '',
|
||||
build: false
|
||||
},
|
||||
published: false
|
||||
published: false,
|
||||
initialize: false
|
||||
}
|
||||
},
|
||||
created()
|
||||
@ -429,37 +439,39 @@ export default defineComponent({
|
||||
handlerInitialize()
|
||||
{
|
||||
setTimeout(() => {
|
||||
this.initialize = true
|
||||
const code = this.$route.params.code as string
|
||||
this.code = code as string
|
||||
const id = this.$route.params.id
|
||||
this.id = id as unknown as number
|
||||
DatasetService.getColumnsByCode(this.code)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.originalData = response.data
|
||||
this.originalMetrics = response.data.filter((item: { mode: string; }) => item.mode === 'METRIC')
|
||||
this.originalDimensions = response.data.filter((item: { mode: string; }) => item.mode === 'DIMENSION')
|
||||
if (id) {
|
||||
ReportService.getById(this.id as number)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.formState.name = response.data.name
|
||||
const query = JSON.parse(response.data.query)
|
||||
this.mergeColumns(query.columns, this.metrics, ColumnType.METRIC)
|
||||
this.mergeColumns(query.columns, this.dimensions, ColumnType.DIMENSION)
|
||||
this.mergeColumns(query.columns, this.filters, ColumnType.FILTER)
|
||||
this.configure.columns = query.columns
|
||||
this.configure.limit = query.limit
|
||||
this.configuration = JSON.parse(response.data.configure)
|
||||
this.handlerApplyAdhoc()
|
||||
this.originalData = response.data
|
||||
this.originalMetrics = response.data.filter((item: { mode: string; }) => item.mode === 'METRIC')
|
||||
this.originalDimensions = response.data.filter((item: { mode: string; }) => item.mode === 'DIMENSION')
|
||||
if (id) {
|
||||
ReportService.getById(this.id as number)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.formState.name = response.data.name
|
||||
const query = JSON.parse(response.data.query)
|
||||
this.mergeColumns(query.columns, this.metrics, ColumnType.METRIC)
|
||||
this.mergeColumns(query.columns, this.dimensions, ColumnType.DIMENSION)
|
||||
this.mergeColumns(query.columns, this.filters, ColumnType.FILTER)
|
||||
this.configure.columns = query.columns
|
||||
this.configure.limit = query.limit
|
||||
this.configuration = JSON.parse(response.data.configure)
|
||||
this.handlerApplyAdhoc()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.initialize = false)
|
||||
}, 0)
|
||||
},
|
||||
handlerApplyAdhoc()
|
||||
@ -474,27 +486,27 @@ export default defineComponent({
|
||||
this.isPublish = true
|
||||
this.loading = true
|
||||
DatasetService.adhoc(this.code as string, this.configure)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
if (this.configuration) {
|
||||
if (response.data.isSuccessful) {
|
||||
this.configuration.headers = response.data.headers
|
||||
this.configuration.columns = response.data.columns
|
||||
this.showSql.content = response.data.content
|
||||
this.configuration.message = null
|
||||
}
|
||||
else {
|
||||
this.configuration.headers = []
|
||||
this.configuration.columns = []
|
||||
this.configuration.message = response.data.message
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
if (this.configuration) {
|
||||
if (response.data.isSuccessful) {
|
||||
this.configuration.headers = response.data.headers
|
||||
this.configuration.columns = response.data.columns
|
||||
this.showSql.content = response.data.content
|
||||
this.configuration.message = null
|
||||
}
|
||||
else {
|
||||
this.configuration.headers = []
|
||||
this.configuration.columns = []
|
||||
this.configuration.message = response.data.message
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
},
|
||||
handlerClone(value: any)
|
||||
{
|
||||
@ -567,14 +579,16 @@ export default defineComponent({
|
||||
configure.id = this.id
|
||||
}
|
||||
ReportService.saveOrUpdate(configure)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
ToastUtils.success(this.$t('report.common.publishSuccess').replace('REPLACE_NAME', this.formState.name))
|
||||
this.formState.visible = false
|
||||
router.push('/admin/report')
|
||||
}
|
||||
})
|
||||
.finally(() => this.published = false)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
ToastUtils.success(this.$t('report.tip.publishSuccess').replace('$VALUE', this.formState.name))
|
||||
this.formState.visible = false
|
||||
if (!this.formState.build) {
|
||||
router.push('/admin/report')
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => this.published = false)
|
||||
},
|
||||
mergeColumns(originalColumns: any[], array: any[], type?: ColumnType)
|
||||
{
|
||||
|
@ -1,260 +1,275 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<div class="flex justify-end">
|
||||
<Button size="sm" @click="configureVisible = true">
|
||||
{{ $t('common.configure') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="data || code" class="mt-3">
|
||||
<AgGridVue v-if="data?.columns" :style="{height: '300px'}" class="ag-theme-datacap" :pagination="true" :columnDefs="columnDefs" :rowData="data.columns"
|
||||
:gridOptions="gridOptions as any"/>
|
||||
<Alert v-else variant="destructive">
|
||||
{{ i18n.t('dataset.tip.modifyNotSupportDataPreview') }}
|
||||
</Alert>
|
||||
<Sheet :default-open="configureVisible" :open="configureVisible" @update:open="configureVisible = false">
|
||||
<SheetContent side="bottom" class="w-full h-[80%]">
|
||||
<SheetHeader class="border-b pb-3">
|
||||
<SheetTitle>
|
||||
{{ $t('common.configure') }}
|
||||
<Button size="sm" class="float-right mr-5 -mt-2" @click="handlerCreate">
|
||||
{{ code ? $t('dataset.common.modify') : $t('dataset.common.create') }}
|
||||
</Button>
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Alert v-if="validator" variant="destructive">{{ validatorMessage }}</Alert>
|
||||
<Tabs default-value="columns" class="mt-1">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="columns">{{ $t('dataset.common.dataColumn') }}</TabsTrigger>
|
||||
<TabsTrigger value="configure">{{ $t('dataset.common.dataConfigure') }}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="columns">
|
||||
<CircularLoading v-if="loading" :show="loading"/>
|
||||
<div v-else class="flex w-full flex-col">
|
||||
<div class="flex flex-1 flex-col gap-4 p-1 text-center">
|
||||
<div class="grid items-center gap-3 md:grid-cols-2 md:gap-4 lg:grid-cols-12">
|
||||
<div>{{ $t('dataset.common.columnName') }}</div>
|
||||
<div>{{ $t('dataset.common.columnAlias') }}</div>
|
||||
<div>{{ $t('dataset.common.columnType') }}</div>
|
||||
<div>{{ $t('dataset.common.columnMode') }}</div>
|
||||
<div>{{ $t('dataset.common.columnDefaultValue') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsNullable') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsOrderByKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsPartitionKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsPrimaryKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsSampling') }}</div>
|
||||
<div>{{ $t('dataset.common.columnLength') }}</div>
|
||||
<div>{{ $t('common.action') }}</div>
|
||||
<CircularLoading v-if="loading" :show="loading"/>
|
||||
<div v-else>
|
||||
<Card v-if="sourceInfo" title-class="p-2" body-class="p-0">
|
||||
<template #title>
|
||||
<Button size="sm" :loading="running" :disabled="running" @click="handlerRun()">
|
||||
{{ $t('query.common.execute') }}
|
||||
</Button>
|
||||
</template>
|
||||
<FormField v-slot="{ componentField }" name="content">
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<AceEditor :value="value" v-bind="componentField" @update:value="value = $event"/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Card>
|
||||
<div v-if="data || code" class="mt-3">
|
||||
<CircularLoading v-if="running" :show="running"/>
|
||||
<AgGridVue v-else-if="data?.data.columns" :style="{height: '300px'}" class="ag-theme-datacap" :pagination="true" :columnDefs="columnDefs" :rowData="data.data.columns"
|
||||
:gridOptions="gridOptions as any"/>
|
||||
<Sheet :default-open="configureVisible" :open="configureVisible" @update:open="configureVisible = false">
|
||||
<SheetContent side="bottom" class="w-full h-[80%]">
|
||||
<SheetHeader class="border-b pb-3">
|
||||
<SheetTitle>
|
||||
{{ $t('common.configure') }}
|
||||
<Button size="sm" class="float-right mr-5 -mt-2" @click="handlerCreate">
|
||||
{{ code ? $t('dataset.common.modify') : $t('dataset.common.create') }}
|
||||
</Button>
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Alert v-if="validator" variant="destructive" class="mt-2">{{ validatorMessage }}</Alert>
|
||||
<Tabs default-value="columns" class="mt-1">
|
||||
<TabsList class="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="columns">{{ $t('dataset.common.dataColumn') }}</TabsTrigger>
|
||||
<TabsTrigger value="configure">{{ $t('dataset.common.dataConfigure') }}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="columns">
|
||||
<CircularLoading v-if="loading" :show="loading"/>
|
||||
<div v-else class="flex w-full flex-col">
|
||||
<div class="flex flex-1 flex-col gap-4 p-1 text-center">
|
||||
<div class="grid items-center gap-3 md:grid-cols-2 md:gap-4 lg:grid-cols-12">
|
||||
<div>{{ $t('dataset.common.columnName') }}</div>
|
||||
<div>{{ $t('dataset.common.columnAlias') }}</div>
|
||||
<div>{{ $t('dataset.common.columnType') }}</div>
|
||||
<div>{{ $t('dataset.common.columnMode') }}</div>
|
||||
<div>{{ $t('dataset.common.columnDefaultValue') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsNullable') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsOrderByKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsPartitionKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsPrimaryKey') }}</div>
|
||||
<div>{{ $t('dataset.common.columnIsSampling') }}</div>
|
||||
<div>{{ $t('dataset.common.columnLength') }}</div>
|
||||
<div>{{ $t('common.action') }}</div>
|
||||
</div>
|
||||
<div class="grid gap-3 md:grid-cols-2 md:gap-3 lg:grid-cols-12 h-[480px] overflow-y-auto pt-2 pb-2">
|
||||
<template v-for="(item, index) in formState.columns" :key="index">
|
||||
<div>
|
||||
<Input v-model="item.name" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.aliasName" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<Select v-model="item.type">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a fruit"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="STRING">{{ $t('dataset.common.columnTypeString') }}</SelectItem>
|
||||
<SelectItem value="NUMBER">{{ $t('dataset.common.columnTypeNumber') }}</SelectItem>
|
||||
<SelectItem value="NUMBER_SIGNED">{{ $t('dataset.common.columnTypeNumberSigned') }}</SelectItem>
|
||||
<SelectItem value="BOOLEAN">{{ $t('dataset.common.columnTypeBoolean') }}</SelectItem>
|
||||
<SelectItem value="DATETIME">{{ $t('dataset.common.columnTypeDateTime') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="flex items-center space-x-2 mt-2">
|
||||
<Label for="airplane-mode">{{ $t('dataset.common.columnModeMetric') }}</Label>
|
||||
<Switch v-model="item.mode" :default-checked="item.mode === 'DIMENSION'" @update:checked="setMode(item, $event)"/>
|
||||
<Label for="airplane-mode">{{ $t('dataset.common.columnModeDimension') }}</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.defaultValue" type="text" :disabled="item.virtualColumn"/>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Switch v-model="item.nullable" :disabled="item.virtualColumn" :default-checked="item.nullable"
|
||||
@update:checked="setNullable(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.orderByKey" :default-checked="item.orderByKey" :disabled="item.virtualColumn"
|
||||
@update:checked="setOrderByKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.partitionKey" :disabled="item.virtualColumn" :default-checked="item.partitionKey"
|
||||
@update:checked="setPartitionKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.primaryKey" :disabled="item.virtualColumn" :default-checked="item.primaryKey"
|
||||
@update:checked="setPrimaryKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.samplingKey" :disabled="item.virtualColumn" :default-checked="item.samplingKey"
|
||||
@update:checked="setSamplingKey(item, $event)"/>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.length" type="number" :disabled="item.type === 'BOOLEAN' || item.type === 'DATETIME' || item.virtualColumn"/>
|
||||
</div>
|
||||
<div class="space-x-1 ml-4">
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button class="rounded-full w-8 h-8" variant="outline" size="icon">
|
||||
<Pencil :size="15"/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-80">
|
||||
<div class="grid gap-4">
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-medium leading-none">{{ $t('dataset.common.columnComment') }}</h4>
|
||||
</div>
|
||||
<Textarea v-model="item.comment"/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button class="rounded-full w-8 h-8" variant="destructive" size="icon" :disabled="!item.customColumn" @click="handlerRemoveColumn(index)">
|
||||
<Trash :size="15"/>
|
||||
</Button>
|
||||
<Button class="rounded-full w-8 h-8" size="icon" @click="handlerAddColumn(index)">
|
||||
<Plus :size="15"/>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-3 md:grid-cols-2 md:gap-3 lg:grid-cols-12 h-[480px] overflow-y-auto pt-2 pb-2">
|
||||
<template v-for="(item, index) in formState.columns" :key="index">
|
||||
<div>
|
||||
<Input v-model="item.name" type="text"/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="configure">
|
||||
<Card class="border-0 mt-5 shadow-transparent">
|
||||
<CardContent class="grid gap-6 justify-center pt-2 pb-2">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">{{ $t('common.name') }}</Label>
|
||||
<Input v-model="formState.name as string"/>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.aliasName" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<Select v-model="item.type">
|
||||
<div class="grid gap-2">
|
||||
<Label for="executor">{{ $t('common.executor') }}</Label>
|
||||
<Select v-model="formState.executor">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a fruit"/>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="STRING">{{ $t('dataset.common.columnTypeString') }}</SelectItem>
|
||||
<SelectItem value="NUMBER">{{ $t('dataset.common.columnTypeNumber') }}</SelectItem>
|
||||
<SelectItem value="NUMBER_SIGNED">{{ $t('dataset.common.columnTypeNumberSigned') }}</SelectItem>
|
||||
<SelectItem value="BOOLEAN">{{ $t('dataset.common.columnTypeBoolean') }}</SelectItem>
|
||||
<SelectItem value="DATETIME">{{ $t('dataset.common.columnTypeDateTime') }}</SelectItem>
|
||||
<SelectItem v-for="item in executors" :value="item">{{ item }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="flex items-center space-x-2 mt-2">
|
||||
<Label for="airplane-mode">{{ $t('dataset.common.columnModeMetric') }}</Label>
|
||||
<Switch v-model="item.mode" :default-checked="item.mode === 'DIMENSION'" @update:checked="setMode(item, $event)"/>
|
||||
<Label for="airplane-mode">{{ $t('dataset.common.columnModeDimension') }}</Label>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="syncMode">{{ $t('dataset.common.syncMode') }}</Label>
|
||||
<Select v-model="formState.syncMode">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="$t('card.tip.roleHolder')"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MANUAL">{{ $t('dataset.common.syncModeManual') }}</SelectItem>
|
||||
<SelectItem value="TIMING">{{ $t('dataset.common.syncModeTiming') }}</SelectItem>
|
||||
<SelectItem value="OUT_SYNC">{{ $t('dataset.common.syncModeOutSync') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.defaultValue" type="text" :disabled="item.virtualColumn"/>
|
||||
<div class="grid gap-2" v-if="formState.syncMode === 'TIMING'">
|
||||
<Label for="syncMode">{{ $t('dataset.common.columnExpression') }}</Label>
|
||||
<Input v-model="formState.expression as string" placeholder="0 0 * * * ?"/>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Switch v-model="item.nullable" :disabled="item.virtualColumn" :default-checked="item.nullable"
|
||||
@update:checked="setNullable(item, $event)"/>
|
||||
<div class="grid gap-2" v-if="formState.syncMode === 'TIMING'">
|
||||
<Label for="syncMode">{{ $t('common.scheduler') }}</Label>
|
||||
<Select v-model="formState.scheduler">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in schedulers" :value="item">{{ item }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.orderByKey" :default-checked="item.orderByKey" :disabled="item.virtualColumn"
|
||||
@update:checked="setOrderByKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.partitionKey" :disabled="item.virtualColumn" :default-checked="item.partitionKey"
|
||||
@update:checked="setPartitionKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.primaryKey" :disabled="item.virtualColumn" :default-checked="item.primaryKey"
|
||||
@update:checked="setPrimaryKey(item, $event)"/>
|
||||
</div>
|
||||
<div class="mt-2 ml-4">
|
||||
<Switch v-model="item.samplingKey" :disabled="item.virtualColumn" :default-checked="item.samplingKey"
|
||||
@update:checked="setSamplingKey(item, $event)"/>
|
||||
</div>
|
||||
<div>
|
||||
<Input v-model="item.length" type="number" :disabled="item.type === 'BOOLEAN' || item.type === 'DATETIME' || item.virtualColumn"/>
|
||||
</div>
|
||||
<div class="space-x-1 ml-4">
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button class="rounded-full w-8 h-8" variant="outline" size="icon">
|
||||
<Pencil :size="15"/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-80">
|
||||
<div class="grid gap-4">
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-medium leading-none">{{ $t('dataset.common.columnComment') }}</h4>
|
||||
</div>
|
||||
<Textarea v-model="item.comment"/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button class="rounded-full w-8 h-8" variant="destructive" size="icon" :disabled="!item.customColumn" @click="handlerRemoveColumn(index)">
|
||||
<Trash :size="15"/>
|
||||
</Button>
|
||||
<Button class="rounded-full w-8 h-8" size="icon" @click="handlerAddColumn(index)">
|
||||
<Plus :size="15"/>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="configure">
|
||||
<Card class="border-0 mt-5 shadow-transparent">
|
||||
<CardContent class="grid gap-6 justify-center pt-2 pb-2">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">{{ $t('common.name') }}</Label>
|
||||
<Input v-model="formState.name as string"/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="executor">{{ $t('common.executor') }}</Label>
|
||||
<Select v-model="formState.executor">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in executors" :value="item">{{ item }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Separator/>
|
||||
<div class="grid gap-2 -mt-3">
|
||||
<Label for="description">
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<Button variant="link" class="-ml-4">{{ $t('dataset.common.dataLifeCycle') }}</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-80">
|
||||
{{ $t('dataset.tip.lifeCycle') }}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</Label>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="syncMode">{{ $t('dataset.common.syncMode') }}</Label>
|
||||
<Select v-model="formState.syncMode">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="$t('card.tip.roleHolder')"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MANUAL">{{ $t('dataset.common.syncModeManual') }}</SelectItem>
|
||||
<SelectItem value="TIMING">{{ $t('dataset.common.syncModeTiming') }}</SelectItem>
|
||||
<SelectItem value="OUT_SYNC">{{ $t('dataset.common.syncModeOutSync') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Alert v-if="formState.columns.filter(item => item.type === 'DATETIME').length === 0" variant="destructive" class="-mt-3">
|
||||
{{ $t('dataset.tip.lifeCycleMustDateColumn') }}
|
||||
</Alert>
|
||||
<div v-else class="grid grid-cols-2 gap-4 -mt-3">
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleColumn') }}</Label>
|
||||
<Select v-model="formState.lifeCycleColumn as string">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in formState.columns.filter(item => item.type === 'DATETIME')" :key="item.name" :value="item.name">
|
||||
{{ item.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleNumber') }}</Label>
|
||||
<Input :disabled="!formState.lifeCycleColumn" type="number" v-model="formState.lifeCycle as number"/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleNumber') }}</Label>
|
||||
<Select :disabled="!formState.lifeCycleColumn" v-model="formState.lifeCycleType as string">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MONTH">
|
||||
{{ $t('dataset.common.lifeCycleMonth') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="WEEK">
|
||||
{{ $t('dataset.common.lifeCycleWeek') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="DAY">
|
||||
{{ $t('dataset.common.lifeCycleDay') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="HOUR">
|
||||
{{ $t('dataset.common.lifeCycleHour') }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2" v-if="formState.syncMode === 'TIMING'">
|
||||
<Label for="syncMode">{{ $t('dataset.common.columnExpression') }}</Label>
|
||||
<Input v-model="formState.expression as string" placeholder="0 0 * * * ?"/>
|
||||
<Separator/>
|
||||
<div class="grid gap-2 -mt-3">
|
||||
<Label for="description">{{ $t('common.description') }}</Label>
|
||||
<Textarea v-model="formState.description as string"/>
|
||||
</div>
|
||||
<div class="grid gap-2" v-if="formState.syncMode === 'TIMING'">
|
||||
<Label for="syncMode">{{ $t('common.scheduler') }}</Label>
|
||||
<Select v-model="formState.scheduler">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in schedulers" :value="item">{{ item }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Separator/>
|
||||
<div class="grid gap-2 -mt-3">
|
||||
<Label for="description">
|
||||
<HoverCard>
|
||||
<HoverCardTrigger as-child>
|
||||
<Button variant="link" class="-ml-4">{{ $t('dataset.common.dataLifeCycle') }}</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-80">
|
||||
{{ $t('dataset.tip.lifeCycle') }}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</Label>
|
||||
</div>
|
||||
<Alert v-if="formState.columns.filter(item => item.type === 'DATETIME').length === 0" variant="destructive" class="-mt-3">
|
||||
{{ $t('dataset.tip.lifeCycleMustDateColumn') }}
|
||||
</Alert>
|
||||
<div v-else class="grid grid-cols-2 gap-4 -mt-3">
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleColumn') }}</Label>
|
||||
<Select v-model="formState.lifeCycleColumn as string">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in formState.columns.filter(item => item.type === 'DATETIME')" :key="item.name" :value="item.name">
|
||||
{{ item.name }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleNumber') }}</Label>
|
||||
<Input :disabled="!formState.lifeCycleColumn" type="number" v-model="formState.lifeCycle as number"/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label>{{ $t('dataset.common.lifeCycleNumber') }}</Label>
|
||||
<Select :disabled="!formState.lifeCycleColumn" v-model="formState.lifeCycleType as string">
|
||||
<SelectTrigger>
|
||||
<SelectValue/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MONTH">
|
||||
{{ $t('dataset.common.lifeCycleMonth') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="WEEK">
|
||||
{{ $t('dataset.common.lifeCycleWeek') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="DAY">
|
||||
{{ $t('dataset.common.lifeCycleDay') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="HOUR">
|
||||
{{ $t('dataset.common.lifeCycleHour') }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Separator/>
|
||||
<div class="grid gap-2 -mt-3">
|
||||
<Label for="description">{{ $t('common.description') }}</Label>
|
||||
<Textarea v-model="formState.description as string"/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
<div v-else class="mt-3 justify-center items-center">
|
||||
<div class="flex flex-col items-center space-y-3">
|
||||
<Alert variant="destructive" class="w-1/3">
|
||||
{{ i18n.t('dataset.common.onlyPreviewCreate') }}
|
||||
</Alert>
|
||||
<Button>
|
||||
<RouterLink to="/admin/query">
|
||||
{{ i18n.t('dataset.common.returnQuery') }}
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
<div v-else-if="!sourceInfo" class="mt-3 justify-center items-center">
|
||||
<div class="flex flex-col items-center space-y-3">
|
||||
<Alert variant="destructive" class="w-1/3">
|
||||
{{ i18n.t('dataset.common.onlyPreviewCreate') }}
|
||||
</Alert>
|
||||
<Button>
|
||||
<RouterLink to="/admin/query">
|
||||
{{ i18n.t('dataset.common.returnQuery') }}
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -262,8 +277,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapState } from 'vuex'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Button from '@/views/ui/button'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GridOptions from '@/views/components/grid/GridOptions'
|
||||
import DatasetService from '@/services/dataset'
|
||||
@ -272,7 +286,7 @@ import { GridColumn } from '@/views/components/grid/GridColumn'
|
||||
import PluginService from '@/services/plugin'
|
||||
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Alert } from '@/components/ui/alert';
|
||||
import { Alert } from '@/components/ui/alert'
|
||||
import CircularLoading from '@/views/components/loading/CircularLoading.vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
@ -281,16 +295,29 @@ import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Pencil, Plus, Trash } from 'lucide-vue-next'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import Card from '@/views/ui/card'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ToastUtils } from '@/utils/toast'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import 'ag-grid-community/styles/ag-grid.css'
|
||||
import '@/views/components/grid/ag-theme-datacap.css'
|
||||
import { DatasetModel } from '@/model/dataset'
|
||||
import { ResponseModel } from '@/model/response.ts'
|
||||
import AceEditor from '@/views/components/editor/AceEditor.vue'
|
||||
import { SourceModel } from '@/model/source.ts'
|
||||
import SourceService from '@/services/source'
|
||||
import ExecuteService from '@/services/execute'
|
||||
import { ExecuteModel } from '@/model/execute.ts'
|
||||
import { FormControl, FormField, FormItem } from '@/components/ui/form'
|
||||
import { ArrayUtils } from '@/utils/array.ts'
|
||||
import { join } from 'lodash'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DatasetInfo',
|
||||
components: {
|
||||
FormItem, FormField, FormControl,
|
||||
AceEditor,
|
||||
Separator,
|
||||
Textarea,
|
||||
Label,
|
||||
@ -304,13 +331,10 @@ export default defineComponent({
|
||||
Tabs, TabsContent, TabsList, TabsTrigger,
|
||||
Popover, PopoverContent, PopoverTrigger,
|
||||
Pencil, Trash, Plus,
|
||||
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
|
||||
Card,
|
||||
HoverCard, HoverCardContent, HoverCardTrigger,
|
||||
AgGridVue
|
||||
},
|
||||
computed: {
|
||||
...mapState(['data'])
|
||||
},
|
||||
setup()
|
||||
{
|
||||
const i18n = useI18n()
|
||||
@ -336,17 +360,21 @@ export default defineComponent({
|
||||
id: null,
|
||||
name: null as string | null | undefined,
|
||||
description: null as string | null | undefined,
|
||||
query: null,
|
||||
query: null as string | null,
|
||||
syncMode: 'MANUAL',
|
||||
columns: [] as any[],
|
||||
source: {id: null},
|
||||
source: { id: null },
|
||||
expression: null as string | null,
|
||||
scheduler: 'Default',
|
||||
executor: 'Default',
|
||||
lifeCycle: null as number | null,
|
||||
lifeCycleColumn: null as string | null,
|
||||
lifeCycleType: null as string | null
|
||||
}
|
||||
},
|
||||
data: null as ResponseModel | null,
|
||||
sourceInfo: null as SourceModel | null,
|
||||
value: '',
|
||||
running: false
|
||||
}
|
||||
},
|
||||
created()
|
||||
@ -358,70 +386,60 @@ export default defineComponent({
|
||||
{
|
||||
setTimeout(() => {
|
||||
PluginService.getPlugins()
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.schedulers = response.data['scheduler']
|
||||
this.executors = response.data['executor']
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.schedulers = response.data['scheduler']
|
||||
this.executors = response.data['executor']
|
||||
}
|
||||
})
|
||||
const code = this.$route.params.code
|
||||
const sourceCode = this.$route.params.sourceCode
|
||||
if (code) {
|
||||
this.loading = true
|
||||
this.code = code as string
|
||||
const axios = new HttpUtils().getAxios()
|
||||
axios.all([DatasetService.getByCode(this.code), DatasetService.getColumnsByCode(this.code)])
|
||||
.then(axios.spread((info, column) => {
|
||||
if (info.status) {
|
||||
this.formState = info.data
|
||||
this.formState.source.id = info.data.source.id
|
||||
}
|
||||
if (column.status) {
|
||||
this.formState.columns = column.data
|
||||
}
|
||||
}))
|
||||
.finally(() => this.loading = false)
|
||||
.then(axios.spread((info, column) => {
|
||||
if (info.status) {
|
||||
this.formState = info.data
|
||||
this.formState.source.id = info.data.source.id
|
||||
this.sourceInfo = info.data.source
|
||||
this.value = info.data.query
|
||||
this.handlerRun()
|
||||
}
|
||||
if (column.status) {
|
||||
this.formState.columns = column.data
|
||||
}
|
||||
}))
|
||||
.finally(() => this.loading = false)
|
||||
}
|
||||
else {
|
||||
this.formState.source.id = this.data?.sourceId
|
||||
this.formState.query = this.data?.query
|
||||
this.data?.headers.forEach((header: any, index: number) => {
|
||||
const columnDef: GridColumn = {headerName: header, field: header}
|
||||
this.columnDefs.push(columnDef)
|
||||
const column = {
|
||||
id: null,
|
||||
name: `column_${index + 1}`,
|
||||
aliasName: header.replace('(', '_').replace(')', ''),
|
||||
type: 'STRING',
|
||||
comment: header,
|
||||
defaultValue: null,
|
||||
position: index,
|
||||
nullable: false,
|
||||
length: 0,
|
||||
original: header,
|
||||
orderByKey: false,
|
||||
partitionKey: false,
|
||||
primaryKey: false,
|
||||
samplingKey: false,
|
||||
mode: 'DIMENSION',
|
||||
virtualColumn: false,
|
||||
customColumn: false
|
||||
}
|
||||
this.formState.columns.push(column)
|
||||
})
|
||||
else if (sourceCode) {
|
||||
this.loading = true
|
||||
SourceService.getByCode(sourceCode as string)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
this.sourceInfo = response.data
|
||||
this.formState.source.id = response.data.id
|
||||
}
|
||||
})
|
||||
.finally(() => this.loading = false)
|
||||
}
|
||||
})
|
||||
},
|
||||
handlerCreate()
|
||||
{
|
||||
this.saving = true
|
||||
DatasetService.saveOrUpdate(this.formState as unknown as DatasetModel)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
ToastUtils.success(`${this.$t('dataset.create')} [ ${this.formState.name} ] ${this.$t('common.success')}`)
|
||||
this.$router.push('/admin/dataset')
|
||||
}
|
||||
})
|
||||
.finally(() => this.saving = false)
|
||||
if (!this.beforeCheck()) {
|
||||
this.saving = true
|
||||
this.formState.query = this.value
|
||||
DatasetService.saveOrUpdate(this.formState as unknown as DatasetModel)
|
||||
.then(response => {
|
||||
if (response.status) {
|
||||
ToastUtils.success(`${ this.$t('dataset.tip.publishSuccess').replace('$VALUE', this.formState.name as string) }`)
|
||||
this.$router.push('/admin/dataset')
|
||||
}
|
||||
})
|
||||
.finally(() => this.saving = false)
|
||||
}
|
||||
},
|
||||
handlerAddColumn(index: number)
|
||||
{
|
||||
@ -449,10 +467,80 @@ export default defineComponent({
|
||||
{
|
||||
this.formState.columns.splice(index, 1)
|
||||
},
|
||||
handlerRun()
|
||||
{
|
||||
const configure: ExecuteModel = {
|
||||
content: this.value,
|
||||
name: this.sourceInfo?.id as unknown as string,
|
||||
mode: 'DATASET',
|
||||
format: 'JSON'
|
||||
}
|
||||
this.running = true
|
||||
ExecuteService.execute(configure, null)
|
||||
.then((response) => {
|
||||
if (response.status) {
|
||||
this.data = response
|
||||
response.data?.headers.forEach((header: any, index: number) => {
|
||||
const columnDef: GridColumn = { headerName: header, field: header }
|
||||
this.columnDefs.push(columnDef)
|
||||
if (this.formState.columns.length === 0) {
|
||||
const column = {
|
||||
id: null,
|
||||
name: `column_${ index + 1 }`,
|
||||
aliasName: header.replace('(', '_').replace(')', ''),
|
||||
type: 'STRING',
|
||||
comment: header,
|
||||
defaultValue: null,
|
||||
position: index,
|
||||
nullable: false,
|
||||
length: 0,
|
||||
original: header,
|
||||
orderByKey: false,
|
||||
partitionKey: false,
|
||||
primaryKey: false,
|
||||
samplingKey: false,
|
||||
mode: 'DIMENSION',
|
||||
virtualColumn: false,
|
||||
customColumn: false
|
||||
}
|
||||
this.formState.columns.push(column)
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
ToastUtils.error(response.message)
|
||||
}
|
||||
})
|
||||
.finally(() => this.running = false)
|
||||
},
|
||||
beforeCheck(): boolean
|
||||
{
|
||||
const duplicateColumns = ArrayUtils.findDuplicates(this.formState.columns)
|
||||
if (duplicateColumns.length > 0) {
|
||||
this.validator = true
|
||||
this.validatorMessage = this.$t('dataset.validator.duplicateColumn').replace('$VALUE', join(duplicateColumns, ','))
|
||||
return true
|
||||
}
|
||||
|
||||
const orderByColumns = this.formState.columns.filter(item => item.orderByKey)
|
||||
const primaryKeyColumns = this.formState.columns.filter(item => item.primaryKey)
|
||||
if (orderByColumns.length === 0 && primaryKeyColumns.length === 0) {
|
||||
this.validator = true
|
||||
this.validatorMessage = this.$t('dataset.validator.specifiedColumn')
|
||||
return true
|
||||
}
|
||||
|
||||
if (!this.formState.name) {
|
||||
this.validator = true
|
||||
this.validatorMessage = this.$t('dataset.validator.specifiedName')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
validatorSampling()
|
||||
{
|
||||
const samplingColumns = this.formState.columns
|
||||
.filter((item: { samplingKey: boolean; }) => item.samplingKey)
|
||||
.filter((item: { samplingKey: boolean; }) => item.samplingKey)
|
||||
if (samplingColumns.length === 0) {
|
||||
this.validator = false
|
||||
this.validatorMessage = null
|
||||
@ -460,9 +548,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const orderByColumns = this.formState.columns
|
||||
.filter((item: { orderByKey: boolean; }) => item.orderByKey)
|
||||
.filter((item: { orderByKey: boolean; }) => item.orderByKey)
|
||||
const isNameInOrderByColumns = samplingColumns.every((samplingItem: { name: string; }) => {
|
||||
return orderByColumns.some((orderByItem: { name: string; }) => orderByItem.name === samplingItem.name);
|
||||
return orderByColumns.some((orderByItem: { name: string; }) => orderByItem.name === samplingItem.name)
|
||||
})
|
||||
if (!isNameInOrderByColumns) {
|
||||
this.validator = true
|
||||
@ -507,5 +595,5 @@ export default defineComponent({
|
||||
this.validatorSampling()
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
@ -16,6 +16,8 @@
|
||||
<Tag v-else-if="row.mode === 'HISTORY'">{{ $t('common.history') }}</Tag>
|
||||
<Tag v-else-if="row.mode === 'REPORT'">{{ $t('common.report') }}</Tag>
|
||||
<Tag v-else-if="row.mode === 'SNIPPET'">{{ $t('common.snippet') }}</Tag>
|
||||
<Tag v-else-if="row.mode === 'DATASET'">{{ $t('common.dataset') }}</Tag>
|
||||
<Tag v-else>{{ row.mode }}</Tag>
|
||||
</template>
|
||||
<template #state="{ row }">
|
||||
<Tag :class="row.state === 'SUCCESS' ? '' : 'bg-color-error'">{{ row.state }}</Tag>
|
||||
|
@ -177,7 +177,8 @@ export default defineComponent({
|
||||
selectSource: {
|
||||
id: null as string | null | undefined,
|
||||
type: null as string | null | undefined,
|
||||
engine: null as string | null | undefined
|
||||
engine: null as string | null | undefined,
|
||||
code: null as string | null | undefined
|
||||
},
|
||||
selectEditor: {
|
||||
editorMaps: new Map<string, EditorInstance>(),
|
||||
@ -259,6 +260,7 @@ export default defineComponent({
|
||||
this.selectSource.id = idAndType[0]
|
||||
this.selectSource.type = idAndType[1]
|
||||
this.selectSource.engine = idAndType[1]
|
||||
this.selectSource.code = idAndType[2]
|
||||
const instance = this.selectEditor.editorMaps.get(this.selectEditor.activeKey as string)
|
||||
if (instance) {
|
||||
this.handlerEditorDidMount(instance.instance as any, idAndType[1])
|
||||
@ -396,7 +398,8 @@ export default defineComponent({
|
||||
width: editorContainer.offsetWidth + 20,
|
||||
showSeriesNumber: false,
|
||||
sourceId: this.selectSource.id as unknown as number,
|
||||
query: content
|
||||
query: content,
|
||||
code: this.selectSource.code as string
|
||||
}
|
||||
this.responseConfigure.gridConfigure = tConfigure
|
||||
editorInstance.instance?.setValue(response.data.content)
|
||||
|
@ -57,17 +57,17 @@ export default defineComponent({
|
||||
handlerInitialize()
|
||||
{
|
||||
this.loading = true
|
||||
const axios = new HttpUtils().getAxios();
|
||||
const axios = new HttpUtils().getAxios()
|
||||
axios.all([UserService.getSourceCount(), UserService.getQueryCount()])
|
||||
.then(axios.spread((source, query) => {
|
||||
if (source.status) {
|
||||
this.summary.sourceCount = source.data
|
||||
}
|
||||
if (query.status) {
|
||||
this.summary.queryCount = query.data
|
||||
}
|
||||
}))
|
||||
.finally(() => this.loading = false)
|
||||
.then(axios.spread((source, query) => {
|
||||
if (source.status) {
|
||||
this.summary.sourceCount = source.data
|
||||
}
|
||||
if (query.status) {
|
||||
this.summary.queryCount = query.data
|
||||
}
|
||||
}))
|
||||
.finally(() => this.loading = false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -14,17 +14,20 @@
|
||||
<CardContent :class="`${bodyClass}`">
|
||||
<slot/>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<slot name="footer"/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DcCard',
|
||||
components: {
|
||||
Card, CardContent, CardHeader, CardTitle
|
||||
CardFooter, Card, CardContent, CardHeader, CardTitle
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
|
68
core/datacap-ui/src/views/ui/carousel/carousel.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<Carousel :orientation="orientation as any" :plugins="[Autoplay({delay: delay})]">
|
||||
<CarouselContent>
|
||||
<CarouselItem v-for="item in items">
|
||||
<div v-if="item.isAlert">
|
||||
<DcLink :external="item.external" :link="item.link">
|
||||
<Alert :title="item.title"/>
|
||||
</DcLink>
|
||||
</div>
|
||||
<div v-else>
|
||||
<DcLink :external="item.external" :link="item.link"/>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
</CarouselContent>
|
||||
<CarouselPrevious v-if="previous"/>
|
||||
<CarouselNext v-if="next"/>
|
||||
</Carousel>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel'
|
||||
import Autoplay from 'embla-carousel-autoplay'
|
||||
import { CarouselModel } from './model.ts'
|
||||
import Alert from '@/views/ui/alert'
|
||||
import DcLink from '@/views/components/link/DcLink.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DcCarousel',
|
||||
computed: {
|
||||
Autoplay()
|
||||
{
|
||||
return Autoplay
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DcLink,
|
||||
Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious,
|
||||
Alert
|
||||
},
|
||||
props: {
|
||||
autoPlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
previous: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
next: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
orientation: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
items: {
|
||||
type: Array as () => CarouselModel[],
|
||||
default: () => new Array<CarouselModel>()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
3
core/datacap-ui/src/views/ui/carousel/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Carousel from '@/views/ui/carousel/carousel.vue'
|
||||
|
||||
export default Carousel
|
9
core/datacap-ui/src/views/ui/carousel/model.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface CarouselModel
|
||||
{
|
||||
title?: string
|
||||
description?: string
|
||||
image?: string
|
||||
link?: string
|
||||
external?: boolean
|
||||
isAlert?: boolean
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="flex items-center space-x-4 text-sm flex-row w-full overflow-hidden">
|
||||
<Separator :class="cn(orientation === Position.left && 'w-10')"/>
|
||||
<Separator :class="cn(position === Position.left && 'w-10')" :orientation="orientation as any"/>
|
||||
<div class="flex items-center flex-grow">
|
||||
<slot name="content"/>
|
||||
</div>
|
||||
<Separator :class="cn(orientation === Position.right && 'w-10')"/>
|
||||
<Separator :class="cn(position === Position.right && 'w-10')" :orientation="orientation as any"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -23,6 +23,9 @@ export default defineComponent({
|
||||
name: 'DcDivider',
|
||||
components: { Separator },
|
||||
props: {
|
||||
position: {
|
||||
type: String
|
||||
},
|
||||
orientation: {
|
||||
type: String
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
|
@ -1,4 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
@ -1,5 +0,0 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
@ -1,22 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/typescript/recommended'
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-array-constructor": "off",
|
||||
"vue/no-mutating-props": "off",
|
||||
'@typescript-eslint/no-this-alias': 'off'
|
||||
}
|
||||
}
|
23
core/datacap-web/.gitignore
vendored
@ -1,23 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"name": "datacap-console",
|
||||
"description": "DataCap console",
|
||||
"version": "2024.3.1-SNAPSHOT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"report": "vue-cli-service build --report",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/x6": "^2.16.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.0-5",
|
||||
"@kangc/v-md-editor": "^2.3.15",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@visactor/vchart": "^1.8.6",
|
||||
"@visactor/vtable": "^0.17.8",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "^1.1.0",
|
||||
"@vue-flow/core": "^1.27.1",
|
||||
"@vue-flow/node-resizer": "^1.3.6",
|
||||
"ace-builds": "^1.30.0",
|
||||
"ag-grid-community": "^29.3.5",
|
||||
"ag-grid-vue3": "^29.3.5",
|
||||
"ansi_up": "^6.0.2",
|
||||
"axios": "^0.27.2",
|
||||
"echarts": "^5.4.0",
|
||||
"export-to-csv": "^0.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"uuid": "^9.0.1",
|
||||
"view-ui-plus": "^1.3.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.0.3",
|
||||
"vue3-ace-editor": "^2.2.3",
|
||||
"vue3-calendar-heatmap": "^2.0.5",
|
||||
"vue3-grid-layout-next": "^1.0.6",
|
||||
"vue3-markdown": "^1.1.7",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.1.0",
|
||||
"watermark-dom": "^2.3.0",
|
||||
"webpack-bundle-analyzer": "^4.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.189",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/eslint-config-standard": "^6.1.0",
|
||||
"@vue/eslint-config-typescript": "^9.1.0",
|
||||
"core-js": "^3.8.3",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"typescript": "~4.5.5"
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>static/images/logo.png">
|
||||
<!-- <title><%= htmlWebpackPlugin.options.title %></title>-->
|
||||
<title>DataCap</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 218 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 151 KiB |
Before Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 245 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 3.0 KiB |