diff --git a/Dockerfile b/Dockerfile index f5118e68fd..986319240f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/core/core-backend/pom.xml b/core/core-backend/pom.xml index 1e43ec1d51..72a5f97b9e 100644 --- a/core/core-backend/pom.xml +++ b/core/core-backend/pom.xml @@ -23,6 +23,11 @@ api-permissions ${dataease.version} + + io.dataease + api-sync + ${dataease.version} + com.mysql mysql-connector-j diff --git a/core/core-backend/src/main/java/io/dataease/commons/utils/SqlparserUtils.java b/core/core-backend/src/main/java/io/dataease/commons/utils/SqlparserUtils.java index eacb52d182..4b6ffc6c28 100644 --- a/core/core-backend/src/main/java/io/dataease/commons/utils/SqlparserUtils.java +++ b/core/core-backend/src/main/java/io/dataease/commons/utils/SqlparserUtils.java @@ -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 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); + } + } } } diff --git a/core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java b/core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java index 454e1f80be..ee875b83aa 100644 --- a/core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java +++ b/core/core-backend/src/main/java/io/dataease/datasource/server/DatasourceServer.java @@ -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 diff --git a/core/core-backend/src/main/java/io/dataease/share/dao/auto/entity/XpackShare.java b/core/core-backend/src/main/java/io/dataease/share/dao/auto/entity/XpackShare.java new file mode 100644 index 0000000000..2ea358f8ae --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/dao/auto/entity/XpackShare.java @@ -0,0 +1,150 @@ +package io.dataease.share.dao.auto.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; + +/** + *

+ * + *

+ * + * @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 + + "}"; + } +} diff --git a/core/core-backend/src/main/java/io/dataease/share/dao/auto/mapper/XpackShareMapper.java b/core/core-backend/src/main/java/io/dataease/share/dao/auto/mapper/XpackShareMapper.java new file mode 100644 index 0000000000..488a2acc43 --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/dao/auto/mapper/XpackShareMapper.java @@ -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; + +/** + *

+ * Mapper 接口 + *

+ * + * @author fit2cloud + * @since 2023-09-22 + */ +@Mapper +public interface XpackShareMapper extends BaseMapper { + +} diff --git a/core/core-backend/src/main/java/io/dataease/share/dao/ext/mapper/XpackShareExtMapper.java b/core/core-backend/src/main/java/io/dataease/share/dao/ext/mapper/XpackShareExtMapper.java new file mode 100644 index 0000000000..4b122e87b7 --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/dao/ext/mapper/XpackShareExtMapper.java @@ -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 query(IPage page, @Param("ew") QueryWrapper ew); + + @Select("select type from data_visualization_info where id = #{id}") + String visualizationType(@Param("id") Long id); +} diff --git a/core/core-backend/src/main/java/io/dataease/share/dao/ext/po/XpackSharePO.java b/core/core-backend/src/main/java/io/dataease/share/dao/ext/po/XpackSharePO.java new file mode 100644 index 0000000000..9b89fb7b03 --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/dao/ext/po/XpackSharePO.java @@ -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; + +} diff --git a/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java b/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java new file mode 100644 index 0000000000..c325161e67 --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/manage/XpackShareManage.java @@ -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 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 querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) { + Long uid = AuthUtils.getUser().getUserId(); + QueryWrapper 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 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 query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) { + IPage poiPage = proxy().querySharePage(pageNum, pageSize, request); + List vos = proxy().formatResult(poiPage.getRecords()); + IPage 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 formatResult(List 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 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 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 queryRelationByUserId(Long uid) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("creator", uid); + List result = xpackShareMapper.selectList(queryWrapper); + if (CollectionUtils.isNotEmpty(result)) { + return result.stream() + .collect(Collectors.toMap(xpackShare -> String.valueOf(xpackShare.getResourceId()), XpackShare::getUuid)); + } + return new HashMap<>(); + } +} diff --git a/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java b/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java new file mode 100644 index 0000000000..94bb97bb25 --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/server/XpackShareServer.java @@ -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 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 queryRelationByUserId(@PathVariable("uid") Long uid) { + return xpackShareManage.queryRelationByUserId(uid); + } +} diff --git a/core/core-backend/src/main/java/io/dataease/share/util/LinkTokenUtil.java b/core/core-backend/src/main/java/io/dataease/share/util/LinkTokenUtil.java new file mode 100644 index 0000000000..e460e9361e --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/share/util/LinkTokenUtil.java @@ -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); + } +} diff --git a/core/core-backend/src/main/java/io/dataease/system/server/SysParameterServer.java b/core/core-backend/src/main/java/io/dataease/system/server/SysParameterServer.java index eb33745da1..f1a2cd8450 100644 --- a/core/core-backend/src/main/java/io/dataease/system/server/SysParameterServer.java +++ b/core/core-backend/src/main/java/io/dataease/system/server/SysParameterServer.java @@ -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 settingItemVOS) { sysParameterManage.saveBasic(settingItemVOS); } + + @Override + public Integer RequestTimeOut() { + Integer frontTimeOut = 60; + List 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; + } } diff --git a/core/core-backend/src/main/resources/application.yml b/core/core-backend/src/main/resources/application.yml index f838d5d272..87a0091da2 100644 --- a/core/core-backend/src/main/resources/application.yml +++ b/core/core-backend/src/main/resources/application.yml @@ -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 diff --git a/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql b/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql index e2ff6f3fd5..e26a77c3ec 100644 --- a/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql +++ b/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql @@ -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); diff --git a/core/core-backend/src/main/resources/db/migration/V2.2__update_table_desc_ddl.sql b/core/core-backend/src/main/resources/db/migration/V2.2__update_table_desc_ddl.sql index 44c70caad0..7104b4988d 100644 --- a/core/core-backend/src/main/resources/db/migration/V2.2__update_table_desc_ddl.sql +++ b/core/core-backend/src/main/resources/db/migration/V2.2__update_table_desc_ddl.sql @@ -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`; + + */ diff --git a/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql b/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql index e2ff6f3fd5..e26a77c3ec 100644 --- a/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql +++ b/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql @@ -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); diff --git a/core/core-backend/src/main/resources/i18n/core_zh_CN.properties b/core/core-backend/src/main/resources/i18n/core_zh_CN.properties index d2f6d4d8c5..926674c050 100644 --- a/core/core-backend/src/main/resources/i18n/core_zh_CN.properties +++ b/core/core-backend/src/main/resources/i18n/core_zh_CN.properties @@ -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 diff --git a/core/core-frontend/src/api/datasource.ts b/core/core-frontend/src/api/datasource.ts index 1b32dced92..bc51454740 100644 --- a/core/core-frontend/src/api/datasource.ts +++ b/core/core-frontend/src/api/datasource.ts @@ -111,6 +111,14 @@ export const getDatasetTree = async (data = {}): Promise => { }) } +export const getDsTree = async (data = {}): Promise => { + 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 }) diff --git a/core/core-frontend/src/api/sync/syncDatasource.ts b/core/core-frontend/src/api/sync/syncDatasource.ts new file mode 100644 index 0000000000..5a9686c8fd --- /dev/null +++ b/core/core-frontend/src/api/sync/syncDatasource.ts @@ -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 }) +} diff --git a/core/core-frontend/src/api/sync/syncSummary.ts b/core/core-frontend/src/api/sync/syncSummary.ts new file mode 100644 index 0000000000..801c83f963 --- /dev/null +++ b/core/core-frontend/src/api/sync/syncSummary.ts @@ -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: '' + }) +} diff --git a/core/core-frontend/src/api/sync/syncTask.ts b/core/core-frontend/src/api/sync/syncTask.ts new file mode 100644 index 0000000000..d359119601 --- /dev/null +++ b/core/core-frontend/src/api/sync/syncTask.ts @@ -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}` }) +} diff --git a/core/core-frontend/src/api/sync/syncTaskLog.ts b/core/core-frontend/src/api/sync/syncTaskLog.ts new file mode 100644 index 0000000000..d9b6ffc3f6 --- /dev/null +++ b/core/core-frontend/src/api/sync/syncTaskLog.ts @@ -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 }) +} diff --git a/core/core-frontend/src/assets/svg/icon_sync_close_log_details.svg b/core/core-frontend/src/assets/svg/icon_sync_close_log_details.svg new file mode 100644 index 0000000000..7c0dc4cbc5 --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_close_log_details.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/core-frontend/src/assets/svg/icon_sync_datasource.svg b/core/core-frontend/src/assets/svg/icon_sync_datasource.svg new file mode 100644 index 0000000000..1e3fc9db1d --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_datasource.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/icon_sync_log_number.svg b/core/core-frontend/src/assets/svg/icon_sync_log_number.svg new file mode 100644 index 0000000000..49869e0981 --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_log_number.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/icon_sync_logs_outlined.svg b/core/core-frontend/src/assets/svg/icon_sync_logs_outlined.svg new file mode 100644 index 0000000000..e310452c05 --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_logs_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/core-frontend/src/assets/svg/icon_sync_target_to_datasource.svg b/core/core-frontend/src/assets/svg/icon_sync_target_to_datasource.svg new file mode 100644 index 0000000000..d84bc12a9e --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_target_to_datasource.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/core-frontend/src/assets/svg/icon_sync_task_number.svg b/core/core-frontend/src/assets/svg/icon_sync_task_number.svg new file mode 100644 index 0000000000..bce4a1e198 --- /dev/null +++ b/core/core-frontend/src/assets/svg/icon_sync_task_number.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/components/drawer-filter/src/DrawerTimeFilter.vue b/core/core-frontend/src/components/drawer-filter/src/DrawerTimeFilter.vue index 8fd955d120..68ef025353 100644 --- a/core/core-frontend/src/components/drawer-filter/src/DrawerTimeFilter.vue +++ b/core/core-frontend/src/components/drawer-filter/src/DrawerTimeFilter.vue @@ -1,46 +1,66 @@ @@ -48,108 +68,42 @@ defineExpose({
{{ title }}
- {{ $t(ele.name) }} - - - - - - - - +
- diff --git a/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue b/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue index 14b167ef36..13853900ea 100644 --- a/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue +++ b/core/core-frontend/src/components/drawer-main/src/DrawerMain.vue @@ -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)" /> diff --git a/core/core-frontend/src/components/handle-more/src/DvHandleMore.vue b/core/core-frontend/src/components/handle-more/src/DvHandleMore.vue index 2de573b0d7..76930165ea 100644 --- a/core/core-frontend/src/components/handle-more/src/DvHandleMore.vue +++ b/core/core-frontend/src/components/handle-more/src/DvHandleMore.vue @@ -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']) - = T & { config: { loading?: boolean @@ -25,8 +26,10 @@ type InternalAxiosRequestConfigWidthLoading = 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 & { loading?: boolean } ): Promise } + +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', diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index 605dfb1fe3..e39fa744a9 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -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: '启用水印', diff --git a/core/core-frontend/src/permission.ts b/core/core-frontend/src/permission.ts index 550d8bf2a1..62d0c28fb5 100644 --- a/core/core-frontend/src/permission.ts +++ b/core/core-frontend/src/permission.ts @@ -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() diff --git a/core/core-frontend/src/router/index.ts b/core/core-frontend/src/router/index.ts index 41a64f11bf..a6d785a921 100644 --- a/core/core-frontend/src/router/index.ts +++ b/core/core-frontend/src/router/index.ts @@ -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') } ] diff --git a/core/core-frontend/src/views/chart/ChartView.vue b/core/core-frontend/src/views/chart/ChartView.vue new file mode 100644 index 0000000000..27a7d039c7 --- /dev/null +++ b/core/core-frontend/src/views/chart/ChartView.vue @@ -0,0 +1,29 @@ + + + diff --git a/core/core-frontend/src/views/data-visualization/LinkContainer.vue b/core/core-frontend/src/views/data-visualization/LinkContainer.vue index 78a6bef627..a098cca0e3 100644 --- a/core/core-frontend/src/views/data-visualization/LinkContainer.vue +++ b/core/core-frontend/src/views/data-visualization/LinkContainer.vue @@ -1,6 +1,6 @@ diff --git a/core/core-frontend/src/views/data-visualization/PreviewHead.vue b/core/core-frontend/src/views/data-visualization/PreviewHead.vue index 72b6868994..0799b18454 100644 --- a/core/core-frontend/src/views/data-visualization/PreviewHead.vue +++ b/core/core-frontend/src/views/data-visualization/PreviewHead.vue @@ -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( 预览 - { 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) diff --git a/core/core-frontend/src/views/share/link/ShareProxy.ts b/core/core-frontend/src/views/share/link/ShareProxy.ts new file mode 100644 index 0000000000..cc7461ee56 --- /dev/null +++ b/core/core-frontend/src/views/share/link/ShareProxy.ts @@ -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() diff --git a/core/core-frontend/src/views/share/link/error.vue b/core/core-frontend/src/views/share/link/error.vue new file mode 100644 index 0000000000..dad318a8d5 --- /dev/null +++ b/core/core-frontend/src/views/share/link/error.vue @@ -0,0 +1,6 @@ + + diff --git a/core/core-frontend/src/views/share/link/exp.vue b/core/core-frontend/src/views/share/link/exp.vue new file mode 100644 index 0000000000..4fbda75f88 --- /dev/null +++ b/core/core-frontend/src/views/share/link/exp.vue @@ -0,0 +1,6 @@ + + diff --git a/core/core-frontend/src/views/share/link/index.vue b/core/core-frontend/src/views/share/link/index.vue new file mode 100644 index 0000000000..598474a3b2 --- /dev/null +++ b/core/core-frontend/src/views/share/link/index.vue @@ -0,0 +1,55 @@ + + + diff --git a/core/core-frontend/src/views/share/link/pwd.vue b/core/core-frontend/src/views/share/link/pwd.vue new file mode 100644 index 0000000000..327c71d8a1 --- /dev/null +++ b/core/core-frontend/src/views/share/link/pwd.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/core/core-frontend/src/views/share/share/ShareGrid.vue b/core/core-frontend/src/views/share/share/ShareGrid.vue new file mode 100644 index 0000000000..bb8e449b13 --- /dev/null +++ b/core/core-frontend/src/views/share/share/ShareGrid.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/core/core-frontend/src/views/share/share/ShareHandler.vue b/core/core-frontend/src/views/share/share/ShareHandler.vue new file mode 100644 index 0000000000..0d2892c9dd --- /dev/null +++ b/core/core-frontend/src/views/share/share/ShareHandler.vue @@ -0,0 +1,370 @@ + + + + + diff --git a/core/core-frontend/src/views/share/share/SharePanel.vue b/core/core-frontend/src/views/share/share/SharePanel.vue new file mode 100644 index 0000000000..4775ccf04f --- /dev/null +++ b/core/core-frontend/src/views/share/share/SharePanel.vue @@ -0,0 +1,14 @@ + diff --git a/core/core-frontend/src/views/share/share/ShareVisualHead.vue b/core/core-frontend/src/views/share/share/ShareVisualHead.vue new file mode 100644 index 0000000000..5de4706a9c --- /dev/null +++ b/core/core-frontend/src/views/share/share/ShareVisualHead.vue @@ -0,0 +1,332 @@ + + + + + + + diff --git a/core/core-frontend/src/views/share/share/option.ts b/core/core-frontend/src/views/share/share/option.ts new file mode 100644 index 0000000000..f962408183 --- /dev/null +++ b/core/core-frontend/src/views/share/share/option.ts @@ -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 + } + } +] diff --git a/core/core-frontend/src/views/system/parameter/basic/BasicEdit.vue b/core/core-frontend/src/views/system/parameter/basic/BasicEdit.vue index 92768c6426..be9875c849 100644 --- a/core/core-frontend/src/views/system/parameter/basic/BasicEdit.vue +++ b/core/core-frontend/src/views/system/parameter/basic/BasicEdit.vue @@ -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({ 执行一次 -
+
+ +
+ - - + + + { - - +