Merge branch 'dev-v2' into pr@dev-v2@refactor_recommond-template

This commit is contained in:
王嘉豪 2024-01-16 16:00:36 +08:00 committed by GitHub
commit 028f6170bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 3350 additions and 218 deletions

View File

@ -13,10 +13,11 @@ WORKDIR /opt/apps
ADD core/core-backend/target/CoreApplication.jar /opt/apps/app.jar
ADD de-xpack/xpack-permissions/target/xpack-permissions-$IMAGE_TAG.jar /opt/apps/xpack-permission.jar
ADD de-xpack/xpack-base/target/xpack-base-$IMAGE_TAG.jar /opt/apps/xpack-base.jar
ADD de-xpack/xpack-sync/target/xpack-sync-$IMAGE_TAG.jar /opt/apps/xpack-sync.jar
ENV JAVA_APP_JAR=/opt/apps/app.jar
HEALTHCHECK --interval=15s --timeout=5s --retries=20 --start-period=30s CMD curl -f 127.0.0.1:8100
CMD java -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dataease2.0/logs/dump.hprof -Dloader.path=/opt/apps/xpack-permission.jar,/opt/apps/xpack-base.jar -jar /opt/apps/app.jar
CMD java -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dataease2.0/logs/dump.hprof -Dloader.path=/opt/apps/xpack-permission.jar,/opt/apps/xpack-base.jar,/opt/apps/xpack-sync.jar -jar /opt/apps/app.jar

View File

@ -23,6 +23,11 @@
<artifactId>api-permissions</artifactId>
<version>${dataease.version}</version>
</dependency>
<dependency>
<groupId>io.dataease</groupId>
<artifactId>api-sync</artifactId>
<version>${dataease.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>

View File

@ -60,12 +60,13 @@ public class SqlparserUtils {
}
private static void getDependencies(SqlNode sqlNode, Boolean fromOrJoin) {
if (sqlNode == null) {
return;
}
if (sqlNode.getKind() == JOIN) {
SqlJoin sqlKind = (SqlJoin) sqlNode;
} else if (sqlNode.getKind() == IDENTIFIER) {
} else if (sqlNode.getKind() == AS) {
SqlBasicCall sqlKind = (SqlBasicCall) sqlNode;
} else if (sqlNode.getKind() == SELECT) {
@ -80,9 +81,19 @@ public class SqlparserUtils {
SqlNode newWhere = sqlKind.getWhere().accept(getSqlShuttle());
sqlKind.setWhere(newWhere);
}
} else {
// TODO 这里可根据需求拓展处理其他类型的 sqlNode
} else if (sqlNode.getKind() == ORDER_BY) {
SqlOrderBy sqlKind = (SqlOrderBy) sqlNode;
List<SqlNode> operandList = sqlKind.getOperandList();
for (int i = 0; i < operandList.size(); i++) {
getDependencies(operandList.get(i), false);
}
} else if (sqlNode.getKind() == UNION) {
SqlBasicCall sqlKind = (SqlBasicCall) sqlNode;
if (sqlKind.getOperandList().size() >= 2) {
for (int i = 0; i < sqlKind.getOperandList().size(); i++) {
getDependencies(sqlKind.getOperandList().get(i), false);
}
}
}
}

View File

@ -136,6 +136,9 @@ public class DatasourceServer implements DatasourceApi {
dataSourceManage.move(dataSourceDTO);
}
case "rename" -> {
if(StringUtils.isEmpty(dataSourceDTO.getName())){
DEException.throwException("名称不能为空!");
}
CoreDatasource datasource = datasourceMapper.selectById(dataSourceDTO.getId());
datasource.setName(dataSourceDTO.getName());
dataSourceManage.innerEdit(datasource);
@ -464,8 +467,9 @@ public class DatasourceServer implements DatasourceApi {
CoreDatasource coreDatasource = new CoreDatasource();
BeanUtils.copyBean(coreDatasource, dataSourceDTO);
checkDatasourceStatus(coreDatasource);
dataSourceDTO.setStatus(coreDatasource.getStatus());
return dataSourceDTO;
DatasourceDTO result = new DatasourceDTO();
result.setStatus(coreDatasource.getStatus());
return result;
}
@Override

View File

@ -0,0 +1,150 @@
package io.dataease.share.dao.auto.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author fit2cloud
* @since 2023-09-22
*/
@TableName("xpack_share")
public class XpackShare implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 创建人
*/
private Long creator;
/**
* 创建时间
*/
private Long time;
/**
* 过期时间
*/
private Long exp;
/**
* uuid
*/
private String uuid;
/**
* 密码
*/
private String pwd;
/**
* 资源ID
*/
private Long resourceId;
/**
* 组织ID
*/
private Long oid;
/**
* 业务类型
*/
private Integer type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCreator() {
return creator;
}
public void setCreator(Long creator) {
this.creator = creator;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
public Long getExp() {
return exp;
}
public void setExp(Long exp) {
this.exp = exp;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public Long getResourceId() {
return resourceId;
}
public void setResourceId(Long resourceId) {
this.resourceId = resourceId;
}
public Long getOid() {
return oid;
}
public void setOid(Long oid) {
this.oid = oid;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
@Override
public String toString() {
return "XpackShare{" +
"id = " + id +
", creator = " + creator +
", time = " + time +
", exp = " + exp +
", uuid = " + uuid +
", pwd = " + pwd +
", resourceId = " + resourceId +
", oid = " + oid +
", type = " + type +
"}";
}
}

View File

@ -0,0 +1,18 @@
package io.dataease.share.dao.auto.mapper;
import io.dataease.share.dao.auto.entity.XpackShare;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2023-09-22
*/
@Mapper
public interface XpackShareMapper extends BaseMapper<XpackShare> {
}

View File

@ -0,0 +1,30 @@
package io.dataease.share.dao.ext.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.dataease.share.dao.ext.po.XpackSharePO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface XpackShareExtMapper {
@Select("""
select
s.id as share_id,
v.id as resource_id,
v.type,
s.creator,
s.time,
s.exp,
v.name
from xpack_share s
left join data_visualization_info v on s.resource_id = v.id
${ew.customSqlSegment}
""")
IPage<XpackSharePO> query(IPage<XpackSharePO> page, @Param("ew") QueryWrapper<Object> ew);
@Select("select type from data_visualization_info where id = #{id}")
String visualizationType(@Param("id") Long id);
}

View File

@ -0,0 +1,31 @@
package io.dataease.share.dao.ext.po;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class XpackSharePO implements Serializable {
@Serial
private static final long serialVersionUID = 7929343371768885789L;
private Long shareId;
private Long resourceId;
private String name;
private String type;
private Long creator;
private Long time;
private Long exp;
}

View File

@ -0,0 +1,201 @@
package io.dataease.share.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
import io.dataease.auth.bo.TokenUserBO;
import io.dataease.constant.AuthConstant;
import io.dataease.constant.BusiResourceEnum;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.share.dao.auto.mapper.XpackShareMapper;
import io.dataease.utils.*;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.dao.ext.mapper.XpackShareExtMapper;
import io.dataease.share.dao.ext.po.XpackSharePO;
import io.dataease.share.util.LinkTokenUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component("xpackShareManage")
public class XpackShareManage {
@Resource(name = "xpackShareMapper")
private XpackShareMapper xpackShareMapper;
@Resource(name = "xpackShareExtMapper")
private XpackShareExtMapper xpackShareExtMapper;
public XpackShare queryByResource(Long resourceId) {
Long userId = AuthUtils.getUser().getUserId();
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", userId);
queryWrapper.eq("resource_id", resourceId);
return xpackShareMapper.selectOne(queryWrapper);
}
public void switcher(Long resourceId) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isNotEmpty(originData)) {
xpackShareMapper.deleteById(originData.getId());
return;
}
TokenUserBO user = AuthUtils.getUser();
Long userId = user.getUserId();
XpackShare xpackShare = new XpackShare();
xpackShare.setId(IDUtils.snowID());
xpackShare.setCreator(userId);
xpackShare.setTime(System.currentTimeMillis());
xpackShare.setResourceId(resourceId);
xpackShare.setUuid(RandomStringUtils.randomAlphanumeric(8));
xpackShare.setOid(user.getDefaultOid());
String dType = xpackShareExtMapper.visualizationType(resourceId);
xpackShare.setType(StringUtils.equalsIgnoreCase("dataV", dType) ? 2 : 1);
xpackShareMapper.insert(xpackShare);
}
public void editExp(Long resourceId, Long exp) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isEmpty(originData)) {
DEException.throwException("share instance not exist");
}
originData.setExp(exp);
if (ObjectUtils.isEmpty(exp)) {
originData.setExp(0L);
}
xpackShareMapper.updateById(originData);
}
public void editPwd(Long resourceId, String pwd) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isEmpty(originData)) {
DEException.throwException("share instance not exist");
}
originData.setPwd(pwd);
xpackShareMapper.updateById(originData);
}
public IPage<XpackSharePO> querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("s.creator", uid);
if (StringUtils.isNotBlank(request.getType())) {
BusiResourceEnum busiResourceEnum = BusiResourceEnum.valueOf(request.getType().toUpperCase());
if (ObjectUtils.isEmpty(busiResourceEnum)) {
DEException.throwException("type is invalid");
}
String resourceType = convertResourceType(request.getType());
if (StringUtils.isNotBlank(resourceType)) {
queryWrapper.eq("v.type", resourceType);
}
}
if (StringUtils.isNotBlank(request.getKeyword())) {
queryWrapper.like("v.name", request.getKeyword());
}
queryWrapper.orderBy(true, request.isAsc(), "s.time");
Page<XpackSharePO> page = new Page<>(goPage, pageSize);
return xpackShareExtMapper.query(page, queryWrapper);
}
private String convertResourceType(String busiFlag) {
return switch (busiFlag) {
case "panel" -> "dashboard";
case "screen" -> "dataV";
default -> null;
};
}
@XpackInteract(value = "perFilterShareManage", recursion = true)
public IPage<XpackShareGridVO> query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) {
IPage<XpackSharePO> poiPage = proxy().querySharePage(pageNum, pageSize, request);
List<XpackShareGridVO> vos = proxy().formatResult(poiPage.getRecords());
IPage<XpackShareGridVO> ipage = new Page<>();
ipage.setSize(poiPage.getSize());
ipage.setCurrent(poiPage.getCurrent());
ipage.setPages(poiPage.getPages());
ipage.setTotal(poiPage.getTotal());
ipage.setRecords(vos);
return ipage;
}
public List<XpackShareGridVO> formatResult(List<XpackSharePO> pos) {
if (CollectionUtils.isEmpty(pos)) return new ArrayList<>();
return pos.stream().map(po ->
new XpackShareGridVO(
po.getShareId(), po.getResourceId(), po.getName(), po.getCreator().toString(),
po.getTime(), po.getExp(), 9)).toList();
}
private XpackShareManage proxy() {
return CommonBeanFactory.getBean(this.getClass());
}
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("uuid", request.getUuid());
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare))
return null;
String linkToken = LinkTokenUtil.generate(xpackShare.getCreator(), xpackShare.getResourceId(), xpackShare.getExp(), xpackShare.getPwd(), xpackShare.getOid());
HttpServletResponse response = ServletUtils.response();
response.addHeader(AuthConstant.LINK_TOKEN_KEY, linkToken);
Integer type = xpackShare.getType();
String typeText = (ObjectUtils.isNotEmpty(type) && type == 1) ? "dashboard" : "dataV";
return new XpackShareProxyVO(xpackShare.getResourceId(), xpackShare.getCreator(), linkExp(xpackShare), pwdValid(xpackShare, request.getCiphertext()), typeText);
}
private boolean linkExp(XpackShare xpackShare) {
if (ObjectUtils.isEmpty(xpackShare.getExp()) || xpackShare.getExp().equals(0L)) return false;
return System.currentTimeMillis() > xpackShare.getExp();
}
private boolean pwdValid(XpackShare xpackShare, String ciphertext) {
if (StringUtils.isBlank(xpackShare.getPwd())) return true;
if (StringUtils.isBlank(ciphertext)) return false;
String text = RsaUtils.decryptStr(ciphertext);
int len = text.length();
int splitIndex = len - 4;
String pwd = text.substring(splitIndex);
String uuid = text.substring(0, splitIndex);
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
}
public boolean validatePwd(XpackSharePwdValidator validator) {
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
int len = ciphertext.length();
int splitIndex = len - 4;
String pwd = ciphertext.substring(splitIndex);
String uuid = ciphertext.substring(0, splitIndex);
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("uuid", uuid);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
}
public Map<String, String> queryRelationByUserId(Long uid) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", uid);
List<XpackShare> result = xpackShareMapper.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(result)) {
return result.stream()
.collect(Collectors.toMap(xpackShare -> String.valueOf(xpackShare.getResourceId()), XpackShare::getUuid));
}
return new HashMap<>();
}
}

View File

@ -0,0 +1,76 @@
package io.dataease.share.server;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.xpack.share.XpackShareApi;
import io.dataease.api.xpack.share.request.XpackShareExpRequest;
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
import io.dataease.api.xpack.share.vo.XpackShareVO;
import io.dataease.utils.BeanUtils;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.manage.XpackShareManage;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RequestMapping("/share")
@RestController
public class XpackShareServer implements XpackShareApi {
@Resource(name = "xpackShareManage")
private XpackShareManage xpackShareManage;
@Override
public boolean status(Long resourceId) {
return ObjectUtils.isNotEmpty(xpackShareManage.queryByResource(resourceId));
}
@Override
public void switcher(Long resourceId) {
xpackShareManage.switcher(resourceId);
}
@Override
public void editExp(XpackShareExpRequest request) {
xpackShareManage.editExp(request.getResourceId(), request.getExp());
}
@Override
public void editPwd(XpackSharePwdRequest request) {
xpackShareManage.editPwd(request.getResourceId(), request.getPwd());
}
@Override
public XpackShareVO detail(Long resourceId) {
XpackShare xpackShare = xpackShareManage.queryByResource(resourceId);
if (ObjectUtils.isEmpty(xpackShare)) return null;
return BeanUtils.copyBean(new XpackShareVO(), xpackShare);
}
@Override
public List<XpackShareGridVO> query(VisualizationWorkbranchQueryRequest request) {
return xpackShareManage.query(1, 20, request).getRecords();
}
@Override
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {
return xpackShareManage.proxyInfo(request);
}
@Override
public boolean validatePwd(XpackSharePwdValidator validator) {
return xpackShareManage.validatePwd(validator);
}
@Override
public Map<String, String> queryRelationByUserId(@PathVariable("uid") Long uid) {
return xpackShareManage.queryRelationByUserId(uid);
}
}

View File

@ -0,0 +1,23 @@
package io.dataease.share.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
public class LinkTokenUtil {
private static final String defaultPwd = "link-pwd-fit2cloud";
public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) {
pwd = StringUtils.isBlank(pwd) ? defaultPwd : pwd;
Algorithm algorithm = Algorithm.HMAC256(pwd);
JWTCreator.Builder builder = JWT.create();
builder.withClaim("uid", uid).withClaim("resourceId", resourceId).withClaim("oid", oid);
if (ObjectUtils.isNotEmpty(exp) && !exp.equals(0L)) {
builder = builder.withExpiresAt(new Date(exp));
}
return builder.sign(algorithm);
}
}

View File

@ -3,10 +3,12 @@ package io.dataease.system.server;
import io.dataease.api.system.SysParameterApi;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.constant.XpackSettingConstants;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.manage.SysParameterManage;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -18,6 +20,7 @@ public class SysParameterServer implements SysParameterApi {
@Resource
private SysParameterManage sysParameterManage;
@Override
public String singleVal(String key) {
return sysParameterManage.singleVal(key);
@ -45,4 +48,17 @@ public class SysParameterServer implements SysParameterApi {
public void saveBasicSetting(List<SettingItemVO> settingItemVOS) {
sysParameterManage.saveBasic(settingItemVOS);
}
@Override
public Integer RequestTimeOut() {
Integer frontTimeOut = 60;
List<SettingItemVO> settingItemVOS = queryBasicSetting();
for (int i = 0; i < settingItemVOS.size(); i++) {
SettingItemVO settingItemVO = settingItemVOS.get(i);
if (StringUtils.isNotBlank(settingItemVO.getPkey()) && settingItemVO.getPkey().equalsIgnoreCase(XpackSettingConstants.Front_Time_Out) && StringUtils.isNotBlank(settingItemVO.getPval())) {
frontTimeOut = Integer.parseInt(settingItemVO.getPval());
}
}
return frontTimeOut;
}
}

View File

@ -46,6 +46,7 @@ quartz:
dataease:
version: '@project.version@'
origin-list: http://192.168.2.70:9080
apisix-api:
domain: http://192.168.0.121:9180
key: edd1c9f034335f136f87ad84b625c8f1

View File

@ -15,3 +15,4 @@ ALTER TABLE `visualization_template`
ADD COLUMN `use_count` int NULL DEFAULT 0 COMMENT '使用次数' AFTER `dynamic_data`;
update visualization_template set use_count = 0;
INSERT INTO `core_sys_setting` (`id`, `pkey`, `pval`, `type`, `sort`) VALUES (9, 'basic.frontTimeOut', '60', 'text', 1);

View File

@ -1,50 +1,5 @@
ALTER TABLE `QRTZ_BLOB_TRIGGERS` COMMENT = '自定义触发器存储开源作业调度框架Quartz';
ALTER TABLE `QRTZ_CALENDARS` COMMENT = 'Quartz日历开源作业调度框架Quartz';
ALTER TABLE `QRTZ_CRON_TRIGGERS` COMMENT = 'CronTrigger存储开源作业调度框架Quartz';
ALTER TABLE `QRTZ_FIRED_TRIGGERS` COMMENT = '存储已经触发的trigger相关信息开源作业调度框架Quartz';
ALTER TABLE `QRTZ_JOB_DETAILS` COMMENT = '存储jobDetails信息开源作业调度框架Quartz';
ALTER TABLE `QRTZ_LOCKS` COMMENT = 'Quartz锁表为多个节点调度提供分布式锁开源作业调度框架Quartz';
ALTER TABLE `QRTZ_PAUSED_TRIGGER_GRPS` COMMENT = '存放暂停掉的触发器开源作业调度框架Quartz';
ALTER TABLE `QRTZ_SCHEDULER_STATE` COMMENT = '存储所有节点的scheduler开源作业调度框架Quartz';
ALTER TABLE `QRTZ_SIMPLE_TRIGGERS` COMMENT = 'SimpleTrigger存储开源作业调度框架Quartz';
ALTER TABLE `QRTZ_SIMPROP_TRIGGERS` COMMENT = '存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器开源作业调度框架Quartz';
ALTER TABLE `QRTZ_TRIGGERS` COMMENT = '存储定义的trigger开源作业调度框架Quartz';
ALTER TABLE `area` COMMENT = '地图区域表';
ALTER TABLE `core_area_custom` COMMENT = '自定义地图区域信息表';
ALTER TABLE `core_chart_view` COMMENT = '组件视图表';
ALTER TABLE `core_dataset_group` COMMENT = '数据集分组表';
ALTER TABLE `core_dataset_table` COMMENT = 'table数据集';
ALTER TABLE `core_dataset_table_field` COMMENT = 'table数据集表字段';
ALTER TABLE `core_dataset_table_sql_log` COMMENT = 'table数据集查询sql日志';
ALTER TABLE `core_datasource` COMMENT = '数据源表';
ALTER TABLE `core_datasource_task` COMMENT = '数据源定时同步任务';
ALTER TABLE `core_datasource_task_log` COMMENT = '数据源定时同步任务执行日志';
ALTER TABLE `core_de_engine` COMMENT = '数据引擎';
ALTER TABLE `core_driver` COMMENT = '驱动';
ALTER TABLE `core_driver_jar` COMMENT = '驱动详情';
ALTER TABLE `core_menu` COMMENT = '路由菜单';
ALTER TABLE `core_opt_recent` COMMENT = '可视化资源表';
ALTER TABLE `core_rsa` COMMENT = 'rsa 密钥表';
ALTER TABLE `core_store` COMMENT = '用户收藏表';
ALTER TABLE `core_sys_setting` COMMENT = '系统设置表';
ALTER TABLE `data_visualization_info` COMMENT = '可视化大屏信息表';
ALTER TABLE `de_standalone_version` COMMENT = '数据库版本变更记录表';
ALTER TABLE `license` COMMENT = '企业版许可证信息表';
ALTER TABLE `per_api_key` COMMENT = 'API Key 密钥表';
ALTER TABLE `per_auth_busi_role` COMMENT = '角色资源权限配置';
ALTER TABLE `per_auth_busi_user` COMMENT = '用户资源权限配置';
ALTER TABLE `per_auth_menu` COMMENT = '菜单资源权限配置';
ALTER TABLE `per_busi_resource` COMMENT = '企业资源';
ALTER TABLE `per_dataset_column_permissions` COMMENT = '数据集列权限';
ALTER TABLE `per_dataset_row_permissions_tree` COMMENT = '数据集行权限';
ALTER TABLE `per_embedded_instance` COMMENT = '嵌入式应用';
ALTER TABLE `per_menu_resource` COMMENT = '菜单资源';
ALTER TABLE `per_org` COMMENT = '组织机构';
ALTER TABLE `per_role` COMMENT = '角色';
ALTER TABLE `per_sys_setting` COMMENT = '系统设置表';
ALTER TABLE `per_user` COMMENT = '用户表';
ALTER TABLE `per_user_role` COMMENT = '用户角色表';
ALTER TABLE `visualization_background` COMMENT = '边框背景表';
/*
ALTER TABLE `visualization_background_image` COMMENT = '背景图';
ALTER TABLE `visualization_link_jump` COMMENT = '跳转记录表';
ALTER TABLE `visualization_link_jump_info` COMMENT = '跳转配置表';
@ -53,8 +8,6 @@ ALTER TABLE `visualization_linkage` COMMENT = '联动记录表';
ALTER TABLE `visualization_linkage_field` COMMENT = '联动字段';
ALTER TABLE `visualization_subject` COMMENT = '主题表';
ALTER TABLE `visualization_template_extend_data` COMMENT = '模板视图明细信息表';
ALTER TABLE `xpack_setting_authentication` COMMENT = '认证设置';
ALTER TABLE `xpack_share` COMMENT = '公共链接';
ALTER TABLE `core_dataset_group`
MODIFY COLUMN `qrtz_instance` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'Quartz 实例 ID' AFTER `create_time`;
@ -91,20 +44,7 @@ ALTER TABLE `de_standalone_version`
MODIFY COLUMN `execution_time` int(0) NOT NULL COMMENT '执行时长' AFTER `installed_on`,
MODIFY COLUMN `success` tinyint(1) NOT NULL COMMENT '状态1-成功0-失败' AFTER `execution_time`;
ALTER TABLE `license`
MODIFY COLUMN `id` bigint(0) NOT NULL COMMENT '主键' FIRST,
MODIFY COLUMN `update_time` bigint(0) NULL DEFAULT NULL COMMENT '更新时间' AFTER `id`,
MODIFY COLUMN `license` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'license' AFTER `update_time`,
MODIFY COLUMN `f2c_license` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'F2C License' AFTER `license`;
ALTER TABLE `per_dataset_column_permissions`
MODIFY COLUMN `update_time` bigint(0) NULL DEFAULT NULL COMMENT '更新时间' AFTER `white_list_user`;
ALTER TABLE `per_dataset_row_permissions_tree`
MODIFY COLUMN `update_time` bigint(0) NULL DEFAULT NULL COMMENT '更新时间' AFTER `white_list_dept`;
ALTER TABLE `per_user`
MODIFY COLUMN `pwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码' AFTER `account`;
ALTER TABLE `visualization_background`
MODIFY COLUMN `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键' FIRST,
@ -175,3 +115,5 @@ ALTER TABLE `visualization_template_extend_data`
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_type` int(0) NOT NULL COMMENT '资源类型 1-可视化资源 2-仪表板 3-数据大屏 4-数据集 5-数据源 6-模板' AFTER `uid`;
*/

View File

@ -15,3 +15,4 @@ ALTER TABLE `visualization_template`
ADD COLUMN `use_count` int NULL DEFAULT 0 COMMENT '使用次数' AFTER `dynamic_data`;
update visualization_template set use_count = 0;
INSERT INTO `core_sys_setting` (`id`, `pkey`, `pval`, `type`, `sort`) VALUES (9, 'basic.frontTimeOut', '60', 'text', 1);

View File

@ -20,7 +20,7 @@ i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406
i18n_menu.auth=\u6743\u9650\u914D\u7F6E
i18n_menu.sync=\u540C\u6B65\u7BA1\u7406
i18n_menu.summary=\u6982\u89C8
i18n_menu.ds=\u6570\u636E\u6E90\u7BA1\u7406
i18n_menu.ds=\u6570\u636e\u8fde\u63a5\u7ba1\u7406
i18n_menu.task=\u4EFB\u52A1\u7BA1\u7406
i18n_menu.embedded=\u5D4C\u5165\u5F0F\u7BA1\u7406
i18n_menu.platform=\u5E73\u53F0\u5BF9\u63A5

View File

@ -111,6 +111,14 @@ export const getDatasetTree = async (data = {}): Promise<IResponse> => {
})
}
export const getDsTree = async (data = {}): Promise<IResponse> => {
return request
.post({ url: '/datasource/tree', data: { ...data, ...{ busiFlag: 'datasource' } } })
.then(res => {
return res?.data
})
}
export const deleteById = (id: number) => request.get({ url: '/datasource/delete/' + id })
export const getById = (id: number) => request.get({ url: '/datasource/get/' + id })

View File

@ -0,0 +1,47 @@
import request from '@/config/axios'
export const sourceDsPageApi = (page: number, limit: number, data) => {
return request.post({ url: `/sync/datasource/source/pager/${page}/${limit}`, data })
}
export const targetDsPageApi = (page: number, limit: number, data) => {
return request.post({ url: `/sync/datasource/target/pager/${page}/${limit}`, data })
}
export const latestUseApi = () => {
return request.post({ url: '/sync/datasource/latestUse', data: {} })
}
export const validateApi = data => {
return request.post({ url: '/sync/datasource/validate', data })
}
export const getSchemaApi = data => {
return request.post({ url: '/sync/datasource/getSchema', data })
}
export const saveApi = data => {
return request.post({ url: '/sync/datasource/save', data })
}
export const getByIdApi = (id: string) => {
return request.get({ url: `/sync/datasource/get/${id}` })
}
export const updateApi = data => {
return request.post({ url: '/sync/datasource/update', data })
}
export const deleteByIdApi = (id: string) => {
return request.post({ url: `/sync/datasource/delete/${id}` })
}
export const batchDelApi = (ids: string[]) => {
return request.post({ url: `/sync/datasource/batchDel`, data: ids })
}
/**
* 获取源数据库字段集合以及目标数据库数据类型集合
*/
export const getFieldListApi = data => {
return request.post({ url: `/sync/datasource/fields`, data })
}

View File

@ -0,0 +1,26 @@
import request from '@/config/axios'
interface IResourceCount {
jobCount: number
datasourceCount: number
jobLogCount: number
}
export const getResourceCount = () => {
return request
.get({
url: 'sync/summary/resourceCount',
method: 'get'
})
.then(res => {
return res.data as IResourceCount
})
}
export const getJobLogLienChartInfo = () => {
return request.post({
url: '/sync/summary/logChartData',
method: 'post',
data: ''
})
}

View File

@ -0,0 +1,224 @@
import request from '@/config/axios'
export interface IGetTaskInfoReq {
id?: string
name?: string
}
export interface ITaskInfoInsertReq {
[key: string]: any
}
export interface ISchedulerOption {
interval: number
unit: string
}
export interface ISource {
type: string
query: string
tables: string
datasourceId: string
tableExtract: string
dsTableList?: IDsTable[]
dsList?: []
fieldList?: ITableField[]
targetFieldTypeList?: string[]
incrementCheckbox?: string
incrementField?: string
}
export interface ITableField {
id: string
fieldSource: string
fieldName: string
fieldType: string
remarks: string
fieldSize: number
fieldPk: boolean
fieldIndex: boolean
}
export interface ITargetProperty {
/**
* 启用分区on
*/
partitionEnable: string
/**
* 分区类型
* DateRange 日期
* NumberRange 数值
* List
*/
partitionType: string
/**
* 启用动态分区on
*/
dynamicPartitionEnable: string
/**
* 动态分区结束偏移量
*/
dynamicPartitionEnd: number
/**
* 动态分区间隔单位
* HOUR WEEK DAY MONTH YEAR
*/
dynamicPartitionTimeUnit: string
/**
* 手动分区列值
* 多个以','隔开
*/
manualPartitionColumnValue: string
/**
* 手动分区数值区间开始值
*/
manualPartitionStart: number
/**
* 手动分区数值区间结束值
*/
manualPartitionEnd: number
/**
* 手动分区数值区间间隔
*/
manualPartitionInterval: number
/**
* 手动分区日期区间
* 2023-09-08 - 2023-09-15
*/
manualPartitionTimeRange: string
/**
* 手动分区日期区间间隔单位
*/
manualPartitionTimeUnit: string
/**
* 分区字段
*/
partitionColumn: string
}
export interface ITarget {
createTable?: string
type: string
fieldList: ITableField[]
tableName: string
datasourceId: string
targetProperty: string
dsList?: []
multipleSelection?: ITableField[]
property: ITargetProperty
}
export class ITaskInfoRes {
id: string
name: string
schedulerType: string
schedulerConf: string
schedulerOption: ISchedulerOption
taskKey: string
desc: string
executorTimeout: number
executorFailRetryCount: number
source: ISource
target: ITarget
status: string
startTime: string
stopTime: string
constructor(
id: string,
name: string,
schedulerType: string,
schedulerConf: string,
taskKey: string,
desc: string,
executorTimeout: number,
executorFailRetryCount: number,
source: ISource,
target: ITarget,
status: string,
startTime: string,
stopTime: string,
schedulerOption: ISchedulerOption
) {
this.id = id
this.name = name
this.schedulerType = schedulerType
this.schedulerConf = schedulerConf
this.taskKey = taskKey
this.desc = desc
this.executorTimeout = executorTimeout
this.executorFailRetryCount = executorFailRetryCount
this.source = source
this.target = target
this.status = status
this.startTime = startTime
this.stopTime = stopTime
this.schedulerOption = schedulerOption
}
}
export interface ITaskInfoUpdateReq {
[key: string]: any
}
export interface IDsTable {
datasourceId: string
name: string
remark: string
enableCheck: string
datasetPath: string
}
export const getDatasourceListByTypeApi = (type: string) => {
return request.get({ url: `/sync/datasource/list/${type}` })
}
export const getTaskInfoListApi = (current: number, size: number, data) => {
return request.post({ url: `/sync/task/pager/${current}/${size}`, data: data })
}
export const executeOneApi = (id: string) => {
return request.get({ url: `/sync/task/execute/${id}` })
}
export const startTaskApi = (id: string) => {
return request.get({ url: `/sync/task/start/${id}` })
}
export const stopTaskApi = (id: string) => {
return request.get({ url: `/sync/task/stop/${id}` })
}
export const addApi = (data: ITaskInfoInsertReq) => {
return request.post({ url: `/sync/task/add`, data: data })
}
export const removeApi = (taskId: string) => {
return request.delete({ url: `/sync/task/remove/${taskId}` })
}
export const batchRemoveApi = (taskIds: string[]) => {
return request.post({ url: `/sync/task/batch/del`, data: taskIds })
}
export const modifyApi = (data: ITaskInfoUpdateReq) => {
return request.post({ url: `/sync/task/update`, data: data })
}
export const findTaskInfoByIdApi = (taskId: string) => {
return request.get({ url: `/sync/task/get/${taskId}` })
}
export const getDatasourceTableListApi = (dsId: string) => {
return request.get({ url: `/sync/datasource/table/list/${dsId}` })
}

View File

@ -0,0 +1,20 @@
import request from '@/config/axios'
export const getTaskLogListApi = (current: number, size: number, data: any) => {
return request.post({
url: `/sync/task/log/pager/${current}/${size}`,
data: data
})
}
export const removeApi = (logId: string) => {
return request.delete({ url: `/sync/task/log/delete/${logId}` })
}
export const getTaskLogDetailApi = (logId: string, fromLineNum: number) => {
return request.get({ url: `/sync/task/log/detail/${logId}/${fromLineNum}` })
}
export const clear = (clearData: {}) => {
return request.post({ url: `/sync/task/log/clear`, data: clearData })
}

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.95219 8.73169L15.5501 3.13376C15.7128 2.97104 15.9767 2.97104 16.1394 3.13376L16.7286 3.72301C16.8913 3.88573 16.8913 4.14955 16.7286 4.31227L11.1307 9.9102L16.7286 15.5081C16.8913 15.6708 16.8913 15.9347 16.7286 16.0974L16.1394 16.6866C15.9767 16.8494 15.7128 16.8494 15.5501 16.6866L9.95219 11.0887L4.35426 16.6866C4.19154 16.8494 3.92772 16.8494 3.76501 16.6866L3.17575 16.0974C3.01303 15.9347 3.01303 15.6708 3.17575 15.5081L8.77368 9.9102L3.17575 4.31227C3.01303 4.14955 3.01303 3.88573 3.17575 3.72301L3.76501 3.13376C3.92772 2.97104 4.19154 2.97104 4.35426 3.13376L9.95219 8.73169Z" fill="#646A73"/>
</svg>

After

Width:  |  Height:  |  Size: 724 B

View File

@ -0,0 +1,16 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_10054_936)">
<path d="M0 6.66667C0 2.98477 3.25611 0 7.27273 0H32.7273C36.7439 0 40 2.98477 40 6.66667V33.3333C40 37.0152 36.7439 40 32.7273 40H7.27273C3.25611 40 0 37.0152 0 33.3333V6.66667Z" fill="#EBF1FF"/>
<g clip-path="url(#clip1_10054_936)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.9019 28.8369C30.5346 28.3383 31.1111 27.6133 31.1111 26.6667V13.3334C31.1111 12.3868 30.5346 11.6618 29.9019 11.1632C29.2567 10.6547 28.392 10.2454 27.4225 9.92225C25.4718 9.27202 22.8476 8.88892 20 8.88892C17.1524 8.88892 14.5283 9.27202 12.5776 9.92225C11.6081 10.2454 10.7433 10.6547 10.0981 11.1632C9.46546 11.6618 8.88892 12.3868 8.88892 13.3334V26.6667C8.88892 27.6133 9.46546 28.3383 10.0981 28.8369C10.7433 29.3453 11.6081 29.7546 12.5776 30.0778C14.5283 30.728 17.1524 31.1111 20 31.1111C22.8476 31.1111 25.4718 30.728 27.4225 30.0778C28.392 29.7546 29.2567 29.3453 29.9019 28.8369ZM11.1111 13.3331C11.1118 12.1059 15.0912 11.1111 20 11.1111C24.9092 11.1111 28.8889 12.1061 28.8889 13.3334C28.8889 14.5607 24.9092 15.5556 20 15.5556C15.0908 15.5556 11.1111 14.5607 11.1111 13.3334C11.1111 13.3333 11.1111 13.3333 11.1111 13.3333C11.1111 13.3332 11.1111 13.3331 11.1111 13.3331ZM27.4225 16.7445C27.9482 16.5692 28.4431 16.3687 28.8889 16.1407V20.0003C28.8889 20.0016 28.8888 20.0064 28.8855 20.017C28.8818 20.0294 28.8724 20.0539 28.8499 20.0905C28.8028 20.1673 28.7066 20.2828 28.5264 20.4248C28.1596 20.714 27.5598 21.0229 26.7197 21.303C25.0511 21.8592 22.6753 22.2222 20 22.2222C17.3248 22.2222 14.9489 21.8592 13.2803 21.303C12.4402 21.0229 11.8405 20.714 11.4736 20.4248C11.2934 20.2828 11.1973 20.1673 11.1501 20.0905C11.1276 20.0539 11.1183 20.0294 11.1145 20.017C11.1113 20.0065 11.1111 20.0014 11.1111 20V16.1407C11.5569 16.3687 12.0519 16.5692 12.5776 16.7445C14.5283 17.3947 17.1524 17.7778 20 17.7778C22.8476 17.7778 25.4718 17.3947 27.4225 16.7445ZM27.4225 23.4111C27.9482 23.2359 28.4431 23.0353 28.8889 22.8074V26.6668C28.8889 26.6678 28.8889 26.6725 28.8855 26.6837C28.8818 26.6961 28.8724 26.7205 28.8499 26.7572C28.8028 26.834 28.7066 26.9495 28.5264 27.0915C28.1596 27.3806 27.5598 27.6896 26.7197 27.9696C25.0511 28.5258 22.6753 28.8889 20 28.8889C17.3248 28.8889 14.9489 28.5258 13.2803 27.9696C12.4402 27.6896 11.8405 27.3806 11.4736 27.0915C11.2934 26.9495 11.1973 26.834 11.1501 26.7572C11.1276 26.7205 11.1183 26.6961 11.1145 26.6837C11.1111 26.6725 11.1111 26.6677 11.1111 26.6667V22.8074C11.5569 23.0353 12.0519 23.2359 12.5776 23.4111C14.5283 24.0614 17.1524 24.4445 20 24.4445C22.8476 24.4445 25.4718 24.0614 27.4225 23.4111Z" fill="#3370FF"/>
</g>
</g>
<defs>
<clipPath id="clip0_10054_936">
<rect width="40" height="40" fill="white"/>
</clipPath>
<clipPath id="clip1_10054_936">
<rect width="26.6667" height="26.6667" fill="white" transform="translate(6.66675 6.66669)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,17 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_10054_2080)">
<path d="M0 6.66667C0 2.98477 3.25611 0 7.27273 0H32.7273C36.7439 0 40 2.98477 40 6.66667V33.3333C40 37.0152 36.7439 40 32.7273 40H7.27273C3.25611 40 0 37.0152 0 33.3333V6.66667Z" fill="#E5FBF8"/>
<g clip-path="url(#clip1_10054_2080)">
<path d="M18.0958 25.682L25.7858 20.8437C25.928 20.7535 26.0452 20.6289 26.1264 20.4814C26.2075 20.3339 26.2501 20.1682 26.2501 19.9998C26.2501 19.8315 26.2075 19.6658 26.1264 19.5183C26.0452 19.3707 25.928 19.2461 25.7858 19.156L18.0962 14.318C17.9388 14.219 17.7575 14.1667 17.5722 14.1667C17.4419 14.1671 17.3131 14.1932 17.1929 14.2434C17.0728 14.2937 16.9637 14.3671 16.8719 14.4595C16.7801 14.5519 16.7075 14.6614 16.658 14.7819C16.6086 14.9024 16.5834 15.0314 16.5838 15.1617V24.8387C16.5839 25.0248 16.636 25.2072 16.7342 25.3653C16.8024 25.4759 16.8919 25.5719 16.9974 25.6478C17.1029 25.7236 17.2225 25.7778 17.349 25.8073C17.4756 25.8367 17.6068 25.8408 17.7349 25.8193C17.8631 25.7978 17.9858 25.7511 18.0958 25.682Z" fill="#00D6B9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9998 7.77771C26.7498 7.77771 32.222 13.2499 32.222 19.9999C32.222 26.7499 26.7498 32.2222 19.9998 32.2222C13.2498 32.2222 7.77759 26.7499 7.77759 19.9999C7.77759 13.2499 13.2498 7.77771 19.9998 7.77771ZM19.9998 9.44438C25.8293 9.44438 30.5554 14.1704 30.5554 19.9999C30.5554 25.8295 25.8293 30.5555 19.9998 30.5555C14.1703 30.5555 9.44425 25.8295 9.44425 19.9999C9.44425 14.1704 14.1703 9.44438 19.9998 9.44438Z" fill="#00D6B9"/>
</g>
</g>
<defs>
<clipPath id="clip0_10054_2080">
<rect width="40" height="40" fill="white"/>
</clipPath>
<clipPath id="clip1_10054_2080">
<rect width="26.6667" height="26.6667" fill="white" transform="translate(6.66675 6.66669)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.66667 2.00033H1.33333V14.0003H10.6667V4.01333H9C8.91159 4.01333 8.82681 3.97821 8.7643 3.91569C8.70179 3.85318 8.66667 3.7684 8.66667 3.67999V2.00033ZM0.666667 0.666992H9.39033C9.47793 0.666967 9.56467 0.684205 9.64561 0.717722C9.72654 0.751238 9.80007 0.800376 9.862 0.862326L11.805 2.80599C11.9299 2.93104 12.0001 3.10058 12 3.27733V14.667C12 14.8438 11.9298 15.0134 11.8047 15.1384C11.6797 15.2634 11.5101 15.3337 11.3333 15.3337H0.666667C0.489856 15.3337 0.320286 15.2634 0.195262 15.1384C0.0702379 15.0134 4.14401e-09 14.8438 0 14.667V1.33366C4.14401e-09 1.15685 0.0702379 0.987279 0.195262 0.862254C0.320286 0.73723 0.489856 0.666992 0.666667 0.666992ZM3.33333 6.66699H8.66667C8.75507 6.66699 8.83986 6.70211 8.90237 6.76462C8.96488 6.82714 9 6.91192 9 7.00033V7.66699C9 7.7554 8.96488 7.84018 8.90237 7.90269C8.83986 7.96521 8.75507 8.00033 8.66667 8.00033H3.33333C3.24493 8.00033 3.16014 7.96521 3.09763 7.90269C3.03512 7.84018 3 7.7554 3 7.66699V7.00033C3 6.91192 3.03512 6.82714 3.09763 6.76462C3.16014 6.70211 3.24493 6.66699 3.33333 6.66699ZM3.33333 10.0003H6.33333C6.37711 10.0003 6.42045 10.0089 6.46089 10.0257C6.50134 10.0424 6.53808 10.067 6.56904 10.098C6.59999 10.1289 6.62454 10.1657 6.64129 10.2061C6.65805 10.2465 6.66667 10.2899 6.66667 10.3337V11.0003C6.66667 11.0441 6.65805 11.0874 6.64129 11.1279C6.62454 11.1683 6.59999 11.2051 6.56904 11.236C6.53808 11.267 6.50134 11.2915 6.46089 11.3083C6.42045 11.325 6.37711 11.3337 6.33333 11.3337H3.33333C3.24493 11.3337 3.16014 11.2985 3.09763 11.236C3.03512 11.1735 3 11.0887 3 11.0003V10.3337C3 10.2453 3.03512 10.1605 3.09763 10.098C3.16014 10.0354 3.24493 10.0003 3.33333 10.0003Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.66666 12.0002H5.66666C2.90523 12.0002 0.666656 9.76164 0.666656 7.00022C0.666656 5.02373 1.81347 3.3151 3.47799 2.50343L4.14705 3.66228C2.8804 4.23986 1.99999 5.51723 1.99999 7.00022C1.99999 9.02526 3.64161 10.6669 5.66666 10.6669H8.66666V9.77154C8.66666 9.57824 8.82336 9.42154 9.01666 9.42154C9.10143 9.42154 9.18333 9.45231 9.24713 9.50814L11.0323 11.0702C11.1778 11.1974 11.1925 11.4186 11.0652 11.564C11.055 11.5757 11.044 11.5867 11.0323 11.597L9.24713 13.159C9.10166 13.2863 8.88054 13.2715 8.75325 13.126C8.69743 13.0622 8.66666 12.9803 8.66666 12.8956V12.0002ZM7.33333 3.33356V4.22891C7.33333 4.31369 7.30256 4.39559 7.24673 4.45939C7.11944 4.60486 6.89832 4.6196 6.75285 4.49231L4.96769 2.9303C4.956 2.92007 4.945 2.90907 4.93477 2.89737C4.80748 2.7519 4.82222 2.53078 4.96769 2.4035L6.75285 0.841481C6.81665 0.785654 6.89855 0.754883 6.98333 0.754883C7.17663 0.754883 7.33333 0.911583 7.33333 1.10488V2.00023H10.3333C13.0947 2.00023 15.3333 4.23881 15.3333 7.00023C15.3333 8.96233 14.2031 10.6605 12.5583 11.4791L11.8897 10.3212C13.1366 9.73578 14 8.46883 14 7.00023C14 4.97519 12.3584 3.33356 10.3333 3.33356H7.33333Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,17 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7277_272394)">
<path d="M0 6.66667C0 2.98477 3.25611 0 7.27273 0H32.7273C36.7439 0 40 2.98477 40 6.66667V33.3333C40 37.0152 36.7439 40 32.7273 40H7.27273C3.25611 40 0 37.0152 0 33.3333V6.66667Z" fill="#FFF3E5"/>
<g clip-path="url(#clip1_7277_272394)">
<path d="M14.7474 21.6382C14.7785 21.7131 14.824 21.7812 14.8814 21.8385L18.3726 25.3304L18.4311 25.3822C18.6733 25.5696 19.0237 25.5526 19.2459 25.3304L25.3563 19.2193C25.472 19.1035 25.537 18.9466 25.537 18.783C25.537 18.6193 25.472 18.4624 25.3563 18.3467L24.4837 17.4733C24.4264 17.4159 24.3583 17.3704 24.2834 17.3393C24.2084 17.3082 24.1281 17.2923 24.047 17.2923C23.9659 17.2923 23.8856 17.3082 23.8106 17.3393C23.7357 17.3704 23.6676 17.4159 23.6103 17.4733L18.8096 22.2748L16.6266 20.0926C16.5109 19.9769 16.354 19.9119 16.1903 19.9119C16.0267 19.9119 15.8697 19.9769 15.754 20.0926L14.8814 20.9652C14.824 21.0225 14.7785 21.0906 14.7474 21.1655C14.7164 21.2404 14.7004 21.3207 14.7004 21.4019C14.7004 21.483 14.7164 21.5633 14.7474 21.6382Z" fill="#FF8800"/>
<path d="M24.4442 7.77777H15.5553C14.8739 7.77777 14.4442 8.2074 14.4442 8.88888V9.99999H11.1111C10.3489 9.99999 10 10.4296 10 11.1111V31.1111C10 31.7933 10.3489 32.2222 11.1111 32.2222L28.8889 32.2222C29.6511 32.2222 30 31.7926 30 31.1111V11.1111C30 10.4289 29.6511 9.99996 28.8889 9.99996L25.5556 9.99999V12.2222H27.7778V30H12.2222V12.2222H14.4442V13.3333C14.4442 14.0148 14.8739 14.4444 15.5553 14.4444H24.4442C25.1257 14.4444 25.5553 14.0148 25.5553 13.3333V8.88888C25.5553 8.20666 25.1257 7.77777 24.4442 7.77777ZM16.6664 12.2222V9.99999H23.3331V12.2222H16.6664Z" fill="#FF8800"/>
</g>
</g>
<defs>
<clipPath id="clip0_7277_272394">
<rect width="40" height="40" fill="white"/>
</clipPath>
<clipPath id="clip1_7277_272394">
<rect width="26.6667" height="26.6667" fill="white" transform="translate(6.66675 6.66669)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,46 +1,66 @@
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes'
import { ElSelect, ElPopover, ElOption, ElIcon } from 'element-plus-secondary'
import { computed, reactive, nextTick, ref } from 'vue'
import { Icon } from '@/components/icon-custom'
import { computed, PropType, reactive, toRefs } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
interface Config {
//
showType: string
//
rangeSeparator: string
//
prefixIcon: string
// label
startPlaceholder: string
// label
endPlaceholder: string
//
format: string
//
valueFormat: string
//
size: string
//
placement: string
}
const props = defineProps({
statusList: propTypes.arrayOf(
propTypes.shape({
id: propTypes.string,
name: propTypes.string
})
),
property: Object as PropType<Config>,
title: propTypes.string
})
const { property } = toRefs(props)
const timeConfig = computed(() => {
let obj = Object.assign(
{
showType: 'datetime',
rangeSeparator: '-',
prefixIcon: 'Calendar',
startPlaceholder: t('datasource.start_time'),
endPlaceholder: t('datasource.end_time'),
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
size: 'default',
placement: 'bottom-end'
},
property.value
)
return obj
})
const state = reactive({
currentStatus: [],
activeStatus: []
modelValue: []
})
const elPopoverU = ref(null)
const more = ref(null)
const statusChange = (id: string | number) => {
state.activeStatus = state.activeStatus.filter(ele => ele.id !== id)
const emits = defineEmits(['filter-change'])
const onChange = () => {
emits('filter-change', state.modelValue)
}
const selectStatus = ids => {
const [item] = ids
state.activeStatus.push(item)
state.currentStatus = []
nextTick(() => {
elPopoverU.value.hide()
more.value.click()
})
const clear = () => {
state.modelValue = []
}
const statusListNotSelect = computed(() => {
return props.statusList.filter(ele => !state.activeStatus.map(t => t.id).includes(ele.id))
})
defineExpose({
activeStatus: state.activeStatus
clear
})
</script>
@ -48,108 +68,42 @@ defineExpose({
<div class="filter">
<span>{{ title }}</span>
<div class="filter-item">
<span
v-for="ele in state.activeStatus"
:key="ele.id"
class="item active"
@click="statusChange(ele.id)"
>{{ $t(ele.name) }}</span
>
<slot v-if="!!statusListNotSelect.length">
<el-popover
:show-arrow="false"
ref="elPopoverU"
placement="bottom"
popper-class="filter-popper"
width="200"
trigger="click"
>
<el-select
:teleported="false"
style="width: 100%"
v-model="state.currentStatus"
value-key="id"
filterable
multiple
@change="selectStatus"
>
<el-option
v-for="item in statusListNotSelect"
:key="item.name"
:label="item.name"
:value="item"
/>
</el-select>
<template #reference>
<span ref="more" class="more">
<el-icon>
<Icon name="icon_add_outlined"> </Icon>
</el-icon>
更多
</span>
</template>
</el-popover>
</slot>
<el-date-picker
v-model="state.modelValue"
:type="timeConfig.showType"
:range-separator="timeConfig.rangeSeparator"
:prefix-icon="timeConfig.prefixIcon"
:start-placeholder="timeConfig.startPlaceholder"
:end-placeholder="timeConfig.endPlaceholder"
:format="timeConfig.format"
:value-format="timeConfig.valueFormat"
key="drawer-time-filt"
:size="timeConfig.size"
@change="onChange"
:placement="timeConfig.placement"
/>
</div>
</div>
</template>
<style lang="less" scope>
.filter {
display: flex;
min-height: 46px;
min-height: 32px;
> :nth-child(1) {
color: var(--deTextSecondary, #1f2329);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 24px;
white-space: nowrap;
width: 116px;
margin-top: 5px;
}
.filter-item {
flex: 1;
.item,
.more {
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
white-space: nowrap;
font-size: 14px;
font-weight: 400;
line-height: 24px;
margin-right: 12px;
text-align: center;
padding: 1px 6px;
background: var(--deTextPrimary5, #f5f6f7);
color: var(--deTextPrimary, #1f2329);
border-radius: 2px;
cursor: pointer;
display: inline-block;
margin-bottom: 12px;
}
.active,
.more:hover {
background: var(--primary10, rgba(51, 112, 255, 0.1));
color: var(--primaryselect, #0c296e);
}
.more {
white-space: nowrap;
display: inline-flex;
align-items: center;
i {
margin-right: 5px;
}
}
}
}
</style>
<style lang="less">
.filter-popper {
padding: 0 !important;
background: #fff !important;
}
</style>

View File

@ -14,7 +14,8 @@ const props = defineProps({
type: propTypes.string,
field: propTypes.string,
option: propTypes.array,
title: propTypes.string
title: propTypes.string,
property: propTypes.shape({})
})
),
title: propTypes.string
@ -126,7 +127,8 @@ defineExpose({
:ref="el => (myRefs[index] = el)"
v-if="component.type === 'time'"
:title="component.title"
@filter-change="v => filterChange(v, component.field, 'eq')"
:property="component.property"
@filter-change="v => filterChange(v, component.field, component.operator)"
/>
</div>

View File

@ -3,7 +3,7 @@ import { Icon } from '@/components/icon-custom'
import { propTypes } from '@/utils/propTypes'
import type { Placement } from 'element-plus-secondary'
import { ref, PropType } from 'vue'
import { XpackComponent } from '@/components/plugin'
import ShareHandler from '@/views/share/share/ShareHandler.vue'
export interface Menu {
svgName?: string
label?: string
@ -44,7 +44,8 @@ const menus = ref([
])
const handleCommand = (command: string | number | object) => {
if (command === 'share') {
shareComponent.value.invokeMethod({ methodName: 'execute' })
// shareComponent.value.invokeMethod({ methodName: 'execute' })
shareComponent.value.execute()
return
}
emit('handleCommand', command)
@ -85,9 +86,8 @@ const emit = defineEmits(['handleCommand'])
</el-dropdown-menu>
</template>
</el-dropdown>
<XpackComponent
<ShareHandler
ref="shareComponent"
jsname="c2hhcmUtaGFuZGxlcg=="
:resource-id="props.node.id"
:resource-type="props.resourceType"
:weight="node.weight"

View File

@ -13,6 +13,7 @@ import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useLinkStoreWithOut } from '@/store/modules/link'
import { config } from './config'
import { configHandler } from './refresh'
type AxiosErrorWidthLoading<T> = T & {
config: {
loading?: boolean
@ -25,8 +26,10 @@ type InternalAxiosRequestConfigWidthLoading<T> = T & {
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import router from '@/router'
const { result_code } = config
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export const PATH_URL = window.DataEaseBi
@ -38,10 +41,41 @@ export interface AxiosInstanceWithLoading extends AxiosInstance {
config: AxiosRequestConfig<D> & { loading?: boolean }
): Promise<R>
}
const getTimeOut = () => {
let time = 100
const url = PATH_URL + '/sysParameter/requestTimeOut'
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
if (xhr.responseText) {
try {
const response = JSON.parse(xhr.responseText)
if (response.code === 0) {
time = response.data
} else {
ElMessage.error('系统异常请联系管理员')
}
} catch (e) {
ElMessage.error('系统异常请联系管理员')
}
} else {
ElMessage.error('网络异常请联系网管')
}
}
}
xhr.open('get', url, false)
xhr.send()
console.log(time)
return time
}
// 创建axios实例
const time = getTimeOut()
const service: AxiosInstanceWithLoading = axios.create({
baseURL: PATH_URL, // api base_url
timeout: config.request_timeout // 请求超时时间
timeout: time ? time * 1000 : config.request_timeout // 请求超时时间
})
const mapping = {
'zh-CN': 'zh-CN',

View File

@ -2126,12 +2126,64 @@ export default {
setting_basic: {
autoCreateUser: '第三方自动创建用户',
dsIntervalTime: '数据源检测时间间隔',
dsExecuteTime: '数据源检测频率'
dsExecuteTime: '数据源检测频率',
frontTimeOut: '请求超时时间'
},
template_manage: {
name_already_exists_type: '分类名称已存在',
the_same_category: '同一分类下该模板名称已存在'
},
sync_manage: {
title: '同步管理',
ds_search_placeholder: '搜索名称,描述'
},
sync_datasource: {
title: '数据连接管理',
source_ds: '源数据源',
target_ds: '目标数据源',
add_source_ds: '@:common.add@:sync_datasource.source_ds',
add_target_ds: '@:common.add@:sync_datasource.target_ds',
name: '名称',
desc: '描述',
type: '类型',
status: '状态',
create_time: '创建时间',
update_time: '更新时间',
operation: '操作',
edit: '编辑',
delete: '删除',
confirm_batch_delete_target_ds: '确定删除{0}个目标数据源吗',
confirm_batch_delete_source_ds: '确定删除{0}个源数据源吗'
},
sync_task: {
title: '任务管理',
list: '任务列表',
log_list: '任务日志',
add_task: '添加任务',
name: '名称',
desc: '描述',
status: '状态',
create_time: '创建时间',
update_time: '更新时间',
operation: '操作',
edit: '编辑',
delete: '删除',
start: '启动',
stop: '停止',
trigger_last_time: '上次执行时间',
trigger_next_time: '下次执行时间',
status_success: '成功',
status_running: '执行中',
status_failed: '失败',
status_stopped: '已停止',
status_waiting: '等待执行',
status_done: '执行结束',
status_suspend: '暂停',
running_one: '执行一次',
keep_going: '继续',
log: '日志',
show_log: '查看日志'
},
watermark: {
support_params: '当前支持的参数',
enable: '启用水印',

View File

@ -18,7 +18,7 @@ const { start, done } = useNProgress()
const { loadStart, loadDone } = usePageLoading()
const whiteList = ['/login', '/de-link'] // 不重定向白名单
const whiteList = ['/login', '/de-link', '/chart-view'] // 不重定向白名单
router.beforeEach(async (to, from, next) => {
start()

View File

@ -112,6 +112,13 @@ export const routes: AppRouteRecordRaw[] = [
meta: { hidden: true }
}
]
},
{
path: '/chart-view',
name: 'chart-view',
hidden: true,
meta: {},
component: () => import('@/views/chart/ChartView.vue')
}
]

View File

@ -0,0 +1,29 @@
<script lang="ts" setup>
import ViewWrapper from '@/pages/panel/ViewWrapper.vue'
import { useRoute } from 'vue-router'
import { onBeforeMount } from 'vue'
const route = useRoute()
const init = () => {
const busiFlag = route.query.busiFlag
const dvId = route.query.dvId
const chartId = route.query.chartId
const embeddedToken = route.query.embeddedToken
console.log(busiFlag)
console.log(dvId)
console.log(chartId)
console.log(embeddedToken)
window.DataEaseBi = {
token: embeddedToken,
chartId: chartId,
dvId: dvId,
busiFlag: busiFlag
}
}
onBeforeMount(() => {
init()
})
</script>
<template>
<view-wrapper />
</template>

View File

@ -1,6 +1,6 @@
<template>
<XpackComponent jsname="bGluaw==" :error-tips="true" />
<link-index :error-tips="true" />
</template>
<script lang="ts" setup>
import { XpackComponent } from '@/components/plugin'
import LinkIndex from '@/views/share/link/index.vue'
</script>

View File

@ -6,7 +6,7 @@ import { useAppStoreWithOut } from '@/store/modules/app'
import DvDetailInfo from '@/views/common/DvDetailInfo.vue'
import { storeApi, storeStatusApi } from '@/api/visualization/dataVisualization'
import { ref, watch, computed } from 'vue'
import { XpackComponent } from '@/components/plugin'
import ShareVisualHead from '@/views/share/share/ShareVisualHead.vue'
const dvMainStore = dvMainStoreWithOut()
const appStore = useAppStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -94,8 +94,7 @@ watch(
</template>
预览</el-button
>
<XpackComponent
jsname="L2NvbXBvbmVudC9zaGFyZS9TaGFyZVZpc3VhbEhlYWQ="
<ShareVisualHead
:resource-id="dvInfo.id"
:weight="dvInfo.weight"
:resource-type="dvInfo.type"

View File

@ -173,7 +173,8 @@ const showLoginErrorMsg = () => {
onMounted(() => {
checkPlatform()
if (localStorage.getItem('DE-GATEWAY-FLAG')) {
loginErrorMsg.value = localStorage.getItem('DE-GATEWAY-FLAG')
const msg = localStorage.getItem('DE-GATEWAY-FLAG')
loginErrorMsg.value = decodeURIComponent(msg)
showLoginErrorMsg()
localStorage.removeItem('DE-GATEWAY-FLAG')
logoutHandler(true)

View File

@ -0,0 +1,42 @@
import request from '@/config/axios'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export interface ProxyInfo {
resourceId: string
uid: string
exp?: boolean
pwdValid?: boolean
type: string
}
class ShareProxy {
uuid: string
constructor() {
this.uuid = ''
}
setUuid() {
const curLocation = window.location.href
const uuidObj = curLocation.substring(
curLocation.lastIndexOf('de-link/') + 8,
curLocation.lastIndexOf('?') > 0 ? curLocation.lastIndexOf('?') : curLocation.length
)
this.uuid = uuidObj
}
async loadProxy() {
this.setUuid()
if (!this.uuid) {
return null
}
const uuid = this.uuid
const url = '/share/proxyInfo'
const param = { uuid, ciphertext: null }
const ciphertext = wsCache.get(`link-${uuid}`)
if (ciphertext) {
param['ciphertext'] = ciphertext
}
const res = await request.post({ url, data: param })
const proxyInfo: ProxyInfo = res.data as ProxyInfo
return proxyInfo
}
}
export const shareProxy = new ShareProxy()

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
</script>
<template>
<EmptyBackground img-type="noneWhite" description="链接不存在" />
</template>

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
</script>
<template>
<EmptyBackground img-type="noneWhite" description="链接已过期" />
</template>

View File

@ -0,0 +1,55 @@
<template>
<div class="link-container" v-loading="loading">
<LinkError v-if="!loading && !linkExist" />
<Exp v-else-if="!loading && linkExp" />
<PwdTips v-else-if="!loading && !pwdValid" />
<PreviewCanvas
v-else
:class="{ 'hidden-link': loading }"
ref="pcanvas"
public-link-status="true"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, nextTick, ref } from 'vue'
import PreviewCanvas from '@/views/data-visualization/PreviewCanvas.vue'
import { ProxyInfo, shareProxy } from './ShareProxy'
import Exp from './exp.vue'
import LinkError from './error.vue'
import PwdTips from './pwd.vue'
const pcanvas = ref(null)
const linkExist = ref(false)
const loading = ref(true)
const linkExp = ref(false)
const pwdValid = ref(false)
onMounted(async () => {
const proxyInfo = (await shareProxy.loadProxy()) as ProxyInfo
if (!proxyInfo?.resourceId) {
loading.value = false
return
}
linkExist.value = true
linkExp.value = !!proxyInfo.exp
pwdValid.value = !!proxyInfo.pwdValid
nextTick(() => {
const method = pcanvas?.value?.loadCanvasDataAsync
if (method) {
method(proxyInfo.resourceId, proxyInfo.type, null)
}
loading.value = false
})
})
</script>
<style lang="less" scoped>
.link-container {
position: absolute !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.hidden-link {
display: none !important;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<div class="pwd-body" v-loading="loading">
<div class="pwd-wrapper">
<div class="pwd-content">
<div class="span-header">
<div class="bi-text">
<span style="text-align: center">{{ t('pblink.key_pwd') }} </span>
</div>
</div>
<div class="input-layout">
<div class="input-main">
<div class="div-input">
<el-form ref="pwdForm" :model="form" :rules="rule" size="small" @submit.stop.prevent>
<el-form-item label="" prop="password">
<CustomPassword
v-model="form.password"
maxlength="4"
show-password
class="real-input"
:placeholder="t('pblink.input_placeholder')"
/>
</el-form-item>
</el-form>
</div>
</div>
<div class="abs-input">
<div class="input-text">{{ msg }}</div>
</div>
</div>
<div class="auth-root-class">
<el-button size="small" type="primary" @click="refresh">{{
t('pblink.sure_bt')
}}</el-button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import request from '@/config/axios'
import { useAppStoreWithOut } from '@/store/modules/app'
import { rsaEncryp } from '@/utils/encryption'
import { useCache } from '@/hooks/web/useCache'
import { queryDekey } from '@/api/login'
import { CustomPassword } from '@/components/custom-password'
const { wsCache } = useCache()
const appStore = useAppStoreWithOut()
const { t } = useI18n()
const msg = ref('')
const loading = ref(true)
const pwdForm = ref<FormInstance>()
const form = ref({
password: ''
})
const rule = reactive<FormRules>({
password: [
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
{
required: true,
pattern: /^[a-zA-Z0-9]{4}$/,
message: t('pblink.pwd_format_error'),
trigger: 'blur'
}
]
})
const refresh = () => {
const curLocation = window.location.href
const paramIndex = curLocation.indexOf('?')
const uuidIndex = curLocation.indexOf('de-link/') + 8
const uuid = curLocation.substring(uuidIndex, paramIndex !== -1 ? paramIndex : curLocation.length)
const pwd = form.value.password
const text = uuid + pwd
const ciphertext = rsaEncryp(text)
request.post({ url: '/share/validate', data: { ciphertext } }).then(res => {
if (res.data) {
wsCache.set(`link-${uuid}`, ciphertext)
window.location.reload()
} else {
msg.value = '密码错误'
}
})
}
onMounted(() => {
if (!wsCache.get(appStore.getDekey)) {
queryDekey()
.then(res => {
wsCache.set(appStore.getDekey, res.data)
})
.finally(() => {
loading.value = false
})
}
loading.value = false
})
</script>
<style lang="less" scoped>
.pwd-body {
position: absolute;
width: 100%;
margin: 0;
padding: 0;
top: 0;
left: 0;
background-repeat: repeat;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
color: #3d4d66;
// font: normal 12px Helvetica Neue,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,,Heiti,,sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-decoration: none;
-kthml-user-focus: normal;
-moz-user-focus: normal;
-moz-outline: 0 none;
outline: 0 none;
height: 100%;
display: block;
}
.pwd-wrapper {
background-color: #f7f8fa;
height: 100%;
justify-content: center !important;
align-items: center !important;
min-height: 25px;
display: flex;
-moz-flex-direction: row;
-o-flex-direction: row;
flex-direction: row;
-moz-justify-content: flex-start;
-ms-justify-content: flex-start;
-o-justify-content: flex-start;
justify-content: flex-start;
-moz-align-items: flex-start;
-ms-align-items: flex-start;
-o-align-items: flex-start;
align-items: flex-start;
-o-flex-wrap: nowrap;
flex-wrap: nowrap;
}
.pwd-content {
width: 450px;
height: 250px;
position: relative;
flex-shrink: 0;
background-color: #ffffff;
display: block;
}
.span-header {
position: relative;
margin: 57px auto 0px;
justify-content: center !important;
align-items: center !important;
}
.bi-text {
max-width: 100%;
text-align: center;
white-space: pre;
text-overflow: ellipsis;
position: relative;
flex-shrink: 0;
box-sizing: border-box;
overflow: hidden;
overflow-x: hidden;
overflow-y: hidden;
word-break: break-all;
display: block;
}
.input-layout {
width: 200px;
position: relative;
margin: 0px auto;
padding: 0;
display: block;
}
.input-main {
width: 192px;
height: 35px;
position: relative;
margin-top: 30px;
// border: 1px solid #e8eaed;
display: block;
}
.abs-input {
height: 20px;
position: relative;
margin-top: 5px;
display: block;
}
.input-text {
height: 20px;
line-height: 20px;
text-align: center;
white-space: pre;
text-overflow: ellipsis;
left: 0px;
top: 0px;
bottom: 0px;
position: absolute;
color: #e65251;
box-sizing: border-box;
}
.auth-root-class {
margin: 15px 0px 5px;
text-align: center;
}
</style>

View File

@ -0,0 +1,267 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, watch, computed } from 'vue'
import GridTable from '@/components/grid-table/src/GridTable.vue'
import request from '@/config/axios'
import dayjs from 'dayjs'
import { propTypes } from '@/utils/propTypes'
import ShareHandler from './ShareHandler.vue'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
const props = defineProps({
activeName: propTypes.string.def('')
})
const { t } = useI18n()
const interactiveStore = interactiveStoreWithOut()
const busiDataMap = computed(() => interactiveStore.getData)
const panelKeyword = ref()
const userAddPopper = ref(false)
const activeCommand = ref('all_types')
const state = reactive({
tableData: [],
curTypeList: ['all_types', 'panel', 'screen'],
tableColumn: [
{ field: 'creator', label: '分享人' },
{ field: 'time', label: '分享时间', type: 'time' },
{ field: 'exp', label: '有效期', type: 'time' }
]
})
const handleVisibleChange = (val: boolean) => {
userAddPopper.value = val
}
const handleCommand = (command: string) => {
activeCommand.value = command
loadTableData()
}
const triggerFilterPanel = () => {
loadTableData()
}
const preview = id => {
const routeUrl = `/#/preview?dvId=${id}`
window.open(routeUrl, '_blank')
}
const formatterTime = (_, _column, cellValue) => {
if (!cellValue) {
return '-'
}
return dayjs(new Date(cellValue)).format('YYYY-MM-DD HH:mm:ss')
}
const showLoading = () => {
emits('setLoading', true)
}
const closeLoading = () => {
emits('setLoading', false)
}
const emits = defineEmits(['setLoading'])
const loadTableData = () => {
showLoading()
const queryType = activeCommand.value === 'all_types' ? '' : activeCommand.value
request
.post({
url: '/share/query',
data: { type: queryType, keyword: panelKeyword.value, asc: !orderDesc.value }
})
.then(res => {
state.tableData = res.data
})
.finally(() => {
imgType.value = getEmptyImg()
emptyDesc.value = getEmptyDesc()
closeLoading()
})
}
const orderDesc = ref(true)
const sortChange = param => {
orderDesc.value = true
const type = param.order.substring(0, param.order.indexOf('ending'))
orderDesc.value = type === 'desc'
loadTableData()
}
const getBusiListWithPermission = () => {
const baseFlagList: string[] = ['panel', 'screen']
const busiFlagList: string[] = []
for (const key in busiDataMap.value) {
if (busiDataMap.value[key].menuAuth) {
busiFlagList.push(baseFlagList[parseInt(key)])
}
}
return busiFlagList
}
const busiAuthList: string[] = getBusiListWithPermission()
const imgType = ref()
const emptyDesc = ref('')
const getEmptyImg = (): string => {
if (panelKeyword.value) {
return 'tree'
}
return 'noneWhite'
}
const getEmptyDesc = (): string => {
if (panelKeyword.value) {
return '没有找到相关内容'
}
return ''
}
watch(
() => props.activeName,
() => {
if (props.activeName === 'share') {
loadTableData()
}
},
{
immediate: true
}
)
</script>
<template>
<el-row v-if="props.activeName === 'share'">
<el-col :span="12">
<el-dropdown
placement="bottom-start"
@visible-change="handleVisibleChange"
popper-class="menu-panel-select_popper"
@command="handleCommand"
trigger="click"
>
<el-button secondary>
{{ t(`auth.${activeCommand}`) }}
<el-icon style="margin-left: 4px">
<arrow-up v-if="userAddPopper" />
<arrow-down v-else />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:class="activeCommand === ele && 'active'"
v-for="ele in state.curTypeList.filter(
busi => busi === 'all_types' || busiAuthList.includes(busi)
)"
:command="ele"
:key="ele"
>
{{ t(`auth.${ele}`) }}
<el-icon v-if="activeCommand === ele">
<Icon name="icon_done_outlined"></Icon>
</el-icon>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<el-col class="search" :span="12">
<el-input
v-model="panelKeyword"
clearable
@change="triggerFilterPanel"
placeholder="搜索关键词"
>
<template #prefix>
<el-icon>
<Icon name="icon_search-outline_outlined"></Icon>
</el-icon>
</template>
</el-input>
</el-col>
</el-row>
<div v-if="props.activeName === 'share'" class="panel-table">
<GridTable
:show-pagination="false"
:table-data="state.tableData"
@sort-change="sortChange"
:empty-desc="emptyDesc"
:empty-img="imgType"
class="workbranch-grid"
>
<el-table-column key="name" width="280" prop="name" :label="t('common.name')">
<template v-slot:default="scope">
<div class="name-content">
<el-icon class="main-color"> <Icon name="icon_dashboard_outlined" /> </el-icon>
<el-tooltip placement="top">
<template #content>{{ scope.row.name }}</template>
<span class="ellipsis" style="max-width: 250px">{{ scope.row.name }}</span>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column
v-for="item in state.tableColumn"
:key="item.label"
prop="name"
show-overflow-tooltip
:label="item.label"
:sortable="item.type === 'time' && item.field === 'time'"
>
<template #default="scope">
<span v-if="item.type && item.type === 'time'">{{
formatterTime(null, null, scope.row[item.field])
}}</span>
<span v-else>{{ scope.row[item.field] }}</span>
</template>
</el-table-column>
<el-table-column width="96" fixed="right" key="_operation" :label="t('common.operate')">
<template #default="scope">
<el-tooltip effect="dark" content="新页面预览" placement="top">
<el-icon class="hover-icon hover-icon-in-table" @click="preview(scope.row.resourceId)">
<Icon name="icon_pc_outlined"></Icon>
</el-icon>
</el-tooltip>
<ShareHandler
:in-grid="true"
:resource-id="scope.row.resourceId"
:weight="scope.row.weight"
/>
</template>
</el-table-column>
</GridTable>
</div>
</template>
<style lang="less" scoped>
.search {
text-align: right;
.ed-input {
width: 240px;
}
}
.panel-table {
margin-top: 16px;
height: calc(100% - 110px);
.name-content {
display: flex;
align-items: center;
}
.main-color {
font-size: 21.33px;
padding: 5.33px;
margin-right: 12px;
border-radius: 4px;
color: #fff;
background: #3370ff;
}
.name-star {
font-size: 15px;
padding-left: 5px;
}
}
.workbranch-grid :deep(.ed-empty) {
padding: 80px 0 !important;
.ed-empty__description {
margin-top: 0px;
line-height: 20px !important;
}
}
</style>

View File

@ -0,0 +1,370 @@
<template>
<el-tooltip
v-if="props.weight >= 7 && props.inGrid"
effect="dark"
:content="t('visualization.share')"
placement="top"
>
<el-icon class="hover-icon hover-icon-in-table share-button-icon" @click="share">
<Icon name="icon_share-label_outlined"></Icon>
</el-icon>
</el-tooltip>
<el-button v-if="props.weight >= 7 && props.isButton" @click="share" icon="Share">{{
t('visualization.share')
}}</el-button>
<el-dialog
v-if="dialogVisible && props.weight >= 7"
class="copy-link_dialog"
:class="{ 'hidden-footer': !shareEnable }"
v-model="dialogVisible"
:close-on-click-modal="true"
:append-to-body="true"
title="公共链接分享"
width="480px"
:show-close="false"
>
<div class="share-dialog-container">
<div class="copy-link">
<div class="open-share flex-align-center">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
{{ shareTips }}
</div>
<div v-if="shareEnable" class="text">{{ linkAddr }}</div>
<div v-if="shareEnable" class="exp-container">
<el-checkbox
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
<div class="inline-share-item-picker">
<el-date-picker
:clearable="false"
size="small"
v-if="state.detailInfo.exp"
class="share-exp-picker"
v-model="state.detailInfo.exp"
type="datetime"
placeholder=""
:shortcuts="shortcuts"
@change="expChangeHandler"
:disabled-date="disabledDate"
value-format="x"
/>
<span v-if="expError" class="exp-error">必须大于当前时间</span>
</div>
</div>
<div v-if="shareEnable" class="pwd-container">
<el-checkbox
:disabled="!shareEnable"
v-model="passwdEnable"
@change="pwdEnableSwitcher"
:label="t('visualization.passwd_protect')"
/>
<div class="inline-share-item" v-if="state.detailInfo.pwd">
<el-input v-model="state.detailInfo.pwd" readonly size="small">
<template #append>
<div @click="resetPwd" class="share-reset-container">
<span>{{ t('commons.reset') }}</span>
</div>
</template>
</el-input>
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="!shareEnable || expError" type="primary" @click="copyInfo">
{{ t('visualization.copy_link') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, onMounted, computed } from 'vue'
import request from '@/config/axios'
import { propTypes } from '@/utils/propTypes'
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const { t } = useI18n()
const props = defineProps({
inGrid: propTypes.bool.def(false),
resourceId: propTypes.string.def(''),
resourceType: propTypes.string.def(''),
weight: propTypes.number.def(0),
isButton: propTypes.bool.def(false)
})
const loadingInstance = ref<any>(null)
const dialogVisible = ref(false)
const overTimeEnable = ref(false)
const passwdEnable = ref(false)
const shareEnable = ref(false)
const linkAddr = ref('')
const expError = ref(false)
const state = reactive({
detailInfo: {
id: '',
uuid: '',
pwd: '',
exp: 0
} as ShareInfo
})
const emits = defineEmits(['loaded'])
const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const copyInfo = async () => {
if (shareEnable.value) {
try {
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {
ElMessage.warning(t('common.copy_unsupported'))
}
} else {
ElMessage.warning(t('common.copy_unsupported'))
}
dialogVisible.value = false
}
const disabledDate = date => {
return date.getTime() < new Date().getTime()
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({ target: '.share-dialog-container' })
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const share = () => {
dialogVisible.value = true
loadShareInfo()
}
const loadShareInfo = () => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
request
.get({ url })
.then(res => {
state.detailInfo = { ...res.data }
setPageInfo()
})
.finally(() => {
closeLoading()
})
}
const setPageInfo = () => {
if (state.detailInfo.id && state.detailInfo.uuid) {
shareEnable.value = true
formatLinkAddr()
}
passwdEnable.value = !!state.detailInfo.pwd
overTimeEnable.value = !!state.detailInfo.exp
}
const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
})
}
const formatLinkAddr = () => {
const href = window.location.href
const prefix = href.substring(0, href.indexOf('#') + 1)
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
}
const expEnableSwitcher = val => {
let exp = 0
if (val) {
const now = new Date()
now.setTime(now.getTime() + 3600 * 1000)
exp = now.getTime()
state.detailInfo.exp = exp
}
expChangeHandler(exp)
}
const expChangeHandler = exp => {
if (overTimeEnable.value && exp < new Date().getTime()) {
expError.value = true
return
}
expError.value = false
const resourceId = props.resourceId
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const pwdEnableSwitcher = val => {
let pwd = ''
if (val) {
pwd = getUuid()
}
resetPwdHandler(pwd)
}
const resetPwd = () => {
const pwd = getUuid()
resetPwdHandler(pwd)
}
const resetPwdHandler = (pwd?: string) => {
const resourceId = props.resourceId
const url = '/share/editPwd'
const data = { resourceId, pwd }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const getUuid = () => {
return 'xyxy'.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}
const execute = () => {
share()
}
defineExpose({
execute
})
onMounted(() => {
if (!props.inGrid && props.weight >= 7) {
const commandInfo = {
label: '分享',
command: 'share',
svgName: 'dv-share'
}
emits('loaded', commandInfo)
}
})
</script>
<style lang="less">
.copy-link_dialog {
.ed-dialog__header {
padding: 16px 16px 10px !important;
.ed-dialog__title {
font-size: 14px !important;
}
}
.ed-dialog__body {
padding: 16px !important;
}
.ed-dialog__footer {
border-top: 1px solid #1f232926;
padding: 12px 16px 16px;
}
}
.hidden-footer {
.ed-dialog__footer {
display: none !important;
}
}
</style>
<style lang="less" scoped>
.share-button-icon {
margin-left: 4px;
}
.copy-link_dialog {
.exp-container {
.ed-checkbox {
margin-right: 10px;
}
.inline-share-item-picker {
display: flex;
align-items: center;
:deep(.share-exp-picker) {
margin-left: 25px !important;
.ed-input__wrapper {
width: 200px !important;
}
}
.exp-error {
color: var(--ed-color-danger);
font-size: 12px;
}
}
}
.pwd-container {
.ed-checkbox {
margin-right: 10px;
}
.inline-share-item {
margin-left: 25px;
width: 220px;
:deep(.ed-input-group__append) {
width: 45px !important;
background: none;
color: #1f2329;
padding: 0px 0px !important;
.share-reset-container {
width: 100%;
display: flex;
justify-content: center;
}
&:hover {
cursor: pointer;
background-color: #f5f6f7;
}
&:active {
cursor: pointer;
background-color: #eff0f1;
}
}
}
}
.copy-link {
font-weight: 400;
font-family: PingFang SC;
.open-share {
margin: -18px 0 8px 0;
color: #646a73;
font-size: 12px;
font-style: normal;
line-height: 20px;
.ed-switch {
margin-right: 8px;
}
}
.text {
border-radius: 4px;
border: 1px solid #bbbfc4;
background: #eff0f1;
margin-bottom: 16px;
height: 32px;
padding: 5px 12px;
color: #8f959e;
font-size: 14px;
font-style: normal;
line-height: 22px;
}
}
}
</style>

View File

@ -0,0 +1,14 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { onMounted } from 'vue'
const { t } = useI18n()
const emits = defineEmits(['loaded'])
const panelInfo = {
title: t('visualization.share_out'),
name: 'share'
}
onMounted(() => {
emits('loaded', panelInfo)
})
</script>

View File

@ -0,0 +1,332 @@
<template>
<el-button ref="shareButtonRef" v-if="props.weight >= 7" v-click-outside="openShare">
<template #icon>
<icon name="icon_share-label_outlined"></icon>
</template>
{{ t('visualization.share') }}
</el-button>
<el-popover
ref="sharePopoverRef"
:virtual-ref="shareButtonRef"
trigger="click"
title=""
virtual-triggering
width="480"
placement="bottom-start"
:show-arrow="false"
popper-class="share-popover"
@show="share"
>
<div class="share-container">
<div class="share-title share-padding">公共链接分享</div>
<div class="open-share flex-align-center share-padding">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
{{ shareTips }}
</div>
<div v-if="shareEnable" class="text share-padding">
<el-input v-model="linkAddr" disabled />
</div>
<div v-if="shareEnable" class="exp-container share-padding">
<el-checkbox
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
<div class="inline-share-item-picker">
<el-date-picker
:clearable="false"
size="small"
class="share-exp-picker"
v-if="state.detailInfo.exp"
v-model="state.detailInfo.exp"
type="datetime"
:teleported="false"
placeholder=""
:shortcuts="shortcuts"
@change="expChangeHandler"
:disabled-date="disabledDate"
value-format="x"
/>
<span v-if="expError" class="exp-error">必须大于当前时间</span>
</div>
</div>
<div v-if="shareEnable" class="pwd-container share-padding">
<el-checkbox
:disabled="!shareEnable"
v-model="passwdEnable"
@change="pwdEnableSwitcher"
:label="t('visualization.passwd_protect')"
/>
<div class="inline-share-item" v-if="state.detailInfo.pwd">
<el-input v-model="state.detailInfo.pwd" readonly size="small">
<template #append>
<div @click="resetPwd" class="share-reset-container">
<span>{{ t('commons.reset') }}</span>
</div>
</template>
</el-input>
</div>
</div>
<el-divider v-if="shareEnable" class="share-divider" />
<div v-if="shareEnable" class="share-foot share-padding">
<el-button :disabled="!shareEnable || expError" type="primary" @click="copyInfo">
{{ t('visualization.copy_link') }}
</el-button>
</div>
</div>
</el-popover>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, unref, computed } from 'vue'
import request from '@/config/axios'
import { propTypes } from '@/utils/propTypes'
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const { t } = useI18n()
const props = defineProps({
resourceId: propTypes.string.def(''),
resourceType: propTypes.string.def(''),
weight: propTypes.number.def(0)
})
const shareButtonRef = ref()
const sharePopoverRef = ref()
const loadingInstance = ref<any>(null)
const dialogVisible = ref(false)
const overTimeEnable = ref(false)
const passwdEnable = ref(false)
const shareEnable = ref(false)
const linkAddr = ref('')
const expError = ref(false)
const state = reactive({
detailInfo: {
id: '',
uuid: '',
pwd: '',
exp: 0
} as ShareInfo
})
const openShare = () => {
unref(sharePopoverRef).popperRef?.delayHide?.()
}
const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const copyInfo = async () => {
if (shareEnable.value) {
try {
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {
ElMessage.warning(t('common.copy_unsupported'))
}
} else {
ElMessage.warning(t('common.copy_unsupported'))
}
dialogVisible.value = false
openShare()
}
const disabledDate = date => {
return date.getTime() < new Date().getTime()
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({ target: '.share-dialog-container' })
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const share = () => {
dialogVisible.value = true
loadShareInfo()
}
const loadShareInfo = () => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
request
.get({ url })
.then(res => {
state.detailInfo = { ...res.data }
setPageInfo()
})
.finally(() => {
closeLoading()
})
}
const setPageInfo = () => {
if (state.detailInfo.id && state.detailInfo.uuid) {
shareEnable.value = true
formatLinkAddr()
passwdEnable.value = !!state.detailInfo.pwd
overTimeEnable.value = !!state.detailInfo.exp
} else {
shareEnable.value = false
passwdEnable.value = false
overTimeEnable.value = false
}
}
const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
})
}
const formatLinkAddr = () => {
const href = window.location.href
const prefix = href.substring(0, href.indexOf('#') + 1)
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
}
const expEnableSwitcher = val => {
let exp = 0
if (val) {
const now = new Date()
now.setTime(now.getTime() + 3600 * 1000)
exp = now.getTime()
state.detailInfo.exp = exp
}
expChangeHandler(exp)
}
const expChangeHandler = exp => {
if (overTimeEnable.value && exp < new Date().getTime()) {
expError.value = true
return
}
expError.value = false
const resourceId = props.resourceId
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const pwdEnableSwitcher = val => {
let pwd = ''
if (val) {
pwd = getUuid()
}
resetPwdHandler(pwd)
}
const resetPwd = () => {
const pwd = getUuid()
resetPwdHandler(pwd)
}
const resetPwdHandler = (pwd?: string) => {
const resourceId = props.resourceId
const url = '/share/editPwd'
const data = { resourceId, pwd }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const getUuid = () => {
return 'xyxy'.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}
const execute = () => {
share()
}
defineExpose({
execute
})
</script>
<style lang="less">
.share-popover {
padding: 16px 0px !important;
}
</style>
<style lang="less" scoped>
.share-container {
.share-title {
font-weight: 500;
color: #1f2329;
padding-bottom: 5px !important;
}
.share-padding {
padding: 0px 16px;
}
.share-divider {
margin-bottom: 10px !important;
border-top: 1px #1f232926 solid;
}
.share-foot {
display: flex;
justify-content: flex-end;
}
.open-share {
font-size: 12px;
color: #646a73;
.ed-switch {
margin-right: 8px;
}
}
.text {
padding-bottom: 5px !important;
}
}
.inline-share-item-picker {
display: flex;
align-items: center;
:deep(.share-exp-picker) {
margin-left: 25px !important;
.ed-input__wrapper {
width: 200px !important;
}
}
.exp-error {
color: var(--ed-color-danger);
font-size: 12px;
}
}
.inline-share-item {
margin-left: 25px;
width: 220px;
:deep(.ed-input-group__append) {
width: 45px !important;
background: none;
color: #1f2329;
padding: 0px 0px !important;
.share-reset-container {
width: 100%;
display: flex;
justify-content: center;
}
&:hover {
cursor: pointer;
background-color: #f5f6f7;
}
&:active {
cursor: pointer;
background-color: #eff0f1;
}
}
}
</style>

View File

@ -0,0 +1,35 @@
export interface ShareInfo {
id: string
exp?: number
uuid: string
pwd?: string
}
export const SHARE_BASE = '/de-link/'
export const shortcuts = [
{
text: '一小时',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 3600 * 1000)
return date
}
},
{
text: '一天',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 3600 * 1000 * 24)
return date
}
},
{
text: '一周',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 7 * 3600 * 1000 * 24)
return date
}
}
]

View File

@ -15,7 +15,8 @@ const options = [
const state = reactive({
form: reactive({
dsIntervalTime: '30',
dsExecuteTime: 'minute'
dsExecuteTime: 'minute',
frontTimeOut: '30'
}),
settingList: []
})
@ -165,7 +166,19 @@ defineExpose({
</el-select>
<span class="ds-span">执行一次</span>
</div>
<div v-else />
<div v-else-if="item.pkey === 'frontTimeOut'">
<el-input-number
v-model="state.form.frontTimeOut"
autocomplete="off"
step-strictly
class="text-left"
:min="1"
:placeholder="t('common.inputText')"
controls-position="right"
type="number"
/>
</div>
<v-else />
</el-form-item>
</el-form>
<template #footer>

View File

@ -22,7 +22,7 @@ const editor = ref()
const infoTemplate = ref()
const tooltips = [
{
key: '请求超时时间',
key: 'setting_basic.frontTimeOut',
val: '请求超时时间(单位:秒,注意:保存后刷新浏览器生效)'
}
]
@ -45,6 +45,7 @@ const search = cb => {
item.pval = item.pval
}
item.pkey = 'setting_' + item.pkey
console.log(item.pkey)
state.templateList.push(item)
}
cb && cb()

View File

@ -103,7 +103,7 @@ const rule = reactive<FormRules>({
},
{
min: 2,
max: 25,
max: 64,
message: t('datasource.input_limit_2_25', [2, 64]),
trigger: 'blur'
}

View File

@ -272,6 +272,7 @@ const saveDataset = () => {
showClose: false,
tip: ''
}
request.apiConfiguration = ''
checkRepeat(request).then(res => {
if (res) {
ElMessageBox.confirm(t('datasource.has_same_ds'), options as ElMessageBoxOptions).then(

View File

@ -64,7 +64,7 @@ const defaultRule = {
},
{
min: 2,
max: 25,
max: 64,
message: t('datasource.input_limit_2_25', [2, 64]),
trigger: 'blur'
}

View File

@ -391,6 +391,7 @@ const saveDS = () => {
request.configuration = Base64.encode(JSON.stringify(request.configuration))
}
const validate = detail.value.submitForm()
request.apiConfiguration = ''
validate(val => {
if (val) {
if (editDs.value && form.id) {
@ -401,7 +402,6 @@ const saveDS = () => {
showClose: false,
tip: ''
}
checkRepeat(request).then(res => {
if (res) {
ElMessageBox.confirm(t('datasource.has_same_ds'), options as ElMessageBoxOptions).then(

View File

@ -6,11 +6,13 @@ import GridTable from '@/components/grid-table/src/GridTable.vue'
import { useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { shortcutOption } from './ShortcutOption'
import { XpackComponent } from '@/components/plugin'
/* import { XpackComponent } from '@/components/plugin' */
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { storeApi } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
import { useUserStoreWithOut } from '@/store/modules/user'
import ShareGrid from '@/views/share/share/ShareGrid.vue'
import ShareHandler from '@/views/share/share/ShareHandler.vue'
const userStore = useUserStoreWithOut()
const { resolve } = useRouter()
const { t } = useI18n()
@ -111,17 +113,18 @@ const loadTableData = () => {
})
}
const panelLoad = paneInfo => {
/* const panelLoad = paneInfo => {
tablePaneList.value.push({
title: paneInfo.title,
name: paneInfo.name,
disabled: tablePaneList.value[1].disabled
})
}
} */
const tablePaneList = ref([
{ title: '最近使用', name: 'recent', disabled: false },
{ title: '我的收藏', name: 'store', disabled: false }
{ title: '我的收藏', name: 'store', disabled: false },
{ title: t('visualization.share_out'), name: 'share', disabled: false }
])
const busiAuthList = getBusiListWithPermission()
@ -206,8 +209,9 @@ const getEmptyDesc = (): string => {
</template>
</el-tab-pane>
</el-tabs>
<XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" />
<XpackComponent :active-name="activeName" jsname="c2hhcmU=" @set-loading="setLoading" />
<!-- <XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" /> -->
<!-- <XpackComponent :active-name="activeName" jsname="c2hhcmU=" @set-loading="setLoading" /> -->
<share-grid :active-name="activeName" @set-loading="setLoading" />
<el-row v-if="activeName === 'recent' || activeName === 'store'">
<el-col :span="12">
<el-select
@ -304,14 +308,19 @@ const getEmptyDesc = (): string => {
<Icon name="icon_pc_outlined"></Icon>
</el-icon>
</el-tooltip>
<XpackComponent
<ShareHandler
:in-grid="true"
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/>
<!-- <XpackComponent
:in-grid="true"
jsname="c2hhcmUtaGFuZGxlcg=="
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/>
/> -->
</template>
<template v-if="['dataset'].includes(scope.row.type)">

@ -1 +1 @@
Subproject commit 8b378277440ec171aa960a537274017d0609551b
Subproject commit c941619c1cb929453b6a8bf4517e3f0036232128

View File

@ -0,0 +1,11 @@
version: '2.1'
services:
task-actuator:
image: registry.cn-qingdao.aliyuncs.com/dataease/dataease-sync-task:DE_TAG
container_name: sync-task-actuator
ports:
- 9001:9001
volumes:
- ${DE_BASE}/dataease2.0/logs:/opt/dataease2.0/logs
networks:
- dataease-network

View File

@ -13,4 +13,9 @@ spring:
dataease:
apisix-api:
domain: http://apisix:9180
key: DE_APISIX_KEY
key: DE_APISIX_KEY
task:
executor:
address: http://sync-task-actuator:9001
log:
path: /opt/dataease2.0/logs/sync-task/task-handler-log

View File

@ -52,6 +52,14 @@ function _check_apisix_init() {
_prepare_apisix
fi
}
function _check_task_init() {
if [[ $DE_INSTALL_MODE != "community" ]];then
_prepare_task
fi
}
function _prepare_task() {
compose_files="${compose_files} -f docker-compose-task.yml"
}
function _prepare_apisix() {
if [[ -z $DE_APISIX_KEY ]];then
need_init_apisix=true
@ -132,6 +140,7 @@ function status() {
function start() {
echo
_check_apisix_init
_check_task_init
cd ${DE_RUNNING_BASE}
${compose_cmd} ${compose_files} up -d
_healthcheck

View File

@ -63,7 +63,8 @@ env | grep DE_ >.env
mkdir -p ${DE_RUN_BASE}/{cache,logs,conf}
mkdir -p ${DE_RUN_BASE}/data/{mysql,static-resource,map,etcd_data,geo}
mkdir -p ${DE_RUN_BASE}/apisix/logs
chmod 777 ${DE_RUN_BASE}/apisix/logs ${DE_RUN_BASE}/data/etcd_data
mkdir -p ${DE_RUN_BASE}/task/logs
chmod 777 ${DE_RUN_BASE}/apisix/logs ${DE_RUN_BASE}/data/etcd_data ${DE_RUN_BASE}/task/logs
if [ "${DE_EXTERNAL_MYSQL}" = "false" ]; then
compose_files="${compose_files} -f docker-compose-mysql.yml"

View File

@ -39,4 +39,8 @@ public interface SysParameterApi {
@PostMapping("/basic/save")
void saveBasicSetting(@RequestBody List<SettingItemVO> settingItemVOS);
@Operation(summary = "查询超时时间(非xpack)")
@GetMapping("/requestTimeOut")
public Integer RequestTimeOut();
}

20
sdk/api/api-sync/pom.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>api</artifactId>
<groupId>io.dataease</groupId>
<version>${dataease.version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api-sync</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@ -0,0 +1,80 @@
package io.dataease.api.sync.datasource.api;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.dataease.api.sync.datasource.dto.DBTableDTO;
import io.dataease.api.sync.datasource.dto.GetDatasourceRequest;
import io.dataease.api.sync.datasource.dto.SyncDatasourceDTO;
import io.dataease.api.sync.datasource.vo.SyncDatasourceVO;
import io.dataease.auth.DeApiPath;
import io.dataease.auth.DePermit;
import io.dataease.exception.DEException;
import io.dataease.request.BaseGridRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
import java.util.Map;
import static io.dataease.constant.AuthResourceEnum.SYNC_DATASOURCE;
/**
* @author fit2cloud
* @date 2023/11/20 10:14
**/
@DeApiPath(value = "/sync/datasource", rt = SYNC_DATASOURCE)
public interface SyncDatasourceApi {
@DePermit("m:read")
@PostMapping("/source/pager/{goPage}/{pageSize}")
IPage<SyncDatasourceVO> sourcePager(@PathVariable("goPage") int goPage, @PathVariable("pageSize") int pageSize, @RequestBody BaseGridRequest request);
@DePermit("m:read")
@PostMapping("/target/pager/{goPage}/{pageSize}")
IPage<SyncDatasourceVO> targetPager(@PathVariable("goPage") int goPage, @PathVariable("pageSize") int pageSize, @RequestBody BaseGridRequest request);
@PostMapping("/save")
void save(@RequestBody SyncDatasourceDTO dataSourceDTO) throws DEException;
@PostMapping("/update")
void update(@RequestBody SyncDatasourceDTO dataSourceDTO) throws DEException;
@PostMapping("/delete/{datasourceId}")
void delete(@PathVariable("datasourceId") String datasourceId) throws DEException;
@GetMapping("/types")
Object datasourceTypes() throws DEException;
@PostMapping("/validate")
String validate(@RequestBody SyncDatasourceDTO dataSourceDTO) throws DEException;
@PostMapping("/getSchema")
List<String> getSchema(@RequestBody SyncDatasourceDTO dataSourceDTO) throws DEException;
@DePermit({"#p0+':manage'"})
@GetMapping("/validate/{datasourceId}")
SyncDatasourceDTO validate(@PathVariable("datasourceId") String datasourceId) throws DEException;
@PostMapping("/latestUse")
List<String> latestUse();
@GetMapping("/get/{datasourceId}")
SyncDatasourceDTO get(@PathVariable("datasourceId") String datasourceId) throws DEException;
@PostMapping("/batchDel")
void batchDel(@RequestBody List<String> ids) throws DEException;
@PostMapping("/fields")
Map<String, Object> getFields(@RequestBody GetDatasourceRequest getDsRequest) throws DEException ;
@GetMapping("/list/{type}")
List<SyncDatasourceDTO> listByType(@PathVariable("type") String type) throws DEException;
@GetMapping("/table/list/{dsId}")
List<DBTableDTO> getTableList(@PathVariable("dsId") String dsId) throws DEException;
}

View File

@ -0,0 +1,18 @@
package io.dataease.api.sync.datasource.dto;
import lombok.Getter;
import lombok.Setter;
/**
* @Author gin
* @Date 2021/4/30 10:57 上午
*/
@Getter
@Setter
public class DBTableDTO {
private String datasourceId;
private String name;
private String remark;
private boolean enableCheck;
private String datasetPath;
}

View File

@ -0,0 +1,25 @@
package io.dataease.api.sync.datasource.dto;
import lombok.Data;
@Data
public class GetDatasourceRequest extends SyncDatasourceDTO {
/**
* 查询sql
*/
private String query;
/**
* 表名
*/
private String table;
/**
* 表格的抽取数据方式
*/
private boolean tableExtract;
/**
* 不为空时获取源数据库表字段将转换为doris的数据类型
*/
private String targetDbType;
}

View File

@ -0,0 +1,61 @@
package io.dataease.api.sync.datasource.dto;
import lombok.Data;
/**
* @author fit2cloud
* @date 2023/11/20 11:14
**/
@Data
public class SyncDatasourceDTO {
/**
* ID
*/
private String id;
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String desc;
/**
* 类型
*/
private String type;
/**
* 详细信息
*/
private String configuration;
/**
* Create timestamp
*/
private Long createTime;
/**
* Update timestamp
*/
private Long updateTime;
/**
* 创建人ID
*/
private Long createBy;
private String createByUserName;
/**
* 状态
*/
private String status;
private String statusRemark;
}

View File

@ -0,0 +1,57 @@
package io.dataease.api.sync.datasource.vo;
import lombok.Data;
/**
* @author fit2cloud
* @date 2023/11/20 11:18
**/
@Data
public class SyncDatasourceVO {
/**
* ID
*/
private String id;
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String desc;
/**
* 类型
*/
private String type;
/**
* 详细信息
*/
private String configuration;
/**
* Create timestamp
*/
private Long createTime;
/**
* Update timestamp
*/
private Long updateTime;
/**
* 创建人
*/
private Long createBy;
private String createByName;
/**
* 状态
*/
private String status;
private String statusRemark;
}

View File

@ -0,0 +1,26 @@
package io.dataease.api.sync.summary.api;
import io.dataease.auth.DeApiPath;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Map;
import static io.dataease.constant.AuthResourceEnum.SUMMARY;
/**
* @author fit2cloud
* @date 2023/12/4 12:43
**/
@DeApiPath(value = "/sync/summary", rt = SUMMARY)
public interface SummaryApi {
@GetMapping("/resourceCount")
Map<String, Long> resourceCount();
@PostMapping("/logChartData")
Map<String, Object> logChartData(@RequestBody String executeTaskLogDate);
}

View File

@ -0,0 +1,52 @@
package io.dataease.api.sync.task.api;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.dataease.api.sync.task.dto.TaskInfoDTO;
import io.dataease.api.sync.task.vo.TaskInfoVO;
import io.dataease.auth.DeApiPath;
import io.dataease.exception.DEException;
import io.dataease.request.BaseGridRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static io.dataease.constant.AuthResourceEnum.TASK;
/**
* @author fit2cloud
* @date 2023/11/20 10:14
**/
@DeApiPath(value = "/sync/task", rt = TASK)
public interface TaskApi {
@PostMapping("/pager/{goPage}/{pageSize}")
IPage<TaskInfoVO> pager(@PathVariable("goPage") int goPage, @PathVariable("pageSize") int pageSize, @RequestBody BaseGridRequest request);
@PostMapping("/add")
void add(@RequestBody TaskInfoDTO jobInfo) throws DEException;
@PostMapping("/update")
void update(@RequestBody TaskInfoDTO jobInfo) throws DEException;
@DeleteMapping("/remove/{id}")
void remove(@PathVariable(value = "id") String id) throws DEException;
@GetMapping("start/{id}")
void startJob(@PathVariable(value = "id") String id) throws DEException;
@GetMapping("stop/{id}")
void stopJob(@PathVariable(value = "id") String id) throws DEException;
@GetMapping("/get/{id}")
TaskInfoVO getOneById(@PathVariable(value = "id") String id) throws DEException;
@GetMapping("/execute/{id}")
void execute(@PathVariable(value = "id") String id) throws DEException;
@PostMapping("/batch/del")
void batchDelete(@RequestBody List<String> ids) throws DEException;
@GetMapping("/count")
Long count() throws DEException;
}

View File

@ -0,0 +1,39 @@
package io.dataease.api.sync.task.api;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.dataease.api.sync.task.vo.LogResultVO;
import io.dataease.api.sync.task.vo.TaskLogVO;
import io.dataease.auth.DeApiPath;
import io.dataease.request.BaseGridRequest;
import org.springframework.web.bind.annotation.*;
import static io.dataease.constant.AuthResourceEnum.TASK_LOG;
/**
* @author fit2cloud
* @date 2023/12/4 12:43
**/
@DeApiPath(value = "/sync/task/log", rt = TASK_LOG)
public interface TaskLogApi {
@PostMapping("/pager/{goPage}/{pageSize}")
IPage<TaskLogVO> pager(@PathVariable("goPage") int goPage, @PathVariable("pageSize") int pageSize, @RequestBody BaseGridRequest request);
@GetMapping("/detail/{logId}/{fromLineNum}")
LogResultVO logDetail(@PathVariable("logId") String logId, @PathVariable("fromLineNum") int fromLineNum);
@PostMapping("/save")
void saveLog(@RequestBody TaskLogVO logDetail);
@PostMapping("/update")
void updateLog(@RequestBody TaskLogVO logDetail);
@DeleteMapping("/deleteByJobId/{jobId}")
void deleteByJobId(@PathVariable("jobId") String jobId);
@DeleteMapping("/delete/{logId}")
void deleteById(@PathVariable("logId") String logId);
@PostMapping("/clear")
void clearJobLog(@RequestBody TaskLogVO taskLogVO);
}

View File

@ -0,0 +1,23 @@
package io.dataease.api.sync.task.dto;
import io.dataease.api.sync.datasource.dto.SyncDatasourceDTO;
import lombok.Data;
import java.util.List;
/**
* @author fit2cloud
* @date 2023/8/10 16:38
**/
@Data
public class Source {
private String type;
private String query;
private String tables;
private SyncDatasourceDTO datasource;
private String datasourceId;
private String tableExtract;
private List<TableField> fieldList;
private String incrementField;
}

View File

@ -0,0 +1,22 @@
package io.dataease.api.sync.task.dto;
import lombok.Getter;
import lombok.Setter;
/**
* @author fit2cloud
*/
@Setter
@Getter
public class TableField {
private String fieldSource;
private String fieldName;
private String remarks;
private String fieldType;
private int fieldSize;
private boolean fieldPk;
private boolean fieldIndex;
private int accuracy;
private Object defaultValue;
}

View File

@ -0,0 +1,21 @@
package io.dataease.api.sync.task.dto;
import io.dataease.api.sync.datasource.dto.SyncDatasourceDTO;
import lombok.Data;
import java.util.List;
/**
* @author fit2cloud
* @date 2023/8/10 16:39
**/
@Data
public class Target {
private String type;
private String createTable;
private List<TableField> fieldList;
private String tableName;
private SyncDatasourceDTO datasource;
private String datasourceId;
private String targetProperty;
}

View File

@ -0,0 +1,112 @@
package io.dataease.api.sync.task.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author fit2cloud
* @date 2023/11/28 17:17
**/
@Data
public class TaskInfoDTO {
private String id;
private String name;
/**
* 任务类型KEY
*/
private String jobKey;
private String desc;
private LocalDateTime createTime;
private LocalDateTime modifyTime;
/**
* 创建人
*/
private Long createBy;
/**
* 修改人
*/
private Long modifyBy;
/**
* 任务参数
*/
private String parameter;
/**
* 扩展参数
*/
private String extParameter;
/**
* 当前任务状态
* unexecuted未执行 currentTime<startTime
* waiting等待执行 stopTime>=currentTime>=startTime,status=1
* suspend暂停 stopTime>=currentTime>=startTime,status=0
* done执行结束 currentTime>stopTime
* running执行中,通过当前任务的日志状态判断如果有日志在执行中
*/
private String status;
/**
* 删除标识
*/
private Boolean deleted;
/**
* 任务执行超时时间
*/
private Long executorTimeout;
/**
* 任务执行失败重试次数
*/
private Long executorFailRetryCount;
/**
* 上次调度时间
*/
private Long triggerLastTime;
/**
* 下次次调度时间
*/
private Long triggerNextTime;
/**
* 调度类型,NONE,CRON,FIX_RATE,FIX_DELAY
*/
private String schedulerType;
/**
* 调度配置取决于调度类型
*/
private String schedulerConf;
/**
* 开始时间
*/
private String startTime;
/**
* 结束时间
*/
private String stopTime;
/**
* 源数据源信息
*/
private Source source;
/**
* 目标数据源信息
*/
private Target target;
}

View File

@ -0,0 +1,44 @@
package io.dataease.api.sync.task.vo;
import lombok.Getter;
import lombok.Setter;
/**
* 日志返回结果
*
* @author fit2cloud
*/
@Getter
@Setter
public class LogResultVO {
/**
* 日志开始行号
*/
private int fromLineNum;
/**
* 日志结束行号
*/
private int toLineNum;
/**
* 日志内容
*/
private String logContent;
/**
* 是否是最后一行
*/
private boolean isEnd;
public LogResultVO() {
}
public LogResultVO(int fromLineNum, int toLineNum, String logContent, boolean isEnd) {
this.fromLineNum = fromLineNum;
this.toLineNum = toLineNum;
this.logContent = logContent;
this.isEnd = isEnd;
}
}

View File

@ -0,0 +1,132 @@
package io.dataease.api.sync.task.vo;
import io.dataease.api.sync.task.dto.Source;
import io.dataease.api.sync.task.dto.Target;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author fit2cloud
* @date 2023/11/28 17:15
**/
@Data
public class TaskInfoVO {
private String id;
private String name;
/**
* 任务类型KEY
*/
private String jobKey;
private String desc;
private LocalDateTime createTime;
private LocalDateTime modifyTime;
/**
* 创建人
*/
private Long createBy;;
/**
* 创建人
*/
private String userName;
/**
* 任务参数
*/
private String parameter;
/**
* 扩展参数
*/
private String extParameter;
/**
* 任务状态
* unexecuted未执行 currentTime<startTime
* waiting等待执行 stopTime>=currentTime>=startTime,status=1
* suspend暂停 stopTime>=currentTime>=startTime,status=0
* done执行结束 currentTime>stopTime
* running执行中,通过当前任务的日志状态判断如果有日志在执行中
*/
private String status;
/**
* 删除标识
*/
private Boolean deleted;
/**
* 任务执行超时时间
*/
private Long executorTimeout;
/**
* 任务执行失败重试次数
*/
private Long executorFailRetryCount;
/**
* 上次调度时间
*/
private Long triggerLastTime;
/**
* 下次次调度时间
*/
private Long triggerNextTime;
/**
* 调度类型,NONE,CRON,FIX_RATE,FIX_DELAY
*/
private String schedulerType;
/**
* 调度配置取决于调度类型
*/
private String schedulerConf;
/**
* 开始时间
*/
private Long startTime;
/**
* 结束时间
*/
private Long stopTime;
private Source source;
private Target target;
/**
* 上次执行结果获取任务最新的日志状态
* running执行中
* success
* fail失败
*/
private String lastExecuteStatus;
private Long incrementValue;
// 以下为日志信息
private String logId;
private Long executorStartTime;
private Long executorEndTime;
private String executorMsg;
/**
* 成功SUCCESS,失败FAIL,执行中RUNNING
*/
private String logStatus;
/**
* 在执行周期内
*/
private boolean withinCycle;
}

View File

@ -0,0 +1,25 @@
package io.dataease.api.sync.task.vo;
import lombok.Data;
/**
* 任务日志
* @author fit2cloud
* @date 2023/9/19 17:44
**/
@Data
public class TaskLogVO {
private String id;
private String jobId;
private String jobName;
private String jobDesc;
private Long executorStartTime;
private Long executorEndTime;
private String status;
private String executorMsg;
private String executorAddress;
private String clearType;
}

View File

@ -15,6 +15,7 @@
<modules>
<module>api-permissions</module>
<module>api-base</module>
<module>api-sync</module>
</modules>
<dependencies>

View File

@ -2,7 +2,7 @@ package io.dataease.constant;
public enum AuthResourceEnum {
PANEL(2, 1), SCREEN(3, 2), DATASET(5, 3), DATASOURCE(6, 4), SYSTEM(7, 0), USER(8, 5), ROLE(8, 6), ORG(9, 7);
PANEL(2, 1), SCREEN(3, 2), DATASET(5, 3), DATASOURCE(6, 4), SYSTEM(7, 0), USER(8, 5), ROLE(8, 6), ORG(9, 7), SYNC_DATASOURCE(10, 8), TASK(11, 9),TASK_LOG(12, 10), SUMMARY(13, 11);
private long menuId;

View File

@ -3,4 +3,5 @@ package io.dataease.constant;
public class XpackSettingConstants {
public static final String AUTO_CREATE_USER = "basic.autoCreateUser";
public static final String Front_Time_Out = "basic.frontTimeOut";
}

View File

@ -18,6 +18,7 @@ public class WhitelistUtils {
"/panel.html",
"/lark/info",
"/lark/token",
"/sysParameter/requestTimeOut",
"/setting/authentication/status",
"/");