Merge branch 'master' into develop

This commit is contained in:
RB 2022-11-06 21:32:40 +08:00
commit f80ac11390
143 changed files with 8509 additions and 994 deletions

View file

@ -40,6 +40,8 @@ module.exports = {
React: true,
ReactDOM: true,
PropTypes: true,
RBCOLORS: true,
RBEMOJIS: true,
rb: true,
$setTimeout: true,
$random: true,

View file

@ -1,15 +1,15 @@
[![Codacy](https://api.codacy.com/project/badge/Grade/599a0a3e46f84e6bbc29e8fbe4632860)](https://www.codacy.com/app/getrebuild/rebuild)
[![codecov](https://codecov.io/gh/getrebuild/rebuild/branch/master/graph/badge.svg)](https://codecov.io/gh/getrebuild/rebuild)
[![Package](https://github.com/getrebuild/rebuild/actions/workflows/maven-publish.yml/badge.svg)](https://github.com/getrebuild/rebuild/actions/workflows/maven-publish.yml)
[![Package](https://github.com/getrebuild/rebuild/actions/workflows/maven-publish.yml/badge.svg)](https://github.com/getrebuild?tab=packages&repo_name=rebuild)
[![Build Status](https://travis-ci.com/getrebuild/rebuild.svg?branch=master)](https://travis-ci.com/getrebuild/rebuild)
[![License GPLv3](https://img.shields.io/github/license/getrebuild/rebuild.svg)](LICENSE)
[![License COMMERCIAL](https://img.shields.io/badge/license-COMMERCIAL-orange.svg)](COMMERCIAL)
[![License GPLv3](https://img.shields.io/github/license/getrebuild/rebuild.svg)](https://getrebuild.com/legal/service-terms)
[![License 商业授权](https://img.shields.io/badge/license-%E5%95%86%E4%B8%9A%E6%8E%88%E6%9D%83-red.svg)](https://getrebuild.com/legal/service-terms)
## 项目特色
**_REBUILD 通过创新的业务流程引擎你快速搭建各类企业管理系统全图形化配置无需了解技术。_**
**_REBUILD 通过创新的业务流程引擎帮助你快速搭建各类企业管理系统全图形化配置无需了解技术。_**
REBUILD 侧重于业务需求实现,而非基础技术框架或项目启动模板,通过 REBUILD 可以真正实现零代码快速搭建无需编程、无需编译代码,甚至无需了解技术。
REBUILD 侧重于业务需求实现,而非基础技术框架或项目启动模板,通过 REBUILD 可以真正实现零代码快速搭建无需编程、无需编译代码,甚至无需了解任何技术。
更多详情介绍 [https://getrebuild.com/](https://getrebuild.com/)
@ -39,7 +39,7 @@ REBUILD 侧重于业务需求实现,而非基础技术框架或项目启动模
## 使用
开始使用 REBUILD 非常简单,不需要搭建复杂的运行环境,零依赖快速部署,超简单!
开始使用 REBUILD 非常简单,不需要配置复杂的运行环境,零依赖快速部署,超简单!
#### 1. 使用已发布版本
@ -80,7 +80,7 @@ REBUILD 对于开发环境的要求非常简单,由于使用 Java 开发,因
- JDK 1.8+(兼容 OpenJDK
- MySQL 5.6+
- Redis 3.2+(非必须,默认使用内的 Ehcache 缓存)
- Redis 3.2+(非必须,默认使用内的 Ehcache 缓存)
- Tomcat 8.0+(非必须,默认使用 SpringBooot 内置 Tomcat
- Apache Maven 3.3+
- IDEA 或 Eclipse (for JEE)
@ -93,6 +93,6 @@ REBUILD 使用 GPL-3.0 开源许可和商业授权双重授权协议,使用将
REBUILD uses the GPL-3.0 open source license and commercial license dual license agreement. Use will be deemed your voluntary commitment to accept all terms of the [Agreement](https://getrebuild.com/legal/service-terms).
## 购买商业
## 购买商业授权
从 2.0 版本开始REBUILD 将推出商业版增值功能计划。如果 REBUILD 对贵公司业务有帮助,请考虑 [购买商业授权](https://getrebuild.com/#pricing-plans) 支持 REBUILD 可持续发展。除了增值功能以外,还可以得到更专业的技术支持服务。非常感谢!
从 2.0 版本开始REBUILD 将推出 [增值功能](https://getrebuild.com/docs/rbv-features) 计划。如果 REBUILD 对贵公司业务有帮助,请考虑 [购买商业授权](https://getrebuild.com/#pricing-plans) 以支持 REBUILD 可持续发展。除了永久享有全部最新功能以外,还可以得到更专业的技术支持服务。非常感谢!

47
pom.xml
View file

@ -5,12 +5,12 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<version>2.6.13</version>
<relativePath/>
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>3.1.0-beta1</version>
<version>3.2.0-dev</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT -->
@ -295,15 +295,10 @@
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.12</version>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
@ -317,12 +312,12 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.11.0</version>
<version>7.12.0</version>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
@ -332,7 +327,7 @@
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.2.2</version>
<version>6.3.1</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
@ -372,7 +367,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
<version>3.1.2</version>
<exclusions>
<exclusion>
<artifactId>ehcache</artifactId>
@ -403,12 +398,12 @@
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.2</version>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.17</version>
<version>0.4.18</version>
</dependency>
<dependency>
<groupId>es.moki.ratelimitj</groupId>
@ -423,7 +418,7 @@
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.6</version>
<version>3.17.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -454,10 +449,10 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.6</version>
<version>5.8.9</version>
</dependency>
<!-- CVEs fix -->
<!-- fix: CVEs -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
@ -466,8 +461,22 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
<version>1.22</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.14.0-rc3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.0-rc3</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
</dependencies>
</project>

View file

@ -69,10 +69,8 @@ public class ApiContext {
* @return
*/
public ID getBindUser() {
if (bindUser == null) {
return UserService.SYSTEM_USER;
}
return bindUser;
if (bindUser == null) return UserService.SYSTEM_USER;
else return bindUser;
}
/**
@ -119,8 +117,9 @@ public class ApiContext {
* @return
*/
public ID getParameterAsId(String name) {
String value = getParameterMap().get(name);
return ID.isId(value) ? ID.valueOf(value) : null;
String value = getParameterNotBlank(name);
if (ID.isId(value)) return ID.valueOf(value);
throw new ApiInvokeException(ApiInvokeException.ERR_BADPARAMS, "Parameter [" + name + "] is invalid");
}
/**
@ -130,10 +129,8 @@ public class ApiContext {
*/
public int getParameterAsInt(String name, int defaultValue) {
String value = getParameterMap().get(name);
if (NumberUtils.isNumber(value)) {
return NumberUtils.toInt(value);
}
return defaultValue;
if (NumberUtils.isNumber(value)) return NumberUtils.toInt(value);
else return defaultValue;
}
/**
@ -143,10 +140,8 @@ public class ApiContext {
*/
public long getParameterAsLong(String name, long defaultValue) {
String value = getParameterMap().get(name);
if (NumberUtils.isNumber(value)) {
return NumberUtils.toLong(value);
}
return defaultValue;
if (NumberUtils.isNumber(value)) return NumberUtils.toLong(value);
else return defaultValue;
}
/**
@ -156,9 +151,7 @@ public class ApiContext {
*/
public boolean getParameterAsBool(String name, boolean defaultValue) {
String value = getParameterMap().get(name);
if (StringUtils.isBlank(value)) {
return defaultValue;
}
return BooleanUtils.toBoolean(value);
if (StringUtils.isBlank(value)) return defaultValue;
else return BooleanUtils.toBoolean(value);
}
}

View file

@ -20,6 +20,8 @@ import com.rebuild.core.configuration.RebuildApiManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.DataSpecificationException;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.task.TaskExecutors;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.RateLimiters;
@ -165,6 +167,9 @@ public class ApiGateway extends Controller implements Initialization {
// 明文签名
if (timestamp == null && signType == null) {
if (RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced)) {
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Invalid [timestamp] or [sign_type]");
}
if (!apiConfig.getString("appSecret").equals(sign)) {
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Invalid [sign] : " + sign);
}

View file

@ -40,7 +40,8 @@ public class AuthTokenManager {
*/
public static final String TYPE_ONCE_TOKEN = "RBOT";
private static final int ACCESSTOKEN_EXPIRES = CommonsCache.TS_HOUR * 12;
// 3d
private static final int ACCESSTOKEN_EXPIRES = CommonsCache.TS_HOUR * 24 * 3;
private static final String TOKEN_PREFIX = "TOKEN:";
@ -107,7 +108,7 @@ public class AuthTokenManager {
* @return
*/
public static ID verifyToken(String token) {
return verifyToken(token, Boolean.FALSE);
return verifyToken(token, Boolean.FALSE, Boolean.FALSE);
}
/**
@ -115,9 +116,10 @@ public class AuthTokenManager {
*
* @param token
* @param verifyAfterDestroy
* @param verifyAfterRefresh
* @return
*/
public static ID verifyToken(String token, boolean verifyAfterDestroy) {
public static ID verifyToken(String token, boolean verifyAfterDestroy, boolean verifyAfterRefresh) {
Assert.notNull(token, "[token] cannot be null");
String desc = Application.getCommonsCache().get(TOKEN_PREFIX + token);
if (desc == null) return null;
@ -130,6 +132,11 @@ public class AuthTokenManager {
if (verifyAfterDestroy) {
log.debug("Destroy token ({}) : {}", descs[0], token);
Application.getCommonsCache().evict(TOKEN_PREFIX + token);
verifyAfterRefresh = false;
}
if (verifyAfterRefresh && TYPE_ACCESS_TOKEN.equals(descs[0])) {
Application.getCommonsCache().put(TOKEN_PREFIX + token, desc, ACCESSTOKEN_EXPIRES);
}
return ID.valueOf(descs[1]);
@ -140,7 +147,10 @@ public class AuthTokenManager {
*
* @param token
* @return
* @see #verifyToken(String, boolean, boolean)
* @deprecated Use {@link #verifyToken(String, boolean, boolean)}
*/
@Deprecated
public static ID refreshAccessToken(String token) {
Assert.notNull(token, "[token] cannot be null");
String desc = Application.getCommonsCache().get(TOKEN_PREFIX + token);

View file

@ -30,8 +30,8 @@ public class LoginToken extends BaseApi {
@Override
public JSON execute(ApiContext context) throws ApiInvokeException {
String user = context.getParameterNotBlank("user");
String password = context.getParameterNotBlank("password");
final String user = context.getParameterNotBlank("user");
final String password = context.getParameterNotBlank("password");
if (RateLimiters.RRL_LOGIN.overLimitWhenIncremented("user:" + user)) {
return formatFailure(Language.L("请求过于频繁,请稍后重试"), ApiInvokeException.ERR_FREQUENCY);

View file

@ -67,11 +67,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "3.1.0-beta1";
public static final String VER = "3.2.0-dev";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 3010001;
public static final int BUILD = 3020000;
static {
// Driver for DB

View file

@ -34,6 +34,8 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
/**
@ -180,25 +182,28 @@ public class NavBuilder extends NavManager {
*/
private JSONArray buildAvailableProjects(ID user) {
ConfigBean[] projects = ProjectManager.instance.getAvailable(user);
// 排序 a-z
Arrays.sort(projects, Comparator.comparing(o -> o.getString("projectName")));
JSONArray navsOfProjects = new JSONArray();
JSONArray navsOfProjects2 = new JSONArray();
JSONArray navsOfProjectsArchived = new JSONArray();
for (ConfigBean e : projects) {
JSONObject item = JSONUtils.toJSONObject(
NAV_ITEM_PROPS,
new Object[] { e.getString("iconName"), e.getString("projectName"), NAV_PROJECT, e.getID("id") });
if (e.getInteger("status") == ProjectManager.STATUS_ARCHIVED) {
navsOfProjects2.add(item);
navsOfProjectsArchived.add(item);
} else {
navsOfProjects.add(item);
}
}
if (!navsOfProjects2.isEmpty()) {
// 归档的
if (!navsOfProjectsArchived.isEmpty()) {
navsOfProjects.add(JSONUtils.toJSONObject(
NAV_ITEM_PROPS,
new String[] { null, Language.L("已归档"), NAV_DIVIDER, "ARCHIVED" }));
navsOfProjects.addAll(navsOfProjects2);
navsOfProjects.addAll(navsOfProjectsArchived);
}
// 管理员显示新建项目入口
@ -312,7 +317,10 @@ public class NavBuilder extends NavManager {
navUrl = AppUtils.getContextPath("/app/" + navUrl + "/list");
}
String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
String iconClazz = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
if (iconClazz.startsWith("mdi-")) iconClazz = "mdi " + iconClazz;
else iconClazz = "zmdi zmdi-" + iconClazz;
String navText = item.getString("text");
JSONArray subNavs = null;
@ -331,12 +339,12 @@ public class NavBuilder extends NavManager {
if (BooleanUtils.toBoolean(item.getString("open"))) parentClass += " open";
navItemHtml = String.format(
"<li class=\"%s\" data-entity=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon zmdi zmdi-%s\"></i><span>%s</span></a>",
"<li class=\"%s\" data-entity=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon %s\"></i><span>%s</span></a>",
navName + (subNavs == null ? StringUtils.EMPTY : parentClass),
navEntity == null ? StringUtils.EMPTY : navEntity,
subNavs == null ? navUrl : "###",
isOutUrl ? "_blank" : "_self",
navIcon,
iconClazz,
navText);
}
StringBuilder navHtml = new StringBuilder(navItemHtml);

View file

@ -15,6 +15,9 @@ import com.rebuild.core.configuration.ConfigManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* 分类数据
@ -22,6 +25,7 @@ import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
* @author devezhao zhaofang123@gmail.com
* @since 2019/03/28
*/
@Slf4j
public class ClassificationManager implements ConfigManager {
public static final ClassificationManager instance = new ClassificationManager();
@ -38,8 +42,8 @@ public class ClassificationManager implements ConfigManager {
* @return
*/
public String getName(ID itemId) {
String[] ns = getItemNames(itemId);
return ns == null ? null : ns[0];
Item item = getItem(itemId);
return item == null ? null : item.Name;
}
/**
@ -49,31 +53,32 @@ public class ClassificationManager implements ConfigManager {
* @return
*/
public String getFullName(ID itemId) {
String[] ns = getItemNames(itemId);
return ns == null ? null : ns[1];
Item item = getItem(itemId);
return item == null ? null : item.FullName;
}
/**
* @param itemId
* @return [名称, 全名称]
* @return
*/
private String[] getItemNames(ID itemId) {
final String ckey = "ClassificationNAME-" + itemId;
String[] cached = (String[]) Application.getCommonsCache().getx(ckey);
if (cached != null) {
return cached[0].equals(DELETED_ITEM) ? null : cached;
private Item getItem(ID itemId) {
final String ckey = "ClassificationITEM31-" + itemId;
Item ditem = (Item) Application.getCommonsCache().getx(ckey);
if (ditem != null) {
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
}
Object[] o = Application.createQueryNoFilter(
"select name,fullName from ClassificationData where itemId = ?")
"select name,fullName,code from ClassificationData where itemId = ?")
.setParameter(1, itemId)
.unique();
if (o != null) cached = new String[]{(String) o[0], (String) o[1]};
// 可能已删除
if (cached == null) cached = new String[]{DELETED_ITEM, DELETED_ITEM};
if (o != null) ditem = new Item((String) o[0], (String) o[1], (String) o[2]);
Application.getCommonsCache().putx(ckey, cached);
return cached[0].equals(DELETED_ITEM) ? null : cached;
// 可能已删除
if (ditem == null) ditem = new Item(DELETED_ITEM, null, null);
Application.getCommonsCache().putx(ckey, ditem);
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
}
/**
@ -85,21 +90,23 @@ public class ClassificationManager implements ConfigManager {
*/
public ID findItemByName(String name, Field field) {
ID dataId = getUseClassification(field, false);
if (dataId == null) {
return null;
}
if (dataId == null) return null;
int openLevel = getOpenLevel(field);
// 后匹配
String ql = String.format(
"select itemId from ClassificationData where dataId = '%s' and fullName like '%%%s'", dataId, name);
Object[][] hasMany = Application.createQueryNoFilter(ql).array();
if (hasMany.length == 0) {
"select itemId from ClassificationData where dataId = ? and level = ? and fullName like '%%%s'", name);
Object[][] found = Application.createQueryNoFilter(ql)
.setParameter(1, dataId)
.setParameter(2, openLevel)
.array();
if (found.length == 0) {
return null;
} else if (hasMany.length == 1) {
return (ID) hasMany[0][0];
} else {
// TODO 多个匹配
return (ID) hasMany[0][0];
// TODO 找到多个匹配的优选
return (ID) found[0][0];
}
}
@ -111,12 +118,11 @@ public class ClassificationManager implements ConfigManager {
*/
public int getOpenLevel(Field field) {
ID dataId = getUseClassification(field, false);
if (dataId == null) {
return BAD_CLASSIFICATION;
}
if (dataId == null) return BAD_CLASSIFICATION;
String ckey = "ClassificationLEVEL-" + dataId;
final String ckey = "ClassificationLEVEL-" + dataId;
Integer cLevel = (Integer) Application.getCommonsCache().getx(ckey);
if (cLevel == null) {
Object[] o = Application.createQueryNoFilter(
"select openLevel from Classification where dataId = ?")
@ -141,29 +147,40 @@ public class ClassificationManager implements ConfigManager {
* 获取指定字段所使用的分类
*
* @param field
* @param verfiy
* @param checkBad
* @return
*/
public ID getUseClassification(Field field, boolean verfiy) {
public ID getUseClassification(Field field, boolean checkBad) {
String classUse = EasyMetaFactory.valueOf(field).getExtraAttr(EasyFieldConfigProps.CLASSIFICATION_USE);
ID dataId = ID.isId(classUse) ? ID.valueOf(classUse) : null;
if (dataId == null) {
return null;
}
if (dataId == null) return null;
if (verfiy && getOpenLevel(field) == BAD_CLASSIFICATION) {
return null;
}
return dataId;
if (checkBad && getOpenLevel(field) == BAD_CLASSIFICATION) return null;
else return dataId;
}
@Override
public void clean(Object cid) {
ID id2 = (ID) cid;
if (id2.getEntityCode() == EntityHelper.ClassificationData) {
Application.getCommonsCache().evict("ClassificationNAME-" + cid);
Application.getCommonsCache().evict("ClassificationITEM31-" + cid);
} else if (id2.getEntityCode() == EntityHelper.Classification) {
Application.getCommonsCache().evict("ClassificationLEVEL-" + cid);
}
}
// Bean
public static class Item implements Serializable {
private static final long serialVersionUID = -1903227875771376652L;
Item(String name, String fullName, String code) {
this.Name = name;
this.FullName = fullName;
this.Code = code;
}
final public String Name;
final public String FullName;
final public String Code;
}
}

View file

@ -14,7 +14,6 @@ import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.rebuild.core.Application;
import com.rebuild.core.cache.CacheTemplate;
import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
@ -42,60 +41,58 @@ public class DataListCategory {
* @return
*/
public static JSON datas(Entity entity, ID user) {
final Field classField = getFieldOfCategory(entity);
if (classField == null) return null;
final Field categoryField = getFieldOfCategory(entity);
if (categoryField == null) return null;
final String ckey = String.format("DLC1.%s.%s", entity.getName(), classField.getName());
JSON c = (JSON) Application.getCommonsCache().getx(ckey);
if (c != null) return c;
DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
DisplayType dt = EasyMetaFactory.getDisplayType(classField);
List<Object[]> list = new ArrayList<>();
List<Object[]> clist = new ArrayList<>();
if (dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST) {
ConfigBean[] entries = MultiSelectManager.instance.getPickListRaw(classField, true);
ConfigBean[] entries = MultiSelectManager.instance.getPickListRaw(categoryField, true);
for (ConfigBean e : entries) {
Object id = e.getID("id");
if (dt == DisplayType.MULTISELECT) id = e.getLong("mask");
list.add(new Object[] { e.getString("text"), id });
clist.add(new Object[] { e.getString("text"), id });
}
} else {
// TODO 考虑支持更多分组字段类型例如日期但要考虑日期格式
String sql;
if (dt == DisplayType.N2NREFERENCE) {
sql = MessageFormat.format(
"select referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}'' group by referenceId",
entity.getName(), classField.getName());
"select distinct referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}''",
entity.getName(), categoryField.getName());
} else {
sql = MessageFormat.format(
"select {0} from {1} where {0} is not null group by {0}", classField.getName(), entity.getName());
"select distinct {0} from {1} where {0} is not null", categoryField.getName(), entity.getName());
}
Query query = user == null
? Application.createQueryNoFilter(sql) : Application.getQueryFactory().createQuery(sql, user);
? Application.createQueryNoFilter(sql)
: Application.getQueryFactory().createQuery(sql, user);
Object[][] array = query.array();
for (Object[] o : array) {
Object id = o[0];
Object label = FieldValueHelper.getLabelNotry((ID) id);
list.add(new Object[] { label, id });
clist.add(new Object[] { label, id });
}
list.sort(Comparator.comparing(o -> o[0].toString()));
// TODO 分类数据 code 排序
clist.sort(Comparator.comparing(o -> o[0].toString()));
}
JSONArray res = new JSONArray();
for (Object[] o : list) {
for (Object[] o : clist) {
res.add(JSONUtils.toJSONObject(
new String[] { "label", "id", "count" },
new Object[] { o[0], o[1], 0 } ));
}
// FIXME 1min 缓存
Application.getCommonsCache().putx(ckey, res, CacheTemplate.TS_HOUR / 60); // 1min
return res;
}

View file

@ -23,6 +23,7 @@ import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyEntityConfigProps;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.privileges.bizz.User;
@ -54,8 +55,10 @@ public class FormsBuilder extends FormsManager {
// 分割线
public static final String DIVIDER_LINE = "$DIVIDER$";
// 引用主记录
public static final String DV_MAINID = "$MAINID$";
// 引用记录
public static final String DV_REFERENCE_PREFIX = "&";
@ -193,7 +196,12 @@ public class FormsBuilder extends FormsManager {
if (hasMainEntity != null) {
model.set("mainMeta", EasyMetaFactory.toJSON(hasMainEntity));
} else if (entityMeta.getDetailEntity() != null) {
model.set("detailMeta", EasyMetaFactory.toJSON(entityMeta.getDetailEntity()));
// v3.1
if (!entityMeta.getExtraAttrs().getBooleanValue(EasyEntityConfigProps.NOT_COEDITING)) {
model.set("detailMeta", EasyMetaFactory.toJSON(entityMeta.getDetailEntity()));
model.set("detailsNotEmpty",
entityMeta.getExtraAttrs().getBooleanValue(EasyEntityConfigProps.DETAILS_NOTEMPTY));
}
}
if (recordData != null && recordData.hasValue(EntityHelper.ModifiedOn)) {
@ -266,7 +274,8 @@ public class FormsBuilder extends FormsManager {
final Date now = CalendarUtils.now();
// 新建
final boolean isNew = recordData == null || recordData.getPrimary() == null || EntityHelper.isUnsavedId(recordData.getPrimary());
final boolean isNew = recordData == null || recordData.getPrimary() == null
|| EntityHelper.isUnsavedId(recordData.getPrimary());
// Check and clean
for (Iterator<Object> iter = elements.iterator(); iter.hasNext(); ) {
@ -368,10 +377,11 @@ public class FormsBuilder extends FormsManager {
}
// 父级级联
ID parentValue = dt == DisplayType.REFERENCE && recordData.getPrimary() != null
? getCascadingFieldParentValue(easyField, recordData.getPrimary()) : null;
if (parentValue != null) {
el.put("_cascadingFieldParentValue", parentValue);
if ((dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE) && recordData.getPrimary() != null) {
ID parentValue = getCascadingFieldParentValue(easyField, recordData.getPrimary(), false);
if (parentValue != null) {
el.put("_cascadingFieldParentValue", parentValue);
}
}
}
// 新建记录
@ -433,9 +443,10 @@ public class FormsBuilder extends FormsManager {
}
// v3.1 父级级联
if (entity.getMainEntity() != null && dt == DisplayType.REFERENCE) {
if (entity.getMainEntity() != null && (dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE)) {
ID mainid = FormsBuilderContextHolder.getMainIdOfDetail(false);
ID parentValue = getCascadingFieldParentValue(easyField, mainid);
ID parentValue = EntityHelper.isUnsavedId(mainid) ? null
: getCascadingFieldParentValue(easyField, mainid, true);
if (parentValue != null) {
el.put("_cascadingFieldParentValue", parentValue);
}
@ -549,7 +560,8 @@ public class FormsBuilder extends FormsManager {
inFormFields.add(((JSONObject) o).getString("field"));
}
// 保持在初始值中TODO 更多保持字段
// 保持在初始值中
// TODO 更多保持字段
Set<String> initialValKeeps = new HashSet<>();
Map<String, Object> initialValReady = new HashMap<>();
@ -573,8 +585,10 @@ public class FormsBuilder extends FormsManager {
// 主实体字段
else if (field.equals(DV_MAINID)) {
Field dtmField = MetadataHelper.getDetailToMainField(entity);
Object mixValue = inFormFields.contains(dtmField.getName()) ? getReferenceMixValue(value)
: (DV_MAINID.equals(value) ? EntityHelper.UNSAVED_ID : value);
Object mixValue = inFormFields.contains(dtmField.getName())
? getReferenceMixValue(value)
: (isNewMainId(value) ? EntityHelper.UNSAVED_ID : value);
if (mixValue != null) {
initialValReady.put(dtmField.getName(), mixValue);
initialValKeeps.add(dtmField.getName());
@ -619,7 +633,7 @@ public class FormsBuilder extends FormsManager {
* @return returns [ID, LABEL]
*/
private JSON getReferenceMixValue(String idValue) {
if (DV_MAINID.equals(idValue)) {
if (isNewMainId(idValue)) {
return FieldValueHelper.wrapMixValue(EntityHelper.UNSAVED_ID, Language.L("新的"));
} else if (!ID.isId(idValue)) {
return null;
@ -639,28 +653,36 @@ public class FormsBuilder extends FormsManager {
*
* @param field
* @param record
* @param recordIsMain
* @return
*/
private ID getCascadingFieldParentValue(EasyField field, ID record) {
private ID getCascadingFieldParentValue(EasyField field, ID record, boolean recordIsMain) {
String pf = field.getExtraAttr("_cascadingFieldParent");
if (pf == null) return null;
String[] pfs = pf.split(MetadataHelper.SPLITER_RE);
String fieldParent = pfs[0];
// 明细级联主实体
if (pfs[0].contains(".")) {
// 明细字段使用主实体字段
// format: MAINENTITY.FIELD
boolean useMainField = pfs[0].contains(".");
if (recordIsMain) {
if (useMainField) {
fieldParent = pfs[0].split("\\.")[1];
} else {
return null;
}
} else if (useMainField) {
Field dtf = MetadataHelper.getDetailToMainField(field.getRawMeta().getOwnEntity());
fieldParent = dtf.getName() + "." + pfs[0].split("\\.")[1];
// 明细新建时 record 传入的是主记录
int fieldCode = field.getRawMeta().getOwnEntity().getEntityCode();
int recordCode = record.getEntityCode();
if (fieldCode != recordCode) {
fieldParent = pfs[0].split("\\.")[1];
}
}
Object[] o = Application.getQueryFactory().uniqueNoFilter(record, fieldParent);
return o == null ? null : (ID) o[0];
}
private boolean isNewMainId(Object id) {
return DV_MAINID.equals(id) || EntityHelper.isUnsavedId(id);
}
}

View file

@ -164,7 +164,7 @@ public class EntityHelper {
*
* @param entityCode
* @return
* @see #isUnsavedId(ID)
* @see #isUnsavedId(Object)
*/
public static ID newUnsavedId(int entityCode) {
if (entityCode == 0) return UNSAVED_ID;
@ -174,11 +174,12 @@ public class EntityHelper {
/**
* @param id
* @return
* @see #newUnsavedId(int)
*/
public static boolean isUnsavedId(ID id) {
return UNSAVED_ID.equals(id) || id.toLiteral().endsWith(UNSAVED_ID_SUFFIX);
public static boolean isUnsavedId(Object id) {
return ID.isId(id) && (UNSAVED_ID.equals(id) || id.toString().endsWith(UNSAVED_ID_SUFFIX));
}
// 公共字段/保留字段
public static final String CreatedOn = "createdOn";
@ -196,6 +197,7 @@ public class EntityHelper {
public static final String ApprovalState = "approvalState";
public static final String ApprovalStepNode = "approvalStepNode";
public static final String ApprovalLastUser = "approvalLastUser";
public static final String ApprovalLastRemark = "approvalLastRemark";
// 用户

View file

@ -256,7 +256,8 @@ public class MetadataHelper {
return EntityHelper.ApprovalId.equalsIgnoreCase(fieldName)
|| EntityHelper.ApprovalState.equalsIgnoreCase(fieldName)
|| EntityHelper.ApprovalStepNode.equalsIgnoreCase(fieldName)
|| EntityHelper.ApprovalLastUser.equalsIgnoreCase(fieldName);
|| EntityHelper.ApprovalLastUser.equalsIgnoreCase(fieldName)
|| EntityHelper.ApprovalLastRemark.equalsIgnoreCase(fieldName);
}
/**

View file

@ -158,7 +158,7 @@ public abstract class EasyField extends BaseEasyMeta<Field> {
// abstract T checkoutValue(Object rawValue);
/**
* 信息脱敏
* 启用信息脱敏
*
* @return
* @see com.rebuild.core.support.general.FieldValueHelper#desensitized(EasyField, Object)

View file

@ -180,7 +180,8 @@ public class DynamicMetadataFactory extends ConfigurationMetadataFactory {
extraAttrs.put("displayType", dt.name());
String cascadingField = extraAttrs.getString(EasyFieldConfigProps.REFERENCE_CASCADINGFIELD);
if (StringUtils.isNotBlank(cascadingField) && dt == DisplayType.REFERENCE) {
if (StringUtils.isNotBlank(cascadingField)
&& (dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE)) {
extraAttrs.put("_cascadingFieldParent", cascadingField);
String[] fs = cascadingField.split(SPLITER_RE);
cascadingFieldsChild.add(entityName + SPLITER + fs[0] + SPLITER + fieldName + SPLITER + fs[1]);
@ -191,6 +192,7 @@ public class DynamicMetadataFactory extends ConfigurationMetadataFactory {
// 处理父级级联的父子级关系
// 多个子级可能使用同一个父级字段这里仅保留一个子级对使用效果无影响已布局的情况下
// v3.1 有影响因为如果是明细>主实体会涉及主实体变化清空明细此处优先使用明细的子级只能一个
for (String child : cascadingFieldsChild) {
String[] fs = child.split(SPLITER_RE);
Element fieldElement;
@ -212,8 +214,12 @@ public class DynamicMetadataFactory extends ConfigurationMetadataFactory {
}
JSONObject extraAttrs = JSON.parseObject(fieldElement.valueOf("@extra-attrs"));
extraAttrs.put("_cascadingFieldChild", fs[2] + SPLITER + fs[3]);
fieldElement.addAttribute("extra-attrs", extraAttrs.toJSONString());
if (extraAttrs.containsKey("_cascadingFieldChild") && extraAttrs.getString("_cascadingFieldChild").contains(".")) {
// 优先明细>主实体
} else {
extraAttrs.put("_cascadingFieldChild", fs[2] + SPLITER + fs[3]);
fieldElement.addAttribute("extra-attrs", extraAttrs.toJSONString());
}
}
if (log.isDebugEnabled()) XmlHelper.dump(rootElement);

View file

@ -21,6 +21,14 @@ public class EasyEntityConfigProps {
* 实体分类
*/
public static final String TAGS = "tags";
/**
* 关闭共同编辑
*/
public static final String NOT_COEDITING = "notCoEditing";
/**
* 明细不允许为空
*/
public static final String DETAILS_NOTEMPTY = "detailsNotEmpty";
/**
* 隐藏常用查询面板

View file

@ -70,6 +70,11 @@ public class EasyFieldConfigProps {
*/
public static final String IMAGE_UPLOADNUMBER = FILE_UPLOADNUMBER;
/**
* 图片获取方式仅H5
*/
public static final String IMAGE_CAPTURE = "imageCapture";
/**
* 自动编号规则
*/

View file

@ -118,7 +118,7 @@ public class Entity2Schema extends Field2Schema {
}
record.setString("nameField", nameFiled);
record = Application.getCommonsService().create(record);
recordedMetaId.add(record.getPrimary());
recordedMetaIds.add(record.getPrimary());
Entity tempEntity = new UnsafeEntity(entityName, physicalName, entityLabel, typeCode, nameFiled);
try {
@ -151,15 +151,17 @@ public class Entity2Schema extends Field2Schema {
createBuiltinField(tempEntity, EntityHelper.OwningUser, Language.L("所属用户"), DisplayType.REFERENCE, null, "User", null);
createBuiltinField(tempEntity, EntityHelper.OwningDept, Language.L("所属部门"), DisplayType.REFERENCE, null, "Department", null);
}
} catch (Throwable ex) {
log.error(null, ex);
Application.getCommonsService().delete(recordedMetaId.toArray(new ID[0]));
log.error("Error on create fields", ex);
Application.getCommonsService().delete(recordedMetaIds.toArray(new ID[0]));
throw new MetadataModificationException(Language.L("无法同步元数据到数据库 : %s", ex.getLocalizedMessage()));
}
boolean schemaReady = schema2Database(tempEntity);
if (!schemaReady) {
Application.getCommonsService().delete(recordedMetaId.toArray(new ID[0]));
Application.getCommonsService().delete(recordedMetaIds.toArray(new ID[0]));
throw new MetadataModificationException(Language.L("无法同步元数据到数据库"));
}

View file

@ -15,6 +15,7 @@ import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.dialect.Dialect;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.metadata.CascadeModel;
import cn.devezhao.persist4j.metadata.impl.AnyEntity;
import cn.devezhao.persist4j.metadata.impl.FieldImpl;
import cn.devezhao.persist4j.util.StringHelper;
import cn.devezhao.persist4j.util.support.Table;
@ -33,6 +34,7 @@ import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.setup.Installer;
import com.rebuild.utils.BlockList;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.RbAssert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.CharSet;
@ -57,7 +59,7 @@ public class Field2Schema extends SetUser {
// 小数位真实长度
private static final int DECIMAL_SCALE = 8;
final protected Set<ID> recordedMetaId = new HashSet<>();
final protected Set<ID> recordedMetaIds = new HashSet<>();
public Field2Schema() {
super();
@ -84,6 +86,20 @@ public class Field2Schema extends SetUser {
* @return
*/
public String createField(Entity entity, String fieldLabel, DisplayType type, String comments, String refEntity, JSON extConfig) {
return createField(entity, fieldLabel, null, type, comments, refEntity, extConfig);
}
/**
* @param entity
* @param fieldLabel
* @param fieldName
* @param type
* @param comments
* @param refEntity
* @param extConfig
* @return
*/
public String createField(Entity entity, String fieldLabel, String fieldName, DisplayType type, String comments, String refEntity, JSON extConfig) {
if (!License.isCommercial()) {
if (entity.getFields().length >= 50) {
throw new NeedRbvException("字段数量超出免费版限制");
@ -91,7 +107,7 @@ public class Field2Schema extends SetUser {
if (type == DisplayType.LOCATION || type == DisplayType.SIGN) {
Object[] limit = Application.createQueryNoFilter(
"select count(fieldId) from MetaField where displayType = ?")
"select count(fieldId) from MetaField where displayType = ?")
.setParameter(1, type.name())
.unique();
if (ObjectUtils.toInt(limit[0]) >= 1) {
@ -100,7 +116,8 @@ public class Field2Schema extends SetUser {
}
}
String fieldName = toPinyinName(fieldLabel);
if (StringUtils.length(fieldName) < 4) fieldName = toPinyinName(fieldLabel);
for (int i = 0; i < 6; i++) {
if (entity.containsField(fieldName) || MetadataHelper.isCommonsField(fieldName)) {
fieldName += RandomUtils.nextInt(0, 9);
@ -117,7 +134,7 @@ public class Field2Schema extends SetUser {
boolean schemaReady = schema2Database(entity, new Field[]{field}, uniqueKeyFields);
if (!schemaReady) {
Application.getCommonsService().delete(recordedMetaId.toArray(new ID[0]));
Application.getCommonsService().delete(recordedMetaIds.toArray(new ID[0]));
throw new MetadataModificationException(Language.L("无法同步元数据到数据库"));
}
@ -301,6 +318,8 @@ public class Field2Schema extends SetUser {
refEntity = "PickList";
} else if (dt == DisplayType.CLASSIFICATION) {
refEntity = "ClassificationData";
} else if (dt == DisplayType.ANYREFERENCE) {
refEntity = AnyEntity.FLAG;
}
if (extConfig != null) {
@ -311,7 +330,7 @@ public class Field2Schema extends SetUser {
// 忽略验证实体是否存在
// 在导入实体时需要需自行保证引用实体有效性否则系统会出错
if (!DynamicMetadataContextHolder.isSkipRefentityCheck(false)) {
if (!MetadataHelper.containsEntity(refEntity)) {
if (!(MetadataHelper.containsEntity(refEntity) || AnyEntity.FLAG.equals(refEntity))) {
throw new MetadataModificationException(Language.L("无效引用实体 : %s", refEntity));
}
}
@ -336,7 +355,7 @@ public class Field2Schema extends SetUser {
}
recordOfField = Application.getCommonsService().create(recordOfField);
recordedMetaId.add(recordOfField.getPrimary());
recordedMetaIds.add(recordOfField.getPrimary());
// 以下会改变一些属性因为并不想他们保存在元数据中
@ -369,16 +388,13 @@ public class Field2Schema extends SetUser {
*/
protected String toPinyinName(final String text) {
String identifier = text;
if (text.length() < 4) {
identifier = "rb" + text + RandomUtils.nextInt(1000, 9999);
}
// 全英文直接返回
if (identifier.matches("[a-zA-Z0-9]+")) {
if (identifier.length() >= 4 && identifier.matches("[a-zA-Z0-9]+")) {
if (!CharSet.ASCII_ALPHA.contains(identifier.charAt(0)) || BlockList.isBlock(identifier)) {
identifier = "rb" + identifier;
}
return identifier;
return CommonsUtils.maxstr(identifier, 40);
}
identifier = HanLP.convertToPinyinString(identifier, "", false);
@ -387,14 +403,13 @@ public class Field2Schema extends SetUser {
identifier = "rb" + RandomUtils.nextInt(1000, 9999);
}
char start = identifier.charAt(0);
if (!CharSet.ASCII_ALPHA.contains(start)) {
if (!CharSet.ASCII_ALPHA.contains(identifier.charAt(0))) {
identifier = "rb" + identifier;
}
identifier = identifier.toLowerCase();
if (identifier.length() > 42) {
identifier = identifier.substring(0, 42);
if (identifier.length() > 40) {
identifier = identifier.substring(0, 40);
} else if (identifier.length() < 4) {
identifier += RandomUtils.nextInt(1000, 9999);
}

View file

@ -41,7 +41,10 @@ public class ApprovalFields2Schema extends Field2Schema {
public boolean createFields(Entity approvalEntity) throws MetadataModificationException {
if (MetadataHelper.hasApprovalField(approvalEntity)) {
if (!approvalEntity.containsField(EntityHelper.ApprovalLastUser)) {
return createApporvalLastUser(approvalEntity);
return schema2DatabaseInternal(approvalEntity, buildApporvalLastUser(approvalEntity));
}
if (!approvalEntity.containsField(EntityHelper.ApprovalLastRemark)) {
return schema2DatabaseInternal(approvalEntity, buildApporvalLastRemark(approvalEntity));
}
return false;
}
@ -57,36 +60,35 @@ public class ApprovalFields2Schema extends Field2Schema {
DisplayType.STATE, true, false, false, true, true, null, null, null, null, ApprovalState.DRAFT.getState());
Field apporvalStepId = createUnsafeField(approvalEntity, EntityHelper.ApprovalStepNode, Language.L("审批步骤"),
DisplayType.TEXT, true, false, false, true, false, null, null, null, null, null);
Field apporvalLastUser = buildApporvalLastUser(approvalEntity);
Field apporvalLastRemark = buildApporvalLastRemark(approvalEntity);
boolean schemaReady = schema2Database(approvalEntity,
new Field[] { apporvalId, apporvalState, apporvalStepId, apporvalLastUser });
if (!schemaReady) {
Application.getCommonsService().delete(recordedMetaId.toArray(new ID[0]));
throw new MetadataModificationException(Language.L("无法同步元数据到数据库"));
}
MetadataHelper.getMetadataFactory().refresh();
schema2DatabaseInternal(approvalEntity, apporvalId, apporvalState, apporvalStepId, apporvalLastUser, apporvalLastRemark);
return true;
}
// v2.7 最后审批人
private boolean createApporvalLastUser(Entity approvalEntity) {
Field apporvalLastUser = buildApporvalLastUser(approvalEntity);
boolean schemaReady = schema2Database(approvalEntity, new Field[] { apporvalLastUser });
private Field buildApporvalLastUser(Entity approvalEntity) {
return createUnsafeField(approvalEntity, EntityHelper.ApprovalLastUser, Language.L("最后审批人"),
DisplayType.REFERENCE, true, false, false, true, true, null, "User", CascadeModel.Ignore, null, null);
}
// v3.1 最后审批批注
private Field buildApporvalLastRemark(Entity approvalEntity) {
return createUnsafeField(approvalEntity, EntityHelper.ApprovalLastRemark, Language.L("最后审批批注"),
DisplayType.NTEXT, true, false, false, true, true, null, null, null, null, null);
}
private boolean schema2DatabaseInternal(Entity approvalEntity, Field... fields) {
boolean schemaReady = schema2Database(approvalEntity, fields);
if (!schemaReady) {
Application.getCommonsService().delete(recordedMetaId.toArray(new ID[0]));
Application.getCommonsService().delete(recordedMetaIds.toArray(new ID[0]));
throw new MetadataModificationException(Language.L("无法同步元数据到数据库"));
}
MetadataHelper.getMetadataFactory().refresh();
return true;
}
private Field buildApporvalLastUser(Entity approvalEntity) {
return createUnsafeField(approvalEntity, EntityHelper.ApprovalLastUser, Language.L("最后审批人"),
DisplayType.REFERENCE, true, false, false, true, true, null, "User", CascadeModel.Ignore, null, null);
}
}

View file

@ -99,9 +99,9 @@ public class ApprovalProcessor extends SetUser {
recordOfMain.setID(EntityHelper.ApprovalId, this.approval);
recordOfMain.setInt(EntityHelper.ApprovalState, ApprovalState.PROCESSING.getState());
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNodes.getApprovalNode().getNodeId());
if (recordOfMain.getEntity().containsField(EntityHelper.ApprovalLastUser)) {
recordOfMain.setNull(EntityHelper.ApprovalLastUser);
}
// Clear on submit
ApprovalStepService.setApprovalLastX(recordOfMain, null, null);
Application.getBean(ApprovalStepService.class).txSubmit(recordOfMain, ccs, nextApprovers);
// 非主事物

View file

@ -172,8 +172,8 @@ public class ApprovalStepService extends InternalPersistService {
final String currentNode = (String) stepObject[2];
final ID approver = UserContextHolder.getUser();
String entityLabel = EasyMetaFactory.getLabel(MetadataHelper.getEntity(recordId.getEntityCode()));
String remark = stepRecord.getString("remark");
final String entityLabel = EasyMetaFactory.getLabel(MetadataHelper.getEntity(recordId.getEntityCode()));
final String remark = stepRecord.getString("remark");
// 抄送人
if (cc != null && !cc.isEmpty()) {
@ -186,17 +186,15 @@ public class ApprovalStepService extends InternalPersistService {
}
}
// 更新主记录
final Record recordOfMain = EntityHelper.forUpdate(recordId, approver, false);
setApprovalLastX(recordOfMain, approver, remark);
// 拒绝了直接返回
if (state == ApprovalState.REJECTED || state == ApprovalState.BACKED) {
// 拒绝了同一节点的其他审批人全部作废
cancelAliveSteps(recordId, approvalId, currentNode, stepRecordId, true);
// 更新主记录
Record recordOfMain = EntityHelper.forUpdate(recordId, UserService.SYSTEM_USER, false);
if (recordOfMain.getEntity().containsField(EntityHelper.ApprovalLastUser)) {
recordOfMain.setID(EntityHelper.ApprovalLastUser, approver);
}
// 退回
if (state == ApprovalState.BACKED) {
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNode);
@ -223,11 +221,11 @@ public class ApprovalStepService extends InternalPersistService {
String approvalMsg = Language.L("有一条 %s 记录请你审批", entityLabel);
// 或签一人通过其他作废
// 或签一人通过其他作废
if (FlowNode.SIGN_OR.equals(signMode)) {
cancelAliveSteps(recordId, approvalId, currentNode, stepRecordId, true);
}
// 会签检查是否都签了
// 会签检查是否都签了
else {
Object[][] currentNodeApprovers = Application.createQueryNoFilter(
"select state,isWaiting,stepId from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and isCanceled = 'F'")
@ -262,17 +260,15 @@ public class ApprovalStepService extends InternalPersistService {
// 最终状态审批通过
if (goNextNode && (nextApprovers == null || nextNode == null)) {
super.update(recordOfMain);
Application.getEntityService(recordId.getEntityCode()).approve(recordId, ApprovalState.APPROVED, approver);
return;
}
// 进入下一步
if (goNextNode) {
Record recordOfMain = EntityHelper.forUpdate(recordId, UserService.SYSTEM_USER, false);
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNode);
if (recordOfMain.getEntity().containsField(EntityHelper.ApprovalLastUser)) {
recordOfMain.setID(EntityHelper.ApprovalLastUser, approver);
}
super.update(recordOfMain);
}
@ -436,7 +432,7 @@ public class ApprovalStepService extends InternalPersistService {
public boolean txAutoApproved(ID recordId, ID useApprover, ID useApproval) {
final ApprovalState currentState = ApprovalHelper.getApprovalState(recordId);
if (currentState == ApprovalState.PROCESSING || currentState == ApprovalState.APPROVED) {
log.warn("Invalid state {} for auto approval : {}", currentState, recordId);
log.warn("Invalid state {} for auto approve : {}", currentState, recordId);
return false;
}
@ -458,6 +454,7 @@ public class ApprovalStepService extends InternalPersistService {
Record recordOfMain = EntityHelper.forUpdate(recordId, useApprover, false);
recordOfMain.setID(EntityHelper.ApprovalId, useApproval);
recordOfMain.setString(EntityHelper.ApprovalStepNode, FlowNode.NODE_AUTOAPPROVAL);
setApprovalLastX(recordOfMain, useApprover, Language.L("自动审批"));
super.update(recordOfMain);
Application.getEntityService(recordId.getEntityCode()).approve(recordId, ApprovalState.APPROVED, useApprover);
@ -491,9 +488,7 @@ public class ApprovalStepService extends InternalPersistService {
recordOfMain.setID(EntityHelper.ApprovalId, useApproval);
recordOfMain.setInt(EntityHelper.ApprovalState, ApprovalState.PROCESSING.getState());
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNodes.getApprovalNode().getNodeId());
if (recordOfMain.getEntity().containsField(EntityHelper.ApprovalLastUser)) {
recordOfMain.setNull(EntityHelper.ApprovalLastUser);
}
setApprovalLastX(recordOfMain, null, null);
Set<ID> ccList = nextNodes.getCcUsers(useApprover, recordId, null);
Set<ID> ccs4share = nextNodes.getCcUsers4Share(useApprover, recordId, null);
@ -622,4 +617,23 @@ public class ApprovalStepService extends InternalPersistService {
OperatingContext.create(approvalUser, BizzPermission.UPDATE, null, approvalRecord));
}
}
/**
* @param record
* @param approver
* @param remark
*/
protected static void setApprovalLastX(Record record, ID approver, String remark) {
Entity entity = record.getEntity();
if (entity.containsField(EntityHelper.ApprovalLastUser)) {
if (approver == null) record.setNull(EntityHelper.ApprovalLastUser);
else record.setID(EntityHelper.ApprovalLastUser, approver);
}
if (entity.containsField(EntityHelper.ApprovalLastRemark)) {
if (remark == null) record.setNull(EntityHelper.ApprovalLastRemark);
else record.setString(EntityHelper.ApprovalLastRemark, remark);
}
}
}

View file

@ -23,6 +23,7 @@ import com.rebuild.core.service.dashboard.charts.ChartsFactory;
import com.rebuild.core.service.dashboard.charts.builtin.BuiltinChart;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import java.util.Iterator;
@ -135,8 +136,8 @@ public class ChartManager implements ConfigManager {
public void richingCharts(JSONArray charts, ID user) {
for (Iterator<Object> iter = charts.iterator(); iter.hasNext(); ) {
JSONObject ch = (JSONObject) iter.next();
ID chartid = ID.valueOf(ch.getString("chart"));
ConfigBean e = getChart(chartid);
ID chartId = ID.valueOf(ch.getString("chart"));
ConfigBean e = getChart(chartId);
if (e == null) {
iter.remove();
continue;
@ -145,6 +146,11 @@ public class ChartManager implements ConfigManager {
ch.put("title", e.getString("title"));
ch.put("type", e.getString("type"));
try {
String c = ((JSONObject) e.getJSON("config")).getJSONObject("option").getString("useColor");
if (StringUtils.isNotBlank(c)) ch.put("color", c);
} catch (Exception ignore) {}
if (user != null) {
ID createdBy = e.getID("createdBy");
ch.put("isManageable", UserHelper.isSelf(user, createdBy));

View file

@ -113,12 +113,13 @@ public interface EntityService extends ServiceSpec {
List<Record> getAndCheckRepeated(Record checkRecord, int limit);
/**
* 审批此处只是为了方便子类复写
* 审批
*
* @param record
* @param state 只接受通过或撤销
* @param approvalUser 审批人
* @see com.rebuild.core.service.approval.ApprovalStepService
* @see com.rebuild.core.service.approval.ApprovalProcessor
*/
void approve(ID record, ApprovalState state, ID approvalUser);
}

View file

@ -696,16 +696,11 @@ public class GeneralEntityService extends ObservableService implements EntitySer
if (approvalUser == null) {
approvalUser = UserService.SYSTEM_USER;
log.warn("Use system user for approval");
log.warn("Use system user for approve");
}
Record approvalRecord = EntityHelper.forUpdate(recordId, approvalUser, false);
approvalRecord.setInt(EntityHelper.ApprovalState, state.getState());
if (state == ApprovalState.APPROVED
&& MetadataHelper.getEntity(recordId.getEntityCode()).containsField(EntityHelper.ApprovalLastUser)) {
approvalRecord.setID(EntityHelper.ApprovalLastUser, approvalUser);
}
delegateService.update(approvalRecord);
// 触发器

View file

@ -12,6 +12,7 @@ import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.service.NoRecordFoundException;
import com.rebuild.core.service.query.QueryHelper;
import com.rebuild.core.support.general.FieldValueHelper;
import org.apache.commons.lang.StringUtils;
@ -23,6 +24,7 @@ import java.util.*;
* @author devezhao zhaofang123@gmail.com
* @since 2019/04/25
*/
@SuppressWarnings("unchecked")
public class RecentlyUsedHelper {
// 最大缓存数量
@ -37,7 +39,20 @@ public class RecentlyUsedHelper {
* @return
*/
public static ID[] gets(ID user, String entity, String type) {
return gets(user, entity, type, 10);
return gets(user, entity, type, 10, null);
}
/**
* 获取最近使用最多10个
*
* @param user
* @param entity
* @param type
* @param checkFilter
* @return
*/
public static ID[] gets(ID user, String entity, String type, String checkFilter) {
return gets(user, entity, type, 10, checkFilter);
}
/**
@ -47,29 +62,34 @@ public class RecentlyUsedHelper {
* @param entity
* @param type
* @param limit
* @param checkFilter
* @return
*/
public static ID[] gets(ID user, String entity, String type, int limit) {
final String key = formatKey(user, entity, type);
@SuppressWarnings("unchecked")
LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(key);
if (cached == null || cached.isEmpty()) {
return ID.EMPTY_ID_ARRAY;
}
protected static ID[] gets(ID user, String entity, String type, int limit, String checkFilter) {
final String ckey = formatKey(user, entity, type);
LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(ckey);
if (cached == null || cached.isEmpty()) return ID.EMPTY_ID_ARRAY;
Set<ID> missed = new HashSet<>();
List<ID> data = new ArrayList<>();
for (int i = 0; i < limit && i < cached.size(); i++) {
final ID raw = cached.get(i);
if (!(raw.getEntityCode() == EntityHelper.ClassificationData
|| Application.getPrivilegesManager().allowRead(user, raw))) {
continue;
boolean allowRead = raw.getEntityCode() == EntityHelper.ClassificationData
|| Application.getPrivilegesManager().allowRead(user, raw);
if (!allowRead) continue;
// 是否符合条件
if (checkFilter != null) {
if (!QueryHelper.isMatchFilter(raw, checkFilter)) {
continue;
}
}
try {
String label = FieldValueHelper.getLabel(raw);
ID clone = ID.valueOf(raw.toLiteral());
clone.setLabel(label);
clone.setLabel(FieldValueHelper.getLabel(raw));
data.add(clone);
} catch (NoRecordFoundException ex) {
missed.add(raw);
@ -78,13 +98,14 @@ public class RecentlyUsedHelper {
if (!missed.isEmpty()) {
cached.removeAll(missed);
Application.getCommonsCache().putx(key, cached);
Application.getCommonsCache().putx(ckey, cached);
}
return data.toArray(new ID[0]);
}
/**
* 添加搜索缓存
* 添加最近使用
*
* @param user
* @param id
@ -92,7 +113,6 @@ public class RecentlyUsedHelper {
*/
public static void add(ID user, ID id, String type) {
final String key = formatKey(user, MetadataHelper.getEntityName(id), type);
@SuppressWarnings("unchecked")
LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(key);
if (cached == null) {
cached = new LinkedList<>();
@ -121,6 +141,6 @@ public class RecentlyUsedHelper {
}
private static String formatKey(ID user, String entity, String type) {
return String.format("RS.%s-%s-%s", user, entity, StringUtils.defaultIfBlank(type, StringUtils.EMPTY));
return String.format("RS31.%s-%s-%s", user, entity, StringUtils.defaultIfBlank(type, StringUtils.EMPTY));
}
}

View file

@ -111,7 +111,7 @@ public class RecordTransfomer extends SetUser {
sourceDetailEntity = sourceEntity.getDetailEntity();
Field sourceRefField;
// v2.10 1 > 2+明细
// v2.10 1条记录 > 2条记录+明细
if (sourceDetailEntity == null) {
sourceDetailEntity = sourceEntity;
sourceRefField = sourceDetailEntity.getPrimaryField();
@ -120,7 +120,7 @@ public class RecordTransfomer extends SetUser {
}
String sql = String.format(
"select %s from %s where %s = '%s'",
"select %s from %s where %s = '%s' order by autoId asc",
sourceDetailEntity.getPrimaryField().getName(), sourceDetailEntity.getName(), sourceRefField.getName(), sourceRecordId);
sourceDetails = Application.createQueryNoFilter(sql).array();
}

View file

@ -15,6 +15,7 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.dialect.Type;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.query.compiler.QueryCompiler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@ -26,7 +27,6 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.general.ContentWithFieldVars;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
@ -69,6 +69,9 @@ public class AdvFilterParser extends SetUser {
// 快速查询
private static final String MODE_QUICK = "QUICK";
// 名称字段 &
private static final String NAME_FIELD_PREFIX = "" + QueryCompiler.NAME_FIELD_PREFIX;
final private JSONObject filterExpr;
final private Entity rootEntity;
// v3.1 条件值使用记录作为变量
@ -107,9 +110,7 @@ public class AdvFilterParser extends SetUser {
* @return
*/
public String toSqlWhere() {
if (filterExpr == null || filterExpr.isEmpty()) {
return null;
}
if (filterExpr == null || filterExpr.isEmpty()) return null;
this.includeFields = new HashSet<>();
@ -136,7 +137,7 @@ public class AdvFilterParser extends SetUser {
index = incrIndex++;
}
String itemSql = parseItem(item, values);
String itemSql = parseItem(item, values, rootEntity);
if (itemSql != null) {
indexItemSqls.put(index, itemSql.trim());
this.includeFields.add(item.getString("field"));
@ -207,35 +208,36 @@ public class AdvFilterParser extends SetUser {
*
* @param item
* @param values
* @param specRootEntity
* @return
*/
private String parseItem(JSONObject item, JSONObject values) {
private String parseItem(JSONObject item, JSONObject values, Entity specRootEntity) {
String field = item.getString("field");
if (field.startsWith("&amp;")) field = field.replace("&amp;", "&"); // fix: _$unthy
if (field.startsWith("&amp;")) field = field.replace("&amp;", NAME_FIELD_PREFIX); // fix: _$unthy
boolean hasNameFlag = field.startsWith("&");
if (hasNameFlag) {
field = field.substring(1);
}
final boolean hasNameFlag = field.startsWith(NAME_FIELD_PREFIX);
if (hasNameFlag) field = field.substring(1);
Field fieldMeta = VF_ACU.equals(field) ? rootEntity.getField(EntityHelper.ApprovalLastUser)
: MetadataHelper.getLastJoinField(rootEntity, field);
Field fieldMeta = VF_ACU.equals(field)
? specRootEntity.getField(EntityHelper.ApprovalLastUser)
: MetadataHelper.getLastJoinField(specRootEntity, field);
if (fieldMeta == null) {
log.warn("Invalid field : {} in {}", field, rootEntity.getName());
log.warn("Invalid field : {} in {}", field, specRootEntity.getName());
return null;
}
DisplayType dt = EasyMetaFactory.getDisplayType(fieldMeta);
if (dt == DisplayType.CLASSIFICATION
|| (dt == DisplayType.PICKLIST && hasNameFlag) /* 快速查询 */) {
field = "&" + field;
field = NAME_FIELD_PREFIX + field;
} else if (hasNameFlag) {
if (!(dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE)) {
log.warn("Non reference-field '{}' in '{}'", field, rootEntity.getName());
log.warn("Non reference-field : {} in {}", field, specRootEntity.getName());
return null;
}
// 为名称字段
// 为名称字段
if (dt == DisplayType.REFERENCE) {
fieldMeta = fieldMeta.getReferenceEntity().getNameField();
dt = EasyMetaFactory.getDisplayType(fieldMeta);
@ -247,20 +249,31 @@ public class AdvFilterParser extends SetUser {
String value = useValueOfVarRecord(item.getString("value"));
String valueEnd = null;
// FIXME N2N 特殊处理仅支持 LK NLK EQ NEQ
// exists ( in (...) )
if (hasNameFlag && dt == DisplayType.N2NREFERENCE) {
Entity refEntity = fieldMeta.getReferenceEntity();
String inWhere = String.format("select %s from %s where %s %s %s",
refEntity.getPrimaryField().getName(),
refEntity.getName(),
refEntity.getNameField().getName(),
ParseHelper.convetOperation(op),
quoteValue('%' + value + '%', FieldType.STRING));
if (dt == DisplayType.N2NREFERENCE) {
String inWhere = null;
if (hasNameFlag) {
Entity refEntity = fieldMeta.getReferenceEntity();
Field nameField = refEntity.getNameField();
return String.format(
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId in (%s))",
rootEntity.getPrimaryField().getName(), fieldMeta.getName(), inWhere);
JSONObject innerItem = (JSONObject) JSONUtils.clone(item);
innerItem.put("field", nameField.getName());
String innerWhereSql = parseItem(innerItem, null, refEntity);
inWhere = String.format("select %s from %s where %s",
refEntity.getPrimaryField().getName(), refEntity.getName(), innerWhereSql);
}
// 查询 ID仅支持 IN
else if (ParseHelper.IN.equals(op) && ID.isId(value)) {
inWhere = quoteValue(value, FieldType.STRING);
}
if (inWhere != null) {
return String.format(
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId in (%s))",
specRootEntity.getPrimaryField().getName(), fieldMeta.getName(), inWhere);
}
}
// 根据字段类型转换 `op`
@ -474,7 +487,7 @@ public class AdvFilterParser extends SetUser {
if (VF_ACU.equals(field)) {
return String.format(
"(exists (select recordId from RobotApprovalStep where ^%s = recordId and state = 1 and %s) and approvalState = 2)",
rootEntity.getPrimaryField().getName(), sb.toString().replace(VF_ACU, "approver"));
specRootEntity.getPrimaryField().getName(), sb.toString().replace(VF_ACU, "approver"));
} else {
return sb.toString();
}
@ -649,17 +662,17 @@ public class AdvFilterParser extends SetUser {
return null;
}
// {{xxx}}
private static final String VAR_PATT = "\\{" + ContentWithFieldVars.PATT_VAR.pattern() + "}";
// {@FIELD}
private static final String VAR_PATT = "\\{@([\\w.]+)}";
private String useValueOfVarRecord(String value) {
if (varRecord == null || StringUtils.isBlank(value)) return value;
if (!value.matches(VAR_PATT)) return value;
String fieldName = value.substring(2, value.length() - 2);
String fieldName = value.substring(2, value.length() - 1);
Field field = MetadataHelper.getLastJoinField(rootEntity, fieldName);
if (field == null) {
log.warn("Invalid field : {} in {}", value, rootEntity.getName());
log.warn("Invalid var-field : {} in {}", value, rootEntity.getName());
return value;
}

View file

@ -264,11 +264,17 @@ public class ParseHelper {
}
}
// QuickCode
// if: QuickCode
if (entity.containsField(EntityHelper.QuickCode)) {
usesFields.add(EntityHelper.QuickCode);
}
// if: User
if (entity.getEntityCode() == EntityHelper.User) {
usesFields.add("loginName");
usesFields.add("email");
}
if (usesFields.isEmpty()) {
log.warn("No fields of search found : " + usesFields);
}

View file

@ -39,12 +39,11 @@ public class QueryHelper {
}
/**
* 指定记录是否符合过滤条件
*
* @param recordId
* @param advFilter
* @param useVarRecord
* @return
* @see #isMatchFilter(ID, String)
*/
public static boolean isMatchAdvFilter(ID recordId, JSONObject advFilter, boolean useVarRecord) {
if (!ParseHelper.validAdvFilter(advFilter)) return true;
@ -52,15 +51,26 @@ public class QueryHelper {
String filterSql = useVarRecord ? new AdvFilterParser(advFilter, recordId).toSqlWhere()
: new AdvFilterParser(advFilter).toSqlWhere();
if (filterSql != null) {
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
String sql = MessageFormat.format(
"select {0} from {1} where {0} = ? and {2}",
entity.getPrimaryField().getName(), entity.getName(), filterSql);
Object[] m = Application.createQueryNoFilter(sql).setParameter(1, recordId).unique();
return m != null;
}
return true;
return isMatchFilter(recordId, filterSql);
}
/**
* 指定记录是否符合过滤条件
*
* @param recordId
* @param filterSql
* @return
*/
public static boolean isMatchFilter(ID recordId, String filterSql) {
if (StringUtils.isBlank(filterSql)) return true;
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
String sql = MessageFormat.format(
"select {0} from {1} where {0} = ? and {2}",
entity.getPrimaryField().getName(), entity.getName(), filterSql);
Object[] m = Application.createQueryNoFilter(sql).setParameter(1, recordId).unique();
return m != null;
}
/**
@ -119,8 +129,9 @@ public class QueryHelper {
*/
public static List<ID> detailIdsNoFilter(ID mainId) {
Entity detailEntity = MetadataHelper.getEntity(mainId.getEntityCode()).getDetailEntity();
String sql = String.format("select %s from %s where %s = ?",
detailEntity.getPrimaryField().getName(), detailEntity.getName(),
String sql = String.format("select %s from %s where %s = ? order by autoId asc",
detailEntity.getPrimaryField().getName(),
detailEntity.getName(),
MetadataHelper.getDetailToMainField(detailEntity).getName());
Query query = Application.createQueryNoFilter(sql).setParameter(1, mainId);

View file

@ -131,33 +131,40 @@ public class RobotTriggerObserver extends OperatingObserver {
if (o != null) log.warn("Force clean last trigger-chain : {}", o);
} else {
// 是否自己触发自己避免无限执行
boolean isOriginRecord = primaryId.equals(triggerSource.getOriginRecord());
String lastKey = triggerSource.getLastSourceKey();
// // FIXME 20220811 此处的判断可能不需要因为有 `trigger-chain`
//
// // 是否自己触发自己避免无限执行
// boolean isOriginRecord = primaryId.equals(triggerSource.getOriginRecord());
//
// String lastKey = triggerSource.getLastSourceKey();
// triggerSource.addNext(context, when);
// String currentKey = triggerSource.getLastSourceKey();
//
// if (isOriginRecord && lastKey.equals(currentKey)) {
// if (!triggerSource.isSkipOnce()) {
// log.warn("Self trigger, ignore : {} < {}", currentKey, lastKey);
// return;
// }
// }
// v3.1-b5
triggerSource.addNext(context, when);
String currentKey = triggerSource.getLastSourceKey();
if (isOriginRecord && lastKey.equals(currentKey)) {
if (!triggerSource.isSkipOnce()) {
log.warn("Self trigger, ignore : {} < {}", currentKey, lastKey);
return;
}
}
// FIXME 20220811 此处的判断可能不需要因为有 `trigger-chain`
}
final String sourceId = triggerSource.getSourceId();
try {
for (TriggerAction action : beExecuted) {
String w = String.format("Trigger.%s [ %s ] executing on record (%s) : %s",
sourceId, action.getType(), when.name(), primaryId);
final int t = triggerSource.incrTriggerTimes();
final String w = String.format("Trigger.%s.%d [ %s ] executing on record (%s) : %s",
sourceId, t, action, when, primaryId);
log.info(w);
try {
Object res = action.execute(context);
System.out.println("[dev] " + w + " > " + (res == null ? "N" : res));
boolean hasAffected = res instanceof TriggerResult && ((TriggerResult) res).hasAffected();
System.out.println("[dev] " + w + " > " + (res == null ? "N" : res) + (hasAffected ? " < REALLY AFFECTED" : ""));
if (res instanceof TriggerResult) {
if (originTriggerSource) {

View file

@ -37,10 +37,14 @@ public class TriggerResult implements JSONAware {
this.affected = affected;
}
public void setChain(TriggerSource chain) {
protected void setChain(TriggerSource chain) {
this.chain = chain;
}
public boolean hasAffected() {
return affected != null && !affected.isEmpty();
}
@Override
public String toJSONString() {
JSONObject res = JSONUtils.toJSONObject("level", level);

View file

@ -32,6 +32,9 @@ public class TriggerSource {
private boolean skipOnce = false;
// 触发次数
private int triggerTimes = 0;
protected TriggerSource(OperatingContext origin, TriggerWhen originAction) {
this.id = TSNO.incrementAndGet() + "-";
addNext(origin, originAction);
@ -76,6 +79,10 @@ public class TriggerSource {
return skipOnceHold;
}
public int incrTriggerTimes() {
return ++triggerTimes;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();

View file

@ -9,8 +9,8 @@ package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.hutool.core.date.DateBetween;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.AviatorLong;
import com.googlecode.aviator.runtime.type.AviatorNil;
@ -84,12 +84,13 @@ public class DateDiffFunction extends AbstractFunction {
} else {
long res = 0;
DateBetween between = DateBetween.create($date1, $date2, Boolean.FALSE);
if (AviatorDate.DU_YEAR.equalsIgnoreCase($du)) res = DateUtil.betweenYear($date1, $date2, true);
else if (AviatorDate.DU_MONTH.equalsIgnoreCase($du)) res = DateUtil.betweenMonth($date1, $date2, true);
else if (AviatorDate.DU_DAY.equalsIgnoreCase($du)) res = DateUtil.betweenDay($date1, $date2, true);
else if (AviatorDate.DU_HOUR.equalsIgnoreCase($du)) res = DateUtil.between($date1, $date2, DateUnit.HOUR, false);
else if (AviatorDate.DU_MINUTE.equalsIgnoreCase($du)) res = DateUtil.between($date1, $date2, DateUnit.MINUTE, false);
if (AviatorDate.DU_YEAR.equalsIgnoreCase($du)) res = between.betweenYear(Boolean.TRUE);
else if (AviatorDate.DU_MONTH.equalsIgnoreCase($du)) res = between.betweenMonth(Boolean.TRUE);
else if (AviatorDate.DU_DAY.equalsIgnoreCase($du)) res = between.between(DateUnit.DAY);
else if (AviatorDate.DU_HOUR.equalsIgnoreCase($du)) res = between.between(DateUnit.HOUR);
else if (AviatorDate.DU_MINUTE.equalsIgnoreCase($du)) res = between.between(DateUnit.MINUTE);
return AviatorLong.valueOf(res);
}

View file

@ -151,6 +151,11 @@ public class FieldAggregation extends TriggerAction {
dataFilterSql = new AdvFilterParser(dataFilter).toSqlWhere();
}
String filterSql = followSourceWhere;
if (dataFilterSql != null) {
filterSql = String.format("( %s ) and ( %s )", followSourceWhere, dataFilterSql);
}
// 构建目标记录数据
Record targetRecord = EntityHelper.forUpdate(targetRecordId, UserService.SYSTEM_USER, false);
@ -162,11 +167,6 @@ public class FieldAggregation extends TriggerAction {
continue;
}
String filterSql = followSourceWhere;
if (dataFilterSql != null) {
filterSql = String.format("( %s ) and ( %s )", followSourceWhere, dataFilterSql);
}
Object evalValue = new AggregationEvaluator(item, sourceEntity, filterSql).eval();
if (evalValue == null) continue;
@ -227,10 +227,11 @@ public class FieldAggregation extends TriggerAction {
String fillbackField = ((JSONObject) actionContext.getActionContent()).getString("fillbackField");
if (fillbackField != null && MetadataHelper.checkAndWarnField(sourceEntity, fillbackField)) {
String sql = String.format("select %s from %s where %s",
sourceEntity.getPrimaryField().getName(), sourceEntity.getName(), dataFilterSql);
sourceEntity.getPrimaryField().getName(), sourceEntity.getName(), filterSql);
Object[][] fillbacks = Application.createQueryNoFilter(sql).array();
for (Object[] fb : fillbacks) {
Record fbRecord = EntityHelper.forUpdate((ID) fb[0], UserService.SYSTEM_USER, false);
for (Object[] to : fillbacks) {
Record fbRecord = EntityHelper.forUpdate((ID) to[0], UserService.SYSTEM_USER, false);
fbRecord.setID(fillbackField, targetRecordId);
// FIXME 回填仅更新无业务规则

View file

@ -102,6 +102,8 @@ public class FieldWriteback extends FieldAggregation {
final boolean forceUpdate = ((JSONObject) actionContext.getActionContent()).getBooleanValue("forceUpdate");
List<ID> affected = new ArrayList<>();
boolean targetSame = false;
for (ID targetRecordId : targetRecordIds) {
if (operatingContext.getAction() == BizzPermission.DELETE
&& targetRecordId.equals(operatingContext.getAnyRecord().getPrimary())) {
@ -116,6 +118,7 @@ public class FieldWriteback extends FieldAggregation {
// 相等则不更新
if (isCurrentSame(targetRecord)) {
log.info("Ignore execution because the record are same : {}", targetRecordId);
targetSame = true;
continue;
}
@ -145,7 +148,8 @@ public class FieldWriteback extends FieldAggregation {
}
}
return TriggerResult.success(affected);
if (targetSame && affected.isEmpty()) return TriggerResult.targetSame();
else return TriggerResult.success(affected);
}
@Override

View file

@ -43,9 +43,9 @@ public class CommonsLock {
* @return
*/
public static Object[] getLockedUserFormat(ID source) {
ID l = getLockedUser(source);
if (l == null) return null;
return new Object[] { l, UserHelper.getName(l) };
ID u = getLockedUser(source);
if (u == null) return null;
return new Object[] { u, UserHelper.getName(u) };
}
/**
@ -78,16 +78,15 @@ public class CommonsLock {
if (lockedUser.equals(unlockUser)) {
Object[] o = Application.createQueryNoFilter(
"select lockId from CommonsLock where source = ?")
"select lockId from CommonsLock where source = ?")
.setParameter(1, source)
.unique();
if (o != null) {
Application.getCommonsService().delete((ID) o[0]);
}
return true;
} else {
return false;
}
return false;
}
}

View file

@ -8,11 +8,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.support;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ThreadPool;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.support.task.TaskExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
@ -67,12 +67,6 @@ public class CommonsLog {
clog.setDate("logTime", CalendarUtils.now());
if (content != null) clog.setString("logContent", content);
ThreadPool.exec(() -> {
try {
Application.getCommonsService().create(clog);
} catch (Throwable ex) {
log.error("Cannot create common-log: {}", clog, ex);
}
});
TaskExecutors.queue(() -> Application.getCommonsService().create(clog, false));
}
}

View file

@ -120,7 +120,7 @@ public final class License {
private static JSONObject siteApi(String api, int t, String domain) {
if (t > 0) {
JSONObject c = MCACHED.get(api, t);
if (c != null) return c;
if (c != null) return c.clone();
}
Map<String, String> hs = new HashMap<>();
@ -140,7 +140,8 @@ public final class License {
} else {
MCACHED.put(api, o);
}
return o;
return o.clone();
} else {
log.error("Bad result format : {}", result);
}

View file

@ -260,6 +260,9 @@ public class RebuildConfiguration extends KVStorage {
* @return
*/
public static void set(ConfigurationItem name, Object value) {
if (ConfigurationItem.DataDirectory == name || ConfigurationItem.RedisDatabase == name) {
throw new SecurityException("Attack configuration detected : " + name + "=" + value);
}
setValue(name.name(), value);
}
}

View file

@ -23,6 +23,8 @@ import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.ZeroEntry;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.StringUtils;
@ -80,6 +82,9 @@ public class DataListWrapper {
if (user != null) {
this.useDesensitized = !Application.getPrivilegesManager().allow(user, ZeroEntry.AllowNoDesensitized);
if (!this.useDesensitized) {
this.useDesensitized = UserHelper.isAdmin(user) && RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced);
}
}
}

View file

@ -29,7 +29,9 @@ import com.rebuild.core.privileges.bizz.ZeroEntry;
import com.rebuild.core.service.NoRecordFoundException;
import com.rebuild.core.service.approval.ApprovalState;
import com.rebuild.core.service.approval.ApprovalStepService;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.DataDesensitized;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
@ -94,8 +96,10 @@ public class FieldValueHelper {
* @see EasyField#wrapValue(Object)
*/
public static Object wrapFieldValue(Object value, EasyField field) {
final DisplayType dt = field.getDisplayType();
if (value != null && !field.isQueryable() &&
(field.getDisplayType() == DisplayType.TEXT || field.getDisplayType() == DisplayType.NTEXT)) {
(dt == DisplayType.TEXT || dt == DisplayType.NTEXT)) {
return DataDesensitized.SECURE_TEXT;
}
@ -111,7 +115,7 @@ public class FieldValueHelper {
}
// ID 数组表示记录主键
if (field.getDisplayType() == DisplayType.N2NREFERENCE && value instanceof ID) {
if (dt == DisplayType.N2NREFERENCE && value instanceof ID) {
value = N2NReferenceSupport.items(field.getRawMeta(), (ID) value);
}
@ -275,8 +279,14 @@ public class FieldValueHelper {
return false;
}
return field.isDesensitized()
&& !Application.getPrivilegesManager().allow(user, ZeroEntry.AllowNoDesensitized);
if (field.isDesensitized()) {
if (UserHelper.isAdmin(user) && RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced)) {
return true;
} else {
return !Application.getPrivilegesManager().allow(user, ZeroEntry.AllowNoDesensitized);
}
}
return false;
}
/**

View file

@ -46,15 +46,20 @@ import java.util.*;
public class ProtocolFilterParser {
// 协议
// via:xxx:[field]
public static final String P_VIA = "via";
// ref:xxx:[id]
public static final String P_REF = "ref";
// category:entity:value
public static final String P_CATEGORY = "category";
// related:field:id
public static final String P_RELATED = "related";
final private String protocolExpr;
/**
* @param protocolExpr via:xxx:[field] ref:xxx:[id] category:entity:value related:field:id
* @param protocolExpr
*/
public ProtocolFilterParser(String protocolExpr) {
this.protocolExpr = protocolExpr;
@ -142,9 +147,14 @@ public class ProtocolFilterParser {
List<String> sqls = new ArrayList<>();
JSONObject advFilter = getFieldDataFilter(field);
if (advFilter != null) sqls.add(new AdvFilterParser(advFilter).toSqlWhere());
// 字段附加过滤条件
JSONObject fieldFilter = getFieldDataFilter(field);
if (ParseHelper.validAdvFilter(fieldFilter)) {
String s = new AdvFilterParser(fieldFilter).toSqlWhere();
if (StringUtils.isNotBlank(s)) sqls.add(s);
}
// 父级级联字段
if (hasFieldCascadingField(field) && ID.isId(cascadingValue)) {
String cascadingFieldParent = field.getExtraAttrs().getString("_cascadingFieldParent");
String cascadingFieldChild = field.getExtraAttrs().getString("_cascadingFieldChild");
@ -153,15 +163,16 @@ public class ProtocolFilterParser {
String[] fs = cascadingFieldParent.split(MetadataHelper.SPLITER_RE);
sqls.add(String.format("%s = '%s'", fs[1], cascadingValue));
}
if (StringUtils.isNotBlank(cascadingFieldChild)) {
String[] fs = cascadingFieldChild.split(MetadataHelper.SPLITER_RE);
Entity refEntity = entity.getField(fs[0]).getReferenceEntity();
String sql = String.format("exists (select %s from %s where ^%s = %s and %s = '%s')",
String s = String.format("exists (select %s from %s where ^%s = %s and %s = '%s')",
fs[1], refEntity.getName(),
field.getReferenceEntity().getPrimaryField().getName(), fs[1],
refEntity.getPrimaryField().getName(), cascadingValue);
sqls.add(sql);
sqls.add(s);
}
}
@ -174,7 +185,7 @@ public class ProtocolFilterParser {
* @param value
* @return
* @see #P_CATEGORY
* @see AdvFilterParser#parseItem(JSONObject, JSONObject)
* @see DataListCategory
*/
protected String parseCategory(String entity, String value) {
Entity rootEntity = MetadataHelper.getEntity(entity);

View file

@ -184,21 +184,21 @@ public class QueryParser {
wheres.add(defaultFilter);
}
// appends ProtocolFilter
// append: ProtocolFilter
String protocolFilter = queryExpr.getString("protocolFilter");
if (StringUtils.isNotBlank(protocolFilter)) {
String where = new ProtocolFilterParser(protocolFilter).toSqlWhere();
if (StringUtils.isNotBlank(where)) wheres.add(where);
}
// appends AdvFilter
// append: AdvFilter
String advFilter = queryExpr.getString("advFilter");
if (ID.isId(advFilter)) {
String where = parseAdvFilter(ID.valueOf(advFilter));
if (StringUtils.isNotBlank(where)) wheres.add(where);
}
// appends QuickQuery
// append: QuickQuery
JSONObject quickFilter = queryExpr.getJSONObject("filter");
if (quickFilter != null) {
String where = new AdvFilterParser(quickFilter, entity).toSqlWhere();

View file

@ -9,6 +9,7 @@ package com.rebuild.core.support.integration;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.CodecUtils;
import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.qiniu.common.QiniuException;
@ -252,29 +253,21 @@ public class QiniuCloud {
* @see #parseFileName(String)
*/
public static String formatFileKey(String fileName, boolean keepName) {
if (!keepName) {
String[] fileNameSplit = fileName.split("\\.");
fileName = CommonsUtils.randomHex(true);
if (fileNameSplit.length > 1 && StringUtils.isNotBlank(fileNameSplit[fileNameSplit.length - 1])) {
fileName += "." + fileNameSplit[fileNameSplit.length - 1];
}
} else {
if (keepName) {
while (fileName.contains("__")) {
fileName = fileName.replace("__", "_");
}
if (fileName.contains("+")) {
fileName = fileName.replace("+", "");
}
if (fileName.contains("#")) {
fileName = fileName.replace("#", "");
}
if (fileName.contains("?")) {
fileName = fileName.replace("?", "");
}
// 去除特殊符号
fileName = fileName.replaceAll("[&+#?%=/\\s]", "");
if (fileName.length() > 41) {
fileName = fileName.substring(0, 20) + "-" + fileName.substring(fileName.length() - 20);
}
} else {
String fileExt = FileUtil.getSuffix(fileName);
fileName = CommonsUtils.randomHex(true);
if (StringUtils.isNotBlank(fileExt)) fileName += "." + fileExt;
}
String datetime = CalendarUtils.getDateFormat("yyyyMMddHHmmssSSS").format(CalendarUtils.now());

View file

@ -60,7 +60,7 @@ public class SMSender {
try {
sendMail(to, subject, content);
} catch (Exception ex) {
log.error("Mail failed to send : " + to + " < " + subject, ex);
log.error("Mail failed to send : {} < {}", to, subject, ex);
}
});
}
@ -97,11 +97,18 @@ public class SMSender {
Objects.requireNonNull(mailbody.selectFirst(".rb-title")).text(subject);
Objects.requireNonNull(mailbody.selectFirst(".rb-content")).html(content);
// 处理变量
String htmlContent = mailbody.html();
// 处理公共变量
htmlContent = htmlContent.replace("%TO%", to);
htmlContent = htmlContent.replace("%TIME%", CalendarUtils.getUTCDateTimeFormat().format(CalendarUtils.now()));
htmlContent = htmlContent.replace("%APPNAME%", RebuildConfiguration.get(ConfigurationItem.AppName));
htmlContent = htmlContent.replace("%APPURL%", RebuildConfiguration.getHomeUrl());
htmlContent = htmlContent.replace("%APPLOGO%", RebuildConfiguration.getHomeUrl("commons/theme/use-logo"));
if (License.isCommercial()) {
htmlContent = htmlContent.replace("%APPNAME%", RebuildConfiguration.get(ConfigurationItem.AppName));
} else {
htmlContent = htmlContent.replace("%APPNAME%", "REBUILD");
}
String pageFooter = RebuildConfiguration.get(ConfigurationItem.PageFooter);
if (StringUtils.isNotBlank(pageFooter)) {
@ -124,7 +131,7 @@ public class SMSender {
return emailId;
} catch (EmailException ex) {
log.error("SMTP failed to send : " + to + " > " + subject, ex);
log.error("SMTP failed to send : {} > {}", to, subject, ex);
return null;
}
}
@ -150,7 +157,7 @@ public class SMSender {
String r = OkHttpUtils.post("https://api-v4.mysubmail.com/mail/send.json", params);
rJson = JSON.parseObject(r);
} catch (Exception ex) {
log.error("Submail failed to send : " + to + " > " + subject, ex);
log.error("Submail failed to send : {} > {}", to, subject, ex);
return null;
}
@ -161,7 +168,7 @@ public class SMSender {
return sendId;
} else {
log.error("Mail failed to send : " + to + " > " + subject + "\nError : " + rJson);
log.error("Mail failed to send : {} > {}\nError : {}", to, subject, rJson);
createLog(to, logContent, TYPE_EMAIL, null, rJson.getString("msg"));
return null;
}
@ -203,21 +210,13 @@ public class SMSender {
* @return
*/
protected static Element getMailTemplate() {
if (MT_CACHE != null && !Application.devMode()) return MT_CACHE.clone();
if (MT_CACHE != null) return MT_CACHE.clone();
String content = CommonsUtils.getStringOfRes("i18n/email.zh_CN.html");
Assert.notNull(content, "Cannot load template of email");
// 生硬替换
if (Application.isReady() && License.getCommercialType() > 10) {
content = content.replace("REBUILD", RebuildConfiguration.get(ConfigurationItem.AppName));
content = content.replace("https://getrebuild.com/img/logo.png", RebuildConfiguration.getHomeUrl("commons/theme/use-logo"));
content = content.replace("https://getrebuild.com/", RebuildConfiguration.getHomeUrl());
}
Document html = Jsoup.parse(content);
MT_CACHE = html.body();
MT_CACHE = html.body();
return MT_CACHE.clone();
}
@ -230,7 +229,7 @@ public class SMSender {
try {
sendSMS(to, content);
} catch (Exception ex) {
log.error("SMS failed to send : " + to, ex);
log.error("SMS failed to send : {}", to, ex);
}
});
}
@ -275,7 +274,7 @@ public class SMSender {
String r = OkHttpUtils.post("https://api-v4.mysubmail.com/sms/send.json", params);
rJson = JSON.parseObject(r);
} catch (Exception ex) {
log.error("Subsms failed to send : " + to + " > " + content, ex);
log.error("Subsms failed to send : {} > {}", to, content, ex);
return null;
} finally {
HeavyStopWatcher.clean();
@ -287,12 +286,13 @@ public class SMSender {
return sendId;
} else {
log.error("SMS failed to send : " + to + " > " + content + "\nError : " + rJson);
log.error("SMS failed to send : {} > {}\nError : {}", to, content, rJson);
createLog(to, content, TYPE_SMS, null, rJson.getString("msg"));
return null;
}
}
// @see com.rebuild.core.support.CommonsLog
private static void createLog(String to, String content, int type, String sentid, String error) {
if (!Application.isReady()) return;
@ -307,6 +307,7 @@ public class SMSender {
slog.setString("sendResult",
CommonsUtils.maxstr("ERR:" + StringUtils.defaultIfBlank(error, "Unknow"), 200));
}
Application.getCommonsService().create(slog);
}

View file

@ -37,6 +37,7 @@ import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.args.FlushMode;
import javax.sql.DataSource;
import java.io.File;
@ -518,7 +519,8 @@ public class Installer implements InstallState {
public static void clearAllCache() {
if (isUseRedis()) {
try (Jedis jedis = Application.getCommonsCache().getJedisPool().getResource()) {
jedis.flushAll();
// Delete all the keys of the currently selected DB
jedis.flushDB(FlushMode.SYNC);
}
} else {
Application.getCommonsCache().getEhcacheCache().clear();

View file

@ -105,13 +105,10 @@ public class AppUtils {
* @param refreshToken 是否需要刷新 Token 有效期
* @return null or UserID
*/
public static ID getRequestUserViaToken(HttpServletRequest request, boolean refreshToken) {
protected static ID getRequestUserViaToken(HttpServletRequest request, boolean refreshToken) {
String authToken = request.getHeader(HF_AUTHTOKEN);
ID user = authToken == null ? null : AuthTokenManager.verifyToken(authToken);
if (user != null && refreshToken) {
AuthTokenManager.refreshAccessToken(authToken);
}
return user;
return authToken == null
? null : AuthTokenManager.verifyToken(authToken, Boolean.FALSE, refreshToken);
}
/**

View file

@ -44,6 +44,20 @@ public class ImageView2 {
return width;
}
/**
* @param img
* @return
*/
public File thumbQuietly(File img) {
try {
File thumb = thumb(img);
return thumb != null && thumb.exists() ? thumb : img;
} catch (Exception ex) {
log.warn("Image thumb failed : {}", img, ex);
}
return img;
}
/**
* @param img
* @return

View file

@ -45,41 +45,42 @@ public class LocationUtils {
* @return
*/
public static JSON getLocation(String ip, boolean useCache) {
ip = ip.split(",")[0];
if (PRIVATE_IP.matcher(ip).find()) {
return JSONUtils.toJSONObject(new String[] { "ip", "country"}, new String[] { ip, "R" });
}
final String ckey = "IPLocation31" + ip;
JSONObject result;
if (useCache && Application.isReady()) {
result = (JSONObject) Application.getCommonsCache().getx("IPLocation2" + ip);
if (result != null) {
return result;
}
result = (JSONObject) Application.getCommonsCache().getx(ckey);
if (result != null) return result;
}
result = new JSONObject();
result.put("ip", ip);
result.put("country", "N");
JSONObject fetchTry;
JSONObject fetchTry = getJSON(String.format("http://ip-api.com/json/%s", ip));
if (fetchTry != null) {
String message = fetchTry.getString("message");
if (fetchTry.getString("countryCode") != null) {
result.put("country", fetchTry.getString("countryCode"));
result.put("region", fetchTry.getString("regionName"));
result.put("city", fetchTry.getString("city"));
} else if (message != null && (message.contains("private") || message.contains("reserved"))) {
result.put("country", "R");
}
// // #1
// fetchTry = getJSON(String.format("https://ip.taobao.com/outGetIpInfo?ip=%s&accessKey=alibaba-inc", ip));
// if (fetchTry != null && fetchTry.getIntValue("code") == 0) {
// fetchTry = fetchTry.getJSONObject("data");
// String c = fetchTry.getString("country");
// if ("local".equalsIgnoreCase(fetchTry.getString("isp_id")) || "xx".equalsIgnoreCase(c)) {
// result.put("country", "R");
// } else {
// result.put("country", "xx".equalsIgnoreCase(c) ? "" : c);
// c = fetchTry.getString("region");
// result.put("region", "xx".equalsIgnoreCase(c) ? "" : c);
// c = fetchTry.getString("city");
// result.put("city", "xx".equalsIgnoreCase(c) ? "" : c);
// }
// return result;
// }
if (Application.isReady()) {
Application.getCommonsCache().putx(ckey, result, CommonsCache.TS_DAY * 90);
}
return result;
}
// #2
// try
fetchTry = getJSON(String.format("https://ipapi.co/%s/json/", ip));
if (fetchTry != null) {
if (fetchTry.getString("country") != null) {
@ -89,30 +90,10 @@ public class LocationUtils {
} else if (fetchTry.getBooleanValue("reserved")) {
result.put("country", "R");
}
return result;
}
// #3
fetchTry = getJSON(String.format("http://ip-api.com/json/%s", ip));
if (fetchTry != null) {
String message = fetchTry.getString("message");
if (fetchTry.getString("countryCode") != null) {
result.put("country", fetchTry.getString("countryCode"));
result.put("region", fetchTry.getString("regionName"));
result.put("city", fetchTry.getString("city"));
return result;
} else if (message != null && (message.contains("private") || message.contains("reserved"))) {
result.put("country", "R");
return result;
}
}
if (result.getString("country") == null) {
result.put("country", "N");
}
if (Application.isReady()) {
Application.getCommonsCache().putx("IPLocation2" + ip, result, CommonsCache.TS_DAY * 90);
Application.getCommonsCache().putx(ckey, result, CommonsCache.TS_DAY * 90);
}
return result;
}

View file

@ -43,4 +43,12 @@ public class RbAssert {
throw new DefinedException(message);
}
}
/**
* @param expression
* @see #is(boolean, String)
*/
public static void checkAllow(boolean expression) {
is(expression, "Not Allow");
}
}

View file

@ -211,11 +211,12 @@ public class RebuildWebConfigurer implements WebMvcConfigurer, ErrorViewResolver
*/
protected static String getRequestUrls(HttpServletRequest request) {
String reqUrl = request.getRequestURL().toString();
if (StringUtils.isNotBlank(request.getQueryString())) reqUrl += "?" + request.getQueryString();
String refUrl = ServletUtils.getReferer(request);
if (refUrl == null) return reqUrl;
else if (reqUrl.endsWith("/error")) return refUrl;
else return reqUrl + " with " + refUrl;
else return reqUrl + " via " + refUrl;
}
/**

View file

@ -86,11 +86,11 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
request.setAttribute(WebConstants.LOCALE, requestEntry.getLocale());
request.setAttribute(WebConstants.$BUNDLE, Application.getLanguage().getBundle(requestEntry.getLocale()));
final String requestUri = requestEntry.getRequestUri();
final String requestUrl = requestEntry.getRequestUrl();
// 服务暂不可用
if (!Application.isReady()) {
final boolean isError = requestUri.endsWith("/error") || requestUri.contains("/error/");
final boolean isError = requestUrl.endsWith("/error") || requestUrl.contains("/error/");
// 已安装
if (checkInstalled()) {
@ -104,7 +104,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
}
}
// 未安装
else if (!(requestUri.contains("/setup/") || requestUri.contains("/commons/theme/") || isError)) {
else if (!(requestUrl.contains("/setup/") || requestUrl.contains("/commons/theme/") || isError)) {
sendRedirect(response, "/setup/install", null);
return false;
} else {
@ -120,9 +120,9 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
if (requestUser != null) {
// 管理中心二次验证
if (requestUri.contains("/admin/") && !AppUtils.isAdminVerified(request)) {
if (requestUrl.contains("/admin/") && !AppUtils.isAdminVerified(request)) {
if (isHtmlRequest(request)) {
sendRedirect(response, "/user/admin-verify", requestUri);
sendRedirect(response, "/user/admin-verify", requestEntry.getRequestUri());
} else {
response.sendError(HttpStatus.FORBIDDEN.value());
}
@ -133,8 +133,6 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
// User
request.setAttribute(WebConstants.$USER, Application.getUserStore().getUser(requestUser));
request.setAttribute(ZeroEntry.AllowCustomNav.name(),
Application.getPrivilegesManager().allow(requestUser, ZeroEntry.AllowCustomNav));
if (isHtmlRequest(request)) {
// Last active
@ -144,25 +142,28 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
String sidebarCollapsed = ServletUtils.readCookie(request, "rb.sidebarCollapsed");
String sideCollapsedClazz = BooleanUtils.toBoolean(sidebarCollapsed) ? "rb-collapsible-sidebar-collapsed" : "";
// Aside collapsed
if (!(requestUri.contains("/admin/") || requestUri.contains("/setup/"))) {
if (!(requestUrl.contains("/admin/") || requestUrl.contains("/setup/"))) {
String asideCollapsed = ServletUtils.readCookie(request, "rb.asideCollapsed");
if (BooleanUtils.toBoolean(asideCollapsed)) sideCollapsedClazz += " rb-aside-collapsed";
}
request.setAttribute("sideCollapsedClazz", sideCollapsedClazz);
request.setAttribute(ZeroEntry.AllowCustomNav.name(),
Application.getPrivilegesManager().allow(requestUser, ZeroEntry.AllowCustomNav));
}
// 非增强安全超管可访问
if (RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced)) skipCheckSafeUse = false;
else skipCheckSafeUse = UserHelper.isSuperAdmin(requestUser);
} else if (!isIgnoreAuth(requestUri)) {
} else if (!isIgnoreAuth(requestUrl)) {
// 独立验证逻辑
if (requestUri.contains("/filex/")) return true;
if (requestUrl.contains("/filex/")) return true;
log.warn("Unauthorized access {}", RebuildWebConfigurer.getRequestUrls(request));
if (isHtmlRequest(request)) {
sendRedirect(response, "/user/login", requestUri);
sendRedirect(response, "/user/login", requestEntry.getRequestUri());
} else {
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
@ -172,7 +173,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
skipCheckSafeUse = true;
}
if (!skipCheckSafeUse) checkSafeUse(ipAddr, requestUri);
if (!skipCheckSafeUse) checkSafeUse(ipAddr, requestEntry.getRequestUri());
return true;
}
@ -258,7 +259,8 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|| requestUri.startsWith("/commons/barcode/render")
|| requestUri.startsWith("/commons/theme/")
|| requestUri.startsWith("/account/user-avatar/")
|| requestUri.startsWith("/rbmob/env");
|| requestUri.startsWith("/rbmob/env")
|| requestUri.endsWith("/logout");
}
private boolean isHtmlRequest(HttpServletRequest request) {
@ -313,6 +315,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
private static class RequestEntry {
final long requestTime;
final String requestUri;
final String requestUrl;
final ID requestUser;
final String locale;
@ -320,6 +323,7 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
this.requestTime = System.currentTimeMillis();
this.requestUri = request.getRequestURI()
+ (request.getQueryString() != null ? ("?" + request.getQueryString()) : "");
this.requestUrl = request.getRequestURI();
this.requestUser = AppUtils.getRequestUser(request, true);
this.locale = locale;
}

View file

@ -57,7 +57,7 @@ public class AdminCLI2 {
* @return
*/
public String exec() {
if (this.commands.length == 0) return "Bad command";
if (this.commands.length == 0) return "WRAN: Bad command";
String result = null;
switch (commands[0]) {
@ -92,7 +92,7 @@ public class AdminCLI2 {
}
}
return StringUtils.defaultIfBlank(result, "Unknown command : " + commands[0]);
return StringUtils.defaultIfBlank(result, "WRAN: Unknown command : `" + commands[0] + "`");
}
/**
@ -101,7 +101,7 @@ public class AdminCLI2 {
* @return
*/
protected String execCache() {
if (commands.length < 2) return "Bad arguments";
if (commands.length < 2) return "WRAN: Bad arguments";
String result = SUCCESS;
@ -109,7 +109,7 @@ public class AdminCLI2 {
if ("clean".equals(name)) {
Installer.clearAllCache();
} else {
result = "Bad arguments";
result = "WRAN: Bad arguments";
}
return result;
@ -122,7 +122,7 @@ public class AdminCLI2 {
* @see ConfigurationItem
*/
protected String execSyscfg() {
if (commands.length < 2) return "Bad arguments";
if (commands.length < 2) return "WRAN: Bad arguments";
String name = commands[1];
try {
@ -142,18 +142,19 @@ public class AdminCLI2 {
}
ConfigurationItem item = ConfigurationItem.valueOf(name);
// Get
// Getter
if (commands.length == 2) {
return RebuildConfiguration.get(item);
}
// Set
String value = commands[2];
RebuildConfiguration.set(item, value);
return "OK";
// Setter
else {
String value = commands[2];
RebuildConfiguration.set(item, value);
return "OK";
}
} catch (IllegalArgumentException ex) {
return "Bad arguments [1] : " + name;
return "WRAN: Bad arguments [1] : " + name;
}
}
@ -182,10 +183,10 @@ public class AdminCLI2 {
result.add("Backup datafile : " + backup);
}
return result.isEmpty() ? "Nothing backup" : StringUtils.join(result, "\n");
return result.isEmpty() ? "WRAN: Nothing to backup" : StringUtils.join(result, "\n");
} catch (Exception ex) {
return "Exec failed : " + ex.getLocalizedMessage();
return "WRAN: Exec failed `" + ex.getLocalizedMessage() + "`";
}
}
@ -195,7 +196,7 @@ public class AdminCLI2 {
* @return
*/
protected String execAes() {
if (commands.length < 2) return "Bad arguments";
if (commands.length < 2) return "WRAN: Bad arguments";
String value = commands.length > 2 ? commands[2] : commands[1];
if ("decrypt".equalsIgnoreCase(commands[1])) {

View file

@ -79,6 +79,7 @@ public class MetaEntityController extends BaseController {
mv.getModel().put("nameField", metaEntity.getNameField().getName());
mv.getModel().put("currentEntity", metaEntity.getName());
if (metaEntity.getMainEntity() != null) {
mv.getModel().put("mainEntity", metaEntity.getMainEntity().getName());
mv.getModel().put("detailEntity", metaEntity.getName());

View file

@ -159,6 +159,7 @@ public class MetaFieldController extends BaseController {
String entityName = reqJson.getString("entity");
String label = reqJson.getString("label");
String name = reqJson.getString("name");
String type = reqJson.getString("type");
String comments = reqJson.getString("comments");
String refEntity = reqJson.getString("refEntity");
@ -182,7 +183,7 @@ public class MetaFieldController extends BaseController {
}
try {
String fieldName = new Field2Schema().createField(entity, label, dt, comments, refEntity, extConfig);
String fieldName = new Field2Schema().createField(entity, label, name, dt, comments, refEntity, extConfig);
return RespBody.ok(fieldName);
} catch (Exception ex) {

View file

@ -19,6 +19,7 @@ import com.rebuild.core.support.setup.InstallState;
import com.rebuild.core.support.setup.Installer;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController;
import com.rebuild.web.user.signup.LoginController;
import lombok.extern.slf4j.Slf4j;
@ -34,7 +35,6 @@ import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@ -51,12 +51,8 @@ import java.sql.SQLException;
public class InstallController extends BaseController implements InstallState {
@GetMapping("install")
public ModelAndView index(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (Application.isReady() && !Application.devMode()) {
response.sendError(404);
return null;
}
public ModelAndView index(HttpServletRequest request) throws IOException {
RbAssert.checkAllow(checkInstalled());
ModelAndView mv = createModelAndView("/admin/setup/install");
mv.getModel().put("Version", Application.VER);
@ -68,6 +64,7 @@ public class InstallController extends BaseController implements InstallState {
@PostMapping("test-connection")
public RespBody testConnection(HttpServletRequest request) {
RbAssert.checkAllow(checkInstalled());
JSONObject dbProps = (JSONObject) ServletUtils.getRequestJson(request);
JSONObject props = JSONUtils.toJSONObject("databaseProps", dbProps);
@ -108,6 +105,7 @@ public class InstallController extends BaseController implements InstallState {
@PostMapping("test-cache")
public RespBody testCache(HttpServletRequest request) {
RbAssert.checkAllow(checkInstalled());
JSONObject cacheProps = (JSONObject) ServletUtils.getRequestJson(request);
JedisPool pool = new JedisPool(new JedisPoolConfig(),
@ -133,6 +131,7 @@ public class InstallController extends BaseController implements InstallState {
@PostMapping("install-rebuild")
public RespBody installExec(HttpServletRequest request) {
RbAssert.checkAllow(checkInstalled());
JSONObject installProps = (JSONObject) ServletUtils.getRequestJson(request);
try {

View file

@ -12,7 +12,6 @@ import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.api.user.AuthTokenManager;
import com.rebuild.core.Application;
import com.rebuild.core.RebuildException;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.QiniuCloud;
@ -69,8 +68,11 @@ public class FileDownloader extends BaseController {
String imageView2 = request.getQueryString();
if (imageView2 != null && imageView2.contains("imageView2/")) {
imageView2 = "imageView2/" + imageView2.split("imageView2/")[1].split("&")[0];
// svg does support
if (filePath.toLowerCase().endsWith(".svg")) imageView2 = null;
// svg/webp does not support
if (filePath.toLowerCase().endsWith(".svg") || filePath.toLowerCase().endsWith(".webp")) {
imageView2 = null;
}
} else {
imageView2 = null;
}
@ -85,7 +87,7 @@ public class FileDownloader extends BaseController {
response.setContentType(mimeType);
}
ImageView2 iv2 = imageView2 == null ? null : new ImageView2(imageView2);
final ImageView2 iv2 = imageView2 == null ? null : new ImageView2(imageView2);
// 使用原图
if (iv2 == null || iv2.getWidth() <= 0 || iv2.getWidth() >= ImageView2.ORIGIN_WIDTH) {
@ -101,7 +103,7 @@ public class FileDownloader extends BaseController {
return;
}
writeLocalFile(iv2.thumb(img), response);
writeLocalFile(iv2.thumbQuietly(img), response);
}
} else {
@ -214,7 +216,7 @@ public class FileDownloader extends BaseController {
if (filepath.contains("../")
|| filepath.startsWith("_log/") || filepath.contains("/_log/")
|| filepath.startsWith("_backups/") || filepath.contains("/_backups/")) {
throw new RebuildException("Attack path detected : " + filepath);
throw new SecurityException("Attack path detected : " + filepath);
}
return filepath;
}

View file

@ -62,13 +62,15 @@ public class WidgetController extends BaseController implements ShareTo {
@GetMapping("widget-charts")
public RespBody gets(@PathVariable String entity, HttpServletRequest request) {
ConfigBean config = DataListManager.instance.getWidgetCharts(getRequestUser(request), entity);
ConfigBean config = DataListManager.instance.getWidgetCharts(
getRequestUser(request), entity);
return RespBody.ok(config == null ? null : config.toJSON());
}
@GetMapping("widget-category-data")
public RespBody getCategoryData(@PathVariable String entity) {
JSON data = DataListCategory.datas(MetadataHelper.getEntity(entity), null);
public RespBody getCategoryData(@PathVariable String entity, HttpServletRequest request) {
JSON data = DataListCategory.datas(
MetadataHelper.getEntity(entity), getRequestUser(request));
return RespBody.ok(data);
}
}

View file

@ -140,7 +140,7 @@ public class GeneralModelController extends EntityController {
FormsBuilder.instance.setFormInitialValue(metaEntity, model, (JSONObject) initialVal);
}
// v3.1 明细导入
// v3.1 明细导入配置
if (metaEntity.getDetailEntity() != null) {
List<ConfigBean> imports = TransformManager.instance.getDetailImports(metaEntity.getDetailEntity().getName());
if (!imports.isEmpty()) {
@ -202,7 +202,7 @@ public class GeneralModelController extends EntityController {
final ID user = getRequestUser(request);
final Entity metaEntity = MetadataHelper.getEntity(entity);
// 转换预览模式
// 记录转换预览模式
final String previewid = request.getParameter("previewid");
if (StringUtils.isNotBlank(previewid)) {
return new TransformerPreview(previewid, user).buildForm(true);

View file

@ -39,7 +39,7 @@ public class PicklistDataController extends BaseController {
// for PickList/MultiSelect/State
@GetMapping({"picklist", "field-options"})
public JSON fetchPicklist(HttpServletRequest request) {
public JSON fetchOptions(HttpServletRequest request) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
@ -61,8 +61,8 @@ public class PicklistDataController extends BaseController {
// for Classification
@RequestMapping("classification")
public RespBody fetchClassification(HttpServletRequest request) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
final String entity = getParameterNotNull(request, "entity");
final String field = getParameterNotNull(request, "field");
Field fieldMeta = getRealField(entity, field);
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, true);
@ -72,13 +72,10 @@ public class PicklistDataController extends BaseController {
ID parent = getIdParameter(request, "parent");
String sql = "select itemId,name from ClassificationData where dataId = ? and isHide = 'F' and ";
if (parent != null) {
sql += "parent = '" + parent + "'";
} else {
sql += "parent is null";
}
sql += " order by code, name";
Object[][] data = Application.createQueryNoFilter(sql)
if (parent != null) sql += "parent = '" + parent + "'";
else sql += "parent is null";
Object[][] data = Application.createQueryNoFilter(sql + " order by code,name")
.setParameter(1, useClassification)
.setLimit(500) // 最多显示
.array();

View file

@ -66,6 +66,7 @@ public class ReferenceSearchController extends EntityController {
Field referenceField = entity.getField(field);
if (!(referenceField.getType() == FieldType.REFERENCE || referenceField.getType() == FieldType.REFERENCE_LIST)) {
log.warn("Unsupportted type of field : {}", referenceField);
return JSONUtils.EMPTY_ARRAY;
}
@ -73,8 +74,11 @@ public class ReferenceSearchController extends EntityController {
Entity searchEntity = referenceField.getReferenceEntity();
// 引用字段数据过滤
String cascadingValue = getParameter(request, "cascadingValue", StringUtils.EMPTY);
if (cascadingValue.contains(",")) cascadingValue = cascadingValue.split(",")[0]; // N2N
String protocolFilter = new ProtocolFilterParser(null)
.parseRef(field + "." + entity.getName(), request.getParameter("cascadingValue"));
.parseRef(field + "." + entity.getName(), cascadingValue);
String q = getParameter(request, "q");
@ -84,23 +88,13 @@ public class ReferenceSearchController extends EntityController {
// 为空则加载最近使用的
if (StringUtils.isBlank(q) && !forceSearchs) {
ID[] recently = null;
ID[] used = RecentlyUsedHelper.gets(
user, searchEntity.getName(), getParameter(request, "type"), protocolFilter);
// 启用数据过滤后最近搜索将不可用
if (protocolFilter != null) {
if (forceResults) recently = new ID[0];
else return JSONUtils.EMPTY_ARRAY;
}
if (recently == null) {
String type = getParameter(request, "type");
recently = RecentlyUsedHelper.gets(user, searchEntity.getName(), type);
}
if (recently == null || recently.length == 0) {
if (used.length == 0) {
if (!forceResults) return JSONUtils.EMPTY_ARRAY;
} else {
return RecentlyUsedSearchController.formatSelect2(recently, Language.L("最近使用"));
return RecentlyUsedSearchController.formatSelect2(used, Language.L("最近使用"));
}
}
@ -111,7 +105,7 @@ public class ReferenceSearchController extends EntityController {
// 搜索指定实体的指定字段
@GetMapping("search")
public JSON search(@EntityParam Entity searchEntity, HttpServletRequest request) {
public JSON commonSearch(@EntityParam Entity searchEntity, HttpServletRequest request) {
final ID user = getRequestUser(request);
// 强制搜索 H5
@ -136,6 +130,7 @@ public class ReferenceSearchController extends EntityController {
searchEntity, getParameter(request, "quickFields"), q, null, pageSize);
}
// 构建查询
private JSON buildResultSearch(Entity searchEntity, String quickFields, String q, String appendWhere, int maxResults) {
String searchWhere = "(1=1)";
@ -161,6 +156,9 @@ public class ReferenceSearchController extends EntityController {
}
// 搜索分类字段
/**
* @see PicklistDataController#fetchClassification(HttpServletRequest)
*/
@GetMapping("classification")
public JSON searchClassification(@EntityParam Entity entity, HttpServletRequest request) {
final ID user = getRequestUser(request);
@ -168,19 +166,19 @@ public class ReferenceSearchController extends EntityController {
Field fieldMeta = entity.getField(field);
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, false);
if (useClassification == null) {
return JSONUtils.EMPTY_ARRAY;
}
if (useClassification == null) return JSONUtils.EMPTY_ARRAY;
String q = getParameter(request, "q");
// 为空则加载最近使用的
if (StringUtils.isBlank(q)) {
String type = "d" + useClassification + ":" + ClassificationManager.instance.getOpenLevel(fieldMeta);
ID[] recently = RecentlyUsedHelper.gets(user, "ClassificationData", type);
if (recently.length == 0) {
ID[] used = RecentlyUsedHelper.gets(user, "ClassificationData", type);
if (used.length == 0) {
return JSONUtils.EMPTY_ARRAY;
} else {
return RecentlyUsedSearchController.formatSelect2(recently, null);
return RecentlyUsedSearchController.formatSelect2(used, null);
}
}
@ -196,6 +194,7 @@ public class ReferenceSearchController extends EntityController {
return (JSON) JSON.toJSON(result);
}
// 查询结果
private List<Object> resultSearch(String sqlWhere, Entity entity, int maxResults) {
Field nameField = entity.getNameField();
@ -265,6 +264,7 @@ public class ReferenceSearchController extends EntityController {
}
/**
* 引用字段搜索页面
* @see com.rebuild.web.general.GeneralListController#pageList(String, HttpServletRequest, HttpServletResponse)
*/
@GetMapping("reference-search")
@ -294,9 +294,9 @@ public class ReferenceSearchController extends EntityController {
if (ProtocolFilterParser.getFieldDataFilter(field) != null
|| ProtocolFilterParser.hasFieldCascadingField(field)) {
String protocolExpr = String.format("ref:%s:%s",
String protocolExpr = String.format("%s:%s:%s", ProtocolFilterParser.P_REF,
getParameterNotNull(request, "field"),
StringUtils.defaultString(getParameter(request, "cascadingValue"), ""));
getParameter(request, "cascadingValue", StringUtils.EMPTY));
mv.getModel().put("referenceFilter", protocolExpr);
} else {
mv.getModel().put("referenceFilter", StringUtils.EMPTY);

View file

@ -101,6 +101,12 @@ public class RelatedListController extends BaseController {
Map<String, Integer> countMap = new HashMap<>();
for (String related : relateds) {
// 附件特殊处理
if (related.startsWith("Attachment.")
&& !Application.getPrivilegesManager().allowRead(user, mainid)) {
continue;
}
String sql = buildBaseSql(mainid, related, null, true, user);
// 任务是获取了全部的相关记录因此总数可能与实际显示的条目数量不一致

View file

@ -8,11 +8,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.user;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.api.RespBody;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.support.License;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* UCenter
*
@ -21,10 +28,13 @@ import org.springframework.web.bind.annotation.*;
*/
@RestController
@RequestMapping("/settings/ucenter")
public class UCenterController {
public class UCenterController extends BaseController {
@PostMapping("/bind")
public RespBody bindCloudAccount(@RequestBody JSONObject body) {
public RespBody bindCloudAccount(@RequestBody JSONObject body, HttpServletRequest request) {
final ID user = getRequestUser(request);
RbAssert.isAllow(UserHelper.isSuperAdmin(user), Language.L("仅超级管理员可操作"));
String account = body.getString("cloudAccount");
String passwd = body.getString("cloudPasswd");
@ -42,9 +52,9 @@ public class UCenterController {
}
@GetMapping("/bind-query")
public RespBody bindQuery() {
public RespBody bindQuery(HttpServletRequest request) {
JSONObject res = License.siteApi("api/ucenter/bind-query");
String bindAccount = res.getString("bindAccount");
return RespBody.ok(bindAccount);
res.put("canBind", UserHelper.isSuperAdmin(getRequestUser(request)));
return RespBody.ok(res);
}
}

View file

@ -107,16 +107,7 @@ public class LoginAction extends BaseController {
String authToken = AuthTokenManager.generateAccessToken(user);
resMap.put("authToken", authToken);
// FIXME 暂不启用 lauthToken 前端有问题
// // 2FA
// int faMode = RebuildConfiguration.getInt(ConfigurationItem.Login2FAMode);
// if (faMode <= 0) {
// String lauthToken = user + "," + System.currentTimeMillis() + ",h5";
// resMap.put("lauthToken", AES.encrypt(lauthToken));
// }
request.getSession().invalidate();
return resMap;
}
@ -149,14 +140,14 @@ public class LoginAction extends BaseController {
String ipAddr = StringUtils.defaultString(ServletUtils.getRemoteAddr(request), "127.0.0.1");
final Record record = EntityHelper.forNew(EntityHelper.LoginLog, UserService.SYSTEM_USER);
record.setID("user", user);
record.setString("ipAddr", ipAddr);
record.setString("userAgent", uaClear);
record.setDate("loginTime", CalendarUtils.now());
final Record llog = EntityHelper.forNew(EntityHelper.LoginLog, UserService.SYSTEM_USER);
llog.setID("user", user);
llog.setString("ipAddr", ipAddr);
llog.setString("userAgent", uaClear);
llog.setDate("loginTime", CalendarUtils.now());
TaskExecutors.queue(() -> {
Application.getCommonsService().create(record);
Application.getCommonsService().create(llog);
User u = Application.getUserStore().getUser(user);
String uid = StringUtils.defaultString(u.getEmail(), u.getName());

View file

@ -67,7 +67,7 @@ public class LoginController extends LoginAction {
// Token 登录
final String useToken = getParameter(request, "token");
if (StringUtils.isNotBlank(useToken)) {
ID tokenUser = AuthTokenManager.verifyToken(useToken, true);
ID tokenUser = AuthTokenManager.verifyToken(useToken, true, false);
if (tokenUser != null) {
loginSuccessed(request, response, tokenUser, false);
@ -187,28 +187,28 @@ public class LoginController extends LoginAction {
ServletUtils.setSessionAttribute(request, UserAvatar.SK_DAVATAR, System.currentTimeMillis());
final User loginUser = Application.getUserStore().getUser(user);
final boolean isMobile = AppUtils.isRbMobile(request);
final boolean isRbMobile = AppUtils.isRbMobile(request);
Map<String, Object> resMap = new HashMap<>();
// 2FA
int faMode = RebuildConfiguration.getInt(ConfigurationItem.Login2FAMode);
if (faMode > 0
&& (!UserHelper.isSuperAdmin(loginUser.getId()) || RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced))) {
boolean faModeSkip = UserHelper.isSuperAdmin(loginUser.getId()) && !RebuildConfiguration.getBool(ConfigurationItem.SecurityEnhanced);
if (faMode > 0 && !faModeSkip) {
resMap.put("login2FaMode", faMode);
String userToken = CodecUtils.randomCode(40);
Application.getCommonsCache().putx(PREFIX_2FA + userToken, loginUser.getId(), 15 * 60); // 15m
resMap.put("login2FaUserToken", userToken);
if (isMobile) {
if (isRbMobile) {
request.getSession().invalidate();
}
return RespBody.ok(resMap);
}
if (isMobile) {
if (isRbMobile) {
resMap = loginSuccessedH5(request, response, loginUser.getId());
} else {
Integer ed = loginSuccessed(

View file

@ -1,31 +1,29 @@
<html>
<head>
<meta charset="utf-8" />
<title>MAIL TEMPLATE</title>
</head>
<head>
<meta charset="utf-8">
<title>EMAIL TEMPLATE</title>
</head>
<body>
<div style="background-color:#f5f5f5;margin:0;font-size:0.9rem;padding:20px;color:#333;font-family:Roboto,Helvetica,'Microsoft YaHei','宋体',sans-serif;line-height:1.5">
<div>
<a href="https://getrebuild.com/" target="_blank">
<img src="https://getrebuild.com/img/logo.png" alt="REBUILD" style="width:134px;display:inline-block;font-size:0"/>
<body>
<div style="background-color: #f5f5f5; margin: 0; font-size: 0.9rem; padding: 20px; color: #333; font-family: Roboto, Helvetica, 'Microsoft YaHei', '宋体', sans-serif; line-height: 1.5">
<div>
<a href="%APPURL%" target="_blank">
<img src="%APPLOGO%" alt="%APPNAME%" style="width: 134px; display: inline-block; font-size: 0" />
</a>
</div>
<div style="border:1px solid #eee;padding:30px 20px;background-color:#fff;border-radius:3px;border-top:3px solid #4285f4;max-width:720px;margin:10px 0">
<h2 class="rb-title" style="font-size:1.3rem;margin:0;font-weight:400">%TITLE%</h2>
<div class="rb-content" style="margin-top:10px">%CONTETN%</div>
</div>
<div>
<div class="rb-footer" style="margin:0;color:#888;font-size:12px;max-width:760px">
本消息由 REBUILD 系统自动发送,请不要直接回复。
<br>
<em>This email was sent to %TO% at %TIME%</em>
<br>
%PAGE_FOOTER%
</div>
<div style="border: 1px solid #eee; padding: 30px 20px; background-color: #fff; border-radius: 3px; border-top: 3px solid #4285f4; max-width: 720px; margin: 10px 0">
<h2 class="rb-title" style="font-size: 1.3rem; margin: 0; font-weight: 400">%TITLE%</h2>
<div class="rb-content" style="margin-top: 10px">%CONTETN%</div>
</div>
<div>
<div class="rb-footer" style="margin: 0; color: #888; font-size: 12px; max-width: 760px">
本消息由 %APPNAME% 系统自动发送,请不要直接回复。
<br />
<em>This email was sent to %TO% at %TIME%</em>
<br />
%PAGE_FOOTER%
</div>
</div>
</div>
</div>
</body>
</html>
</body>
</html>

View file

@ -2360,5 +2360,14 @@
"绑定 REBUILD 云账号":"绑定 REBUILD 云账号",
"当前已绑定云账号":"当前已绑定云账号",
"是否保留已有明细记录?":"是否保留已有明细记录?",
"Office 文件":"Office 文件"
"Office 文件":"Office 文件",
"修改字段":"修改字段",
"请填写新值":"请填写新值",
"最后审批批注":"最后审批批注",
"修改方式":"修改方式",
"仅允许拍照上传":"仅允许拍照上传",
"批量添加字段":"批量添加字段",
"支持 H5":"支持 H5",
"手机登录":"手机登录",
"批量添加":"批量添加"
}

View file

@ -184,12 +184,12 @@
<field name="name" type="string" max-length="100" nullable="false"/>
<field name="fullName" type="string" max-length="191" nullable="false" description="(包括父级名称, 用点号分割)"/>
<field name="parent" type="reference" ref-entity="ClassificationData" cascade="delete"/>
<field name="code" type="string" max-length="50"/>
<field name="code" type="string" max-length="50" default-value="0"/>
<field name="level" type="small-int" default-value="0" updatable="false"/>
<field name="isHide" type="bool" default-value="F"/>
<field name="quickCode" type="string" max-length="50" queryable="false"/>
<index field-list="dataId,parent"/>
<index field-list="dataId,fullName,quickCode"/>
<index field-list="dataId,parent,code"/>
<index field-list="dataId,fullName,quickCode,code"/>
</entity>
<entity name="ShareAccess" type-code="020" description="记录共享" queryable="false">

View file

@ -273,7 +273,7 @@ create table if not exists `classification_data` (
`NAME` varchar(100) not null,
`FULL_NAME` varchar(191) not null comment '(包括父级名称, 用点号分割)',
`PARENT` char(20),
`CODE` varchar(50),
`CODE` varchar(50) default '0',
`LEVEL` smallint(6) default '0',
`IS_HIDE` char(1) default 'F',
`QUICK_CODE` varchar(50),
@ -282,8 +282,8 @@ create table if not exists `classification_data` (
`CREATED_BY` char(20) not null comment '创建人',
`CREATED_ON` timestamp not null default current_timestamp comment '创建时间',
primary key (`ITEM_ID`),
index IX0_classification_data (`DATA_ID`, `PARENT`),
index IX1_classification_data (`DATA_ID`, `FULL_NAME`, `QUICK_CODE`)
index IX0_classification_data (`DATA_ID`, `PARENT`, `CODE`),
index IX1_classification_data (`DATA_ID`, `FULL_NAME`, `QUICK_CODE`, `CODE`)
)Engine=InnoDB;
-- ************ Entity [ShareAccess] DDL ************
@ -830,7 +830,10 @@ insert into `layout_config` (`CONFIG_ID`, `BELONG_ENTITY`, `CONFIG`, `APPLY_TYPE
('013-9000000000000001', 'Department', '[{"field":"name"},{"field":"principalId"},{"field":"parentDept"},{"field":"isDisabled"}]', 'FORM', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000002', 'User', '[{"field":"fullName"},{"field":"jobTitle"},{"field":"workphone"},{"field":"email"},{"field":"loginName"},{"field":"password"},{"field":"$DIVIDER$"},{"field":"deptId"},{"field":"roleId"},{"field":"isDisabled"}]', 'FORM', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000003', 'Role', '[{"field":"name"},{"field":"isDisabled"}]', 'FORM', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000004', 'Team', '[{"field":"name"},{"field":"principalId"},{"field":"isDisabled"}]', 'FORM', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001');
('013-9000000000000004', 'Team', '[{"field":"name"},{"field":"principalId"},{"field":"isDisabled"}]', 'FORM', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000005', 'User', '[{"field":"fullName"},{"field":"jobTitle"},{"field":"deptId"},{"field":"workphone"},{"field":"email"},{"field":"loginName"},{"field":"roleId"},{"field":"isDisabled"},{"field":"createdOn"}]', 'DATALIST', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000006', 'Department', '[{"field":"name"},{"field":"principalId"},{"field":"parentDept"},{"field":"isDisabled"},{"field":"createdOn"}]', 'DATALIST', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001'),
('013-9000000000000007', 'Team', '[{"field":"name"},{"field":"principalId"},{"field":"isDisabled"},{"field":"createdOn"}]', 'DATALIST', 'ALL', CURRENT_TIMESTAMP, '001-0000000000000001', CURRENT_TIMESTAMP, '001-0000000000000001');
-- Classifications (No data)
insert into `classification` (`DATA_ID`, `NAME`, `DESCRIPTION`, `OPEN_LEVEL`, `IS_DISABLED`, `CREATED_ON`, `CREATED_BY`, `MODIFIED_ON`, `MODIFIED_BY`)

View file

@ -6,7 +6,7 @@
<script th:src="@{/assets/lib/widget/mprogress.min.js}"></script>
<script th:src="@{/assets/lib/moment-with-locales.min.js?v=2.27.0}"></script>
<script th:src="@{/assets/lib/widget/bootstrap-datetimepicker.min.js?v=2.4.4}"></script>
<script th:src="@{/assets/lib/jquery.html5uploader.js}"></script>
<script th:src="@{/assets/lib/jquery.html5uploader.js?v=310}"></script>
<script th:src="@{/assets/lib/qiniu.min.js?v=3.4.1}"></script>
<script th:src="@{/assets/lib/widget/select2.js?v=4.0.13.fix2}"></script>
<script th:src="@{/assets/lib/jquery-ui.min.js?v=1.13.1}"></script>

View file

@ -37,7 +37,7 @@
<meta name="rb._uploadMaxSize" th:content="${PortalUploadMaxSize}" />
<!--[if lt IE 10]><script>location.href = '[[${baseUrl}]]/error/unsupported-browser'</script><![endif]-->
<script th:if="${T(com.rebuild.utils.AppUtils).isIE11(#request)}" th:src="@{/assets/lib/react/polyfill.min.js?v=7.6.0}"></script>
<script th:if="${markWatermark == 'true'}" th:src="@{/assets/lib/watermark.js?v=2.3.2.2}"></script>
<script th:if="${markWatermark == 'true'}" th:src="@{/assets/lib/watermark.js?v=310}"></script>
<style>
.logo-img,
.rb-top-header .rb-navbar-header .navbar-brand {

View file

@ -69,6 +69,7 @@
$.post('/admin/admin-cli/exec', c, (o) => {
$c.addClass('ok')
if (o && o.data) _print(o.data, true)
else if (o && o.error_msg) _print(`ERROR: ${o.error_msg}`, true)
})
}
})

View file

@ -22,7 +22,7 @@
<div class="col-12 col-md-6">
<div class="dataTables_filter">
<div class="input-group input-search">
<input class="form-control" type="text" th:placeholder="${bundle.L('查询')}" maxlength="40" data-quickFields="user.loginName,user.email,&user,ipAddr" />
<input class="form-control" type="text" th:placeholder="${bundle.L('查询')}" maxlength="40" data-quickFields="&user,ipAddr" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button"><i class="icon zmdi zmdi-search"></i></button>
</span>

View file

@ -112,10 +112,10 @@
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
<div class="message">
<a class="close" data-dismiss="alert"><span class="zmdi zmdi-close"></span></a>
<p>1</p>
<p>ALERT</p>
</div>
</div>
<div class="float-right" style="margin-top: -1px">
<div class="float-right">
<button class="btn btn-secondary btn-space J_new-role" type="button"><i class="icon zmdi zmdi-plus"></i> [[${bundle.L('新建角色')}]]</button>
<button class="btn btn-primary btn-space J_save mr-0" type="button" disabled="disabled">[[${bundle.L('保存')}]]</button>
</div>
@ -165,6 +165,7 @@
</tr>
</tbody>
</table>
<p th:if="${Entities.size() == 0}" class="text-muted mt-5">[[${bundle.L('暂无可用业务实体')}]]</p>
<div class="legend-wrap">
<div class="legend">
[[${bundle.L('图例')}]]

View file

@ -55,7 +55,7 @@
<div class="col-12 col-lg-5">
<div class="dataTables_filter">
<div class="input-group input-search">
<input class="form-control" type="text" th:placeholder="${bundle.L('快速查询')}" maxlength="40" data-quickFields="loginName,fullName,email,quickCode" />
<input class="form-control" type="text" th:placeholder="${bundle.L('快速查询')}" maxlength="40" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button"><i class="icon zmdi zmdi-search"></i></button>
</span>

View file

@ -74,16 +74,13 @@
<dt class="col-12 col-lg-4">[[${bundle.L('上次登录')}]]</dt>
<dd class="col-12 col-lg-8 J_loginOn"><span>[[${bundle.L('无')}]]</span></dd>
</dl>
</div>
<div>
<div class="alert alert-info alert-icon alert-icon-border alert-sm hide J_roles mt-5">
<div class="icon"><span class="zmdi zmdi-lock"></span></div>
<div class="message">
<span class="text-muted">[[${bundle.L('附加角色')}]]</span>
<p class="column-n2n"></p>
</div>
<div class="form-line admin-show">
<fieldset><legend></legend></fieldset>
</div>
<dl class="row admin-show">
<dt class="col-12 col-lg-4">[[${bundle.L('附加角色')}]]</dt>
<dd class="col-12 col-lg-8 J_roleAppends"><span>[[${bundle.L('无')}]]</span></dd>
</dl>
</div>
</div>
</div>

View file

@ -32,7 +32,7 @@
<div class="float-left">
<div class="page-head-title">
[[${bundle.L('报表模板')}]]
<i class="support-plat mdi mdi-monitor" th:title="${bundle.L('支持 PC')}" style="margin-top: 9px"></i>
<i class="support-plat2 mdi mdi-monitor mt-2" th:title="${bundle.L('支持 PC')}"></i>
</div>
</div>
<div class="float-right pt-1">

View file

@ -78,7 +78,9 @@
<div class="main-content container-fluid pt-1">
<div th:if="${useListMode}" class="card">
<div class="card-header">
[[${bundle.L('列表模式')}]] <sup class="rbv"></sup> <i class="support-plat mdi mdi-monitor" th:title="${bundle.L('支持 PC')}" style="margin-top: 2px"></i>
[[${bundle.L('列表模式')}]]
<sup class="rbv"></sup>
<i class="support-plat2 mdi mdi-monitor mt-1" th:title="${bundle.L('支持 PC')}"></i>
</div>
<div class="card-body mode-select">
<div class="row">
@ -125,12 +127,12 @@
</div>
</div>
<div class="col">
<div class="bosskey-show">
<div class="bosskey-show hide">
<div class="row ph">
<div class="col-3 p-0">
<div class="col-2 p-0">
<div class="block"></div>
</div>
<div class="col-9 p-0">
<div class="col-10 p-0">
<div class="block">
<div class="block-item2" style="margin-left: 3%"><i class="mdi mdi-image"></i></div>
<div class="block-item2"><i class="mdi mdi-image"></i></div>

View file

@ -74,6 +74,26 @@
<p class="form-text mb-0">[[${bundle.L('用于列表、表单引用字段等处的快速查询')}]]</p>
</div>
</div>
<th:block th:if="${currentEntity == mainEntity}">
<div class="form-group row bosskey-show">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">Not Co-Editing</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="notCoEditing" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>
<div class="form-group row bosskey-show">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">Details Not Allow Empty</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="detailsNotEmpty" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>
</th:block>
<div class="form-group row">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">[[${bundle.L('备注')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">

View file

@ -84,7 +84,7 @@
</div>
</div>
</div>
<div th:if="${fieldType == 'REFERENCE'}" class="form-group row">
<div th:if="${fieldType == 'REFERENCE' or fieldType == 'N2NREFERENCE'}" class="form-group row">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">[[${bundle.L('父级级联字段')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<select class="form-control form-control-sm" id="referenceCascadingField">
@ -161,6 +161,18 @@
<div class="form-text" th:utext="${bundle.L('如有多个类型请使用逗号分开')}"></div>
</div>
</div>
<div th:if="${fieldType == 'IMAGE'}" class="form-group row J_for-IMAGE bosskey-show">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">
<i class="support-plat2 mdi mdi-cellphone-check" style="margin-left: -18px" th:title="${bundle.L('支持 H5')}"></i>
[[${bundle.L('仅允许拍照上传')}]]
</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="imageCapture" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>
<div th:if="${fieldType == 'PICKLIST' or fieldType == 'MULTISELECT'}" class="form-group row J_for-PICKLIST J_for-MULTISELECT">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">[[${bundle.L('选项列表')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
@ -219,7 +231,8 @@
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">[[${bundle.L('是否允许负数')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="notNegative" /><span class="custom-control-label"> [[${bundle.L('不允许')}]]</span>
<input class="custom-control-input" type="checkbox" id="notNegative" />
<span class="custom-control-label"> [[${bundle.L('不允许')}]]</span>
</label>
</div>
</div>
@ -234,10 +247,12 @@
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">[[${bundle.L('显示样式')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8 pt-1">
<label class="custom-control custom-control-sm custom-radio custom-control-inline mb-0">
<input class="custom-control-input" type="radio" name="barcodeType" value="QRCODE" checked /><span class="custom-control-label"> [[${bundle.L('二维码')}]]</span>
<input class="custom-control-input" type="radio" name="barcodeType" value="QRCODE" checked />
<span class="custom-control-label"> [[${bundle.L('二维码')}]]</span>
</label>
<label class="custom-control custom-control-sm custom-radio custom-control-inline mb-0">
<input class="custom-control-input" type="radio" name="barcodeType" value="BARCODE" /><span class="custom-control-label"> [[${bundle.L('条形码')}]] (CODE128)</span>
<input class="custom-control-input" type="radio" name="barcodeType" value="BARCODE" />
<span class="custom-control-label"> [[${bundle.L('条形码')}]] (CODE128)</span>
</label>
</div>
</div>
@ -287,7 +302,8 @@
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">[[${bundle.L('使用富文本编辑器')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="useMdedit" /><span class="custom-control-label"> [[${bundle.L('是')}]]</span>
<input class="custom-control-input" type="checkbox" id="useMdedit" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>
@ -296,7 +312,8 @@
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">[[${bundle.L('自动定位')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="locationAutoLocation" /><span class="custom-control-label"> [[${bundle.L('是')}]]</span>
<input class="custom-control-input" type="checkbox" id="locationAutoLocation" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>
@ -304,7 +321,8 @@
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right pt-1">[[${bundle.L('视图页直接显示地图')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="locationMapOnView" /><span class="custom-control-label"> [[${bundle.L('是')}]]</span>
<input class="custom-control-input" type="checkbox" id="locationMapOnView" />
<span class="custom-control-label"> [[${bundle.L('是')}]]</span>
</label>
</div>
</div>

View file

@ -49,6 +49,7 @@
<div class="col-sm-6">
<div class="dataTables_oper">
<button class="btn btn-primary btn-space J_new-field" type="button"><i class="icon zmdi zmdi-plus"></i> [[${bundle.L('添加')}]]</button>
<button class="btn btn-primary btn-space J_new2-field bosskey-show" type="button"><i class="icon zmdi zmdi-plus"></i> [[${bundle.L('批量添加')}]]</button>
</div>
</div>
</div>
@ -98,7 +99,7 @@
$(document).ready(() => {
loadFields()
$('.input-search .btn').on('click', () => __renderList())
$('.input-search .btn').on('click', () => renderList())
$('.input-search .form-control').keydown((e) => {
if (e.which === 13) $('.input-search .btn').trigger('click')
})
@ -106,19 +107,23 @@
if (wpc.isSuperAdmin) RbModal.create(`/p/admin/metadata/field-new?entity=${wpc.entityName}`, $L('添加字段'))
else RbHighbar.error($L('仅超级管理员可添加字段'))
})
$('.J_new2-field').on('click', () => {
if (wpc.isSuperAdmin) RbModal.create(`/p/admin/metadata/field-new2?entity=${wpc.entityName}`, $L('批量添加字段'), { width: 1064 })
else RbHighbar.error($L('仅超级管理员可添加字段'))
})
})
let fields_data = []
const loadFields = function () {
$.get(`../list-field?entity=${wpc.entityName}&refname=true`, function (res) {
fields_data = res.data || []
__renderList()
renderList()
$('.tablesort').tablesort()
})
}
const __renderList = function () {
const renderList = function () {
const $tbody = $('#dataList tbody').empty()
const q = ($val('.input-search .form-control') || '').toLowerCase()

View file

@ -53,7 +53,7 @@
</div>
<div class="dialog-footer">
<button class="btn btn-link" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-secondary" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary J_save" type="button">[[${bundle.L('保存')}]]</button>
</div>
</div>

View file

@ -65,7 +65,7 @@
</div>
<div class="dialog-footer">
<button class="btn btn-link" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-secondary" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary J_save" type="button">[[${bundle.L('保存')}]]</button>
</div>
</div>

View file

@ -77,8 +77,8 @@
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-primary J_save" type="button">[[${bundle.L('保存')}]]</button>
<button class="btn btn-secondary" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary J_save" type="button">[[${bundle.L('保存')}]]</button>
</div>
</div>
<th:block th:replace="~{/_include/footer}" />

View file

@ -39,7 +39,7 @@
<span class="custom-control-label">[[${bundle.L('隐藏无记录项')}]]</span>
</label>
</span>
<button class="btn btn-link" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-secondary" onclick="parent.RbModal.hide()" type="button">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary J_save" type="button">[[${bundle.L('保存')}]]</button>
</div>
</div>

View file

@ -51,9 +51,6 @@
border-radius: 2px;
margin-right: 3px;
}
.support-plat {
margin-top: -1px;
}
.J_cloudAccount .icon {
font-size: 24px;
float: left;
@ -121,14 +118,14 @@
<tr>
<td>
[[${bundle.L('登录页每日一图')}]]
<i class="support-plat mdi mdi-monitor" th:title="${bundle.L('支持 PC')}"></i>
<i class="support-plat2 mdi mdi-monitor mt-0" th:title="${bundle.L('支持 PC')}"></i>
</td>
<td data-id="LiveWallpaper" th:data-value="${LiveWallpaper}">[[${LiveWallpaper ? bundle.L('是') : bundle.L('否')}]]</td>
</tr>
<tr>
<td>
[[${bundle.L('自定义登录页图')}]]
<i class="support-plat mdi mdi-monitor" th:title="${bundle.L('支持 PC')}"></i>
<i class="support-plat2 mdi mdi-monitor mt-0" th:title="${bundle.L('支持 PC')}"></i>
</td>
<td class="fs-0 bgimg">
<a class="img-thumbnail p-0 border-0" data-id="CustomWallpaper">
@ -307,7 +304,7 @@
</div>
<div class="J_has-bind">
<p class="mb-1 mt-1 text-bold">
<i class="mdi mdi-shield-check icon"></i>
<i class="mdi mdi-shield-check icon up-3"></i>
[[${bundle.L('当前已绑定云账号')}]] <a href="https://getrebuild.com/ucenter/account" target="_blank" class="text-uppercase"></a>
</p>
</div>

View file

@ -33,7 +33,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
position: relative;
margin: 0;
z-index: 2;
background-color: #fff;
}
.chart-box .chart-head .chart-title {
@ -56,13 +55,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
.chart-box .chart-head .chart-oper a {
color: #aaa;
color: #000;
opacity: 0.4;
display: inline-block;
height: 20px;
margin-left: 10px;
padding-left: 3px;
}
.chart-box .chart-head .chart-oper a:hover {
opacity: 0.7;
}
.chart-box .chart-head .chart-oper a .zmdi {
font-size: 1.3rem;
vertical-align: middle;
@ -77,10 +81,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
font-size: 1.43rem;
}
.chart-box .chart-head .chart-oper a:hover {
color: #5a5a5a;
}
.chart-undata {
margin: 0;
color: #999;
@ -120,12 +120,12 @@ See LICENSE and COMMERCIAL in the project root for license information.
.chart.index > .data-item p {
font-size: 1.1rem;
color: #888;
margin-bottom: 1px;
opacity: 0.8;
}
.chart.index > .data-item strong {
font-size: 2.8rem;
font-size: 3.1rem;
font-weight: 400;
}
@ -134,7 +134,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
.chart.index > .data-item a:hover {
opacity: 0.8;
opacity: 0.9;
}
.chart.ctable {
@ -452,3 +452,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
.grid-stack-item.fullscreen .ui-resizable-handle {
display: none !important;
}
/* useColor */
.grid-stack-item.color .grid-stack-item-content,
.grid-stack-item.color .chart.index > .data-item strong {
color: #fff;
}
.grid-stack-item.color .rb-loading:after {
background-color: transparent;
}
.grid-stack-item.color .rb-spinner svg {
stroke: #fff;
}

View file

@ -327,7 +327,6 @@ form.field-attr label > span {
position: absolute;
font-size: 18px;
left: 5px;
margin-top: -1px;
}
.form-preview .dd-handle:hover::before,

View file

@ -780,6 +780,7 @@ body.dialog .main-content {
.dialog-footer .btn {
margin-left: 5px;
min-width: 98px;
}
.dialog-footer > .float-left > .custom-control {
@ -912,14 +913,8 @@ select.form-control:not([disabled]) {
}
@media (min-width: 1064px) {
.form-layout > .row .form-group.col-sm-3,
.form-layout > .row .form-group.col-sm-4 {
padding-right: 0;
}
.form-layout > .row .form-group.col-sm-3 > .col-form-label,
.form-layout > .row .form-group.col-sm-4 > .col-form-label {
width: 110px;
.form-layout > .row .form-group.col-sm-3 > .col-form-label {
width: 100px;
}
}
@ -1061,7 +1056,8 @@ select.form-control:not([disabled]) {
.img-field .img-upload[disabled],
.img-field .img-upload[disabled]:hover,
.img-field .img-upload[disabled] .zmdi {
.img-field .img-upload[disabled] .zmdi,
.img-field .img-upload[disabled] .mdi {
opacity: 1 !important;
color: #dbdbdb !important;
cursor: not-allowed !important;
@ -1076,10 +1072,10 @@ select.form-control:not([disabled]) {
cursor: pointer;
}
.img-field label.img-upload .zmdi {
font-size: 1.6rem;
.img-field label.img-upload .zmdi,
.img-field label.img-upload .mdi {
font-size: 1.8rem;
color: #aaa;
font-weight: 300;
}
.img-field a.img-upload {
@ -2433,6 +2429,10 @@ th.column-fixed {
padding: 30px;
}
.dropdown-menu-advfilter .alert {
display: none;
}
div.dataTables_wrapper div.dataTables_filter .dropdown-menu-advfilter input {
width: 100%;
display: block;
@ -3537,7 +3537,7 @@ form {
}
#asideFilters .dropdown-item,
#asideClass .dropdown-item {
#asideCategory .dropdown-item {
padding: 8px 10px;
border-radius: 2px;
font-size: 1rem;
@ -3546,17 +3546,17 @@ form {
}
#asideFilters .dropdown-item:hover,
#asideClass .dropdown-item:hover {
#asideCategory .dropdown-item:hover {
background-color: #eee;
}
#asideFilters .dropdown-item:active,
#asideClass .dropdown-item:active {
#asideCategory .dropdown-item:active {
color: #404040;
}
#asideFilters .dropdown-item.active,
#asideClass .dropdown-item.active {
#asideCategory .dropdown-item.active {
background-color: #4285f4;
color: #fff;
}
@ -4183,6 +4183,12 @@ h3.modal-title > .rbv {
font-weight: normal;
}
.mdedit-content a[target]:hover,
.editor-preview a[target]:hover {
text-decoration: underline;
opacity: 1;
}
.popover {
max-width: 300px;
}
@ -4258,6 +4264,14 @@ html.external-auth .auth-body.must-center .login {
display: none;
}
@media (max-width: 576px) {
html.external-auth .auth-body .card {
padding-left: 10px;
padding-right: 10px;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
}
}
.page-footer {
position: fixed;
bottom: 0;
@ -4881,6 +4895,7 @@ pre.unstyle {
.rb-left-sidebar .sidebar-elements > li ul {
background-color: #f5f5f5;
background-color: #eee;
padding: 6px 0;
}
.rb-left-sidebar .sidebar-elements > li > a:hover,
@ -4921,20 +4936,27 @@ pre.unstyle {
background-color: #eee;
}
/* mdi7 */
/* v3: mdi7 */
.mdi {
display: inline-block;
line-height: 1;
}
.mdi.text-bold::before {
font-weight: bold;
}
.zmdi.mdi::before {
line-height: unset;
}
.modal-header > .icon.mdi,
.header-icon.mdi {
font-size: 2rem;
line-height: 1;
}
.over-tab {
margin-top: -63px;
transition: margin-top 0.4s;
}
/* theme defs v3.1 */
/* v3.1: theme defs */
.rb-left-sidebar .sidebar-elements > li.active > a,
.rb-left-sidebar .sidebar-elements > li.active > a > span,
@ -4946,17 +4968,14 @@ pre.unstyle {
border-top-color: var(--rb-theme-color);
}
.support-plat {
.support-plat2 {
position: absolute;
margin-left: 8px;
background-color: var(--rb-color);
color: #fff;
display: inline-block;
border-radius: 50%;
width: 18px;
height: 18px;
line-height: 18px;
cursor: help;
text-align: center;
font-size: 13px;
font-size: 15px;
margin-top: -1px;
margin-left: 8px;
}
.modal-title > .support-plat2 {
margin-top: 7px;
}

View file

@ -38,7 +38,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
.task-form .hover:hover {
cursor: pointer;
opacity: 0.8;
opacity: 0.9;
}
.task-form .user-selector,
@ -223,6 +223,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
font-size: 1.231rem;
background-color: #eee;
border-radius: 50%;
width: 26px;
height: 26px;
}
.task-tags a.tag-add:hover {
@ -248,15 +250,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
.tags-editor .colors > a {
width: 28px;
height: 28px;
width: 22px;
height: 22px;
display: inline-block;
background-color: #ccc;
border-radius: 50%;
color: #fff;
overflow: hidden;
font-size: 1.3rem;
padding-top: 6px;
padding-top: 4px;
}
.tags-editor .colors > a:hover {
@ -264,7 +266,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
.tags-editor .colors > a + a {
margin-left: 11px;
margin-left: 7px;
}
.tags-editor .colors > a > i {

View file

@ -98,11 +98,14 @@ body {
line-height: 1.428571;
}
.rbview-form .type-NTEXT .form-control-plaintext.mdedit-content,
.rbview-form .type-NTEXT.col-sm-12 .form-control-plaintext {
max-height: 134px;
}
.rbview-form .type-NTEXT .form-control-plaintext.mdedit-content {
max-height: 1001px;
}
.nav-tabs > li.nav-item a.nav-link {
padding: 11px 10px;
overflow: hidden;

View file

@ -31,7 +31,7 @@ class DataList extends React.Component {
}
}
const _pageIps = []
let _pageIps = []
const CellRenders_renderSimple = CellRenders.renderSimple
// eslint-disable-next-line react/display-name
@ -39,7 +39,7 @@ CellRenders.renderSimple = function (v, s, k) {
let comp = CellRenders_renderSimple(v, s, k)
if (k.endsWith('.ipAddr')) {
if (!_pageIps.contains(v)) _pageIps.push(v)
comp = React.cloneElement(comp, { className: `J_ip-${v.replace(/\./g, '-')}` })
comp = React.cloneElement(comp, { className: `J_ip-${v.replace(/[^0-9]/g, '')}` })
}
return comp
}
@ -50,13 +50,14 @@ RbList.renderAfter = function () {
if (res.error_code === 0 && res.data.country !== 'N') {
let L = res.data.country === 'R' ? $L('局域网') : [res.data.region, res.data.country].join(', ')
L = `${ip} (${L})`
$(`.J_ip-${ip.replace(/\./g, '-')}`)
.attr('title', L)
$(`.J_ip-${ip.replace(/[^0-9]/g, '')}`)
.find('div')
.attr('title', L)
.text(L)
}
})
})
_pageIps = []
}
// ~ 在线用户

View file

@ -221,7 +221,6 @@ class ReporEdit extends ConfigFormDlg {
if (this.props.id) {
post.isDisabled = this.state.isDisabled === true
this._save(post)
} else {
post.belongEntity = this.__select2.val()
post.templateFile = this.state.templateFile

View file

@ -19,14 +19,18 @@ $(document).ready(() => {
})
// UC
UCenter.query((bindAccount) => {
UCenter.query((res) => {
const bindAccount = res.bindAccount
$('.J_cloudAccount').removeClass('hide')
if (bindAccount) {
$('.J_not-bind').addClass('hide')
$('.J_has-bind a').text(bindAccount)
} else {
$('.J_has-bind').addClass('hide')
$('.J_not-bind .btn').on('click', () => UCenter.bind())
$('.J_not-bind .btn').on('click', () => {
if (res.canBind) UCenter.bind()
else RbHighbar.create($L('仅超级管理员可操作'))
})
}
})
})

View file

@ -125,9 +125,9 @@ $(document).ready(function () {
if (res.data.roleAppends && res.data.roleAppends.length > 0) {
$.get(`/commons/search/read-labels?ids=${res.data.roleAppends.join(',')}`, (res) => {
const $p = $('.J_roles').removeClass('hide').find('p')
const $p = $('.J_roleAppends').empty()
for (let k in res.data) {
$(`<a class="text-bold">${res.data[k]}</a>`).appendTo($p)
$(`<span class="badge badge-light up-2">${res.data[k]}</span>`).appendTo($p)
}
})
}

View file

@ -85,9 +85,8 @@ $(document).ready(() => {
$this.addClass('select')
render_option()
})
$('.chart-option .custom-control').on('click', function () {
render_option()
})
$('.chart-option .custom-control').on('click', () => render_option())
// 保存按钮
$('.rb-toggle-left-sidebar')
@ -377,12 +376,13 @@ const build_config = () => {
if (dims.length === 0 && nums.length === 0) return
cfg.axis = { dimension: dims, numerical: nums }
const opts = {}
const option = {}
$('.chart-option input').each(function () {
const name = $(this).data('name')
if (name) opts[name] = $val(this)
if (name) option[name] = $val(this)
})
cfg.option = opts
if (option.useColor === '#000000') delete option.useColor
cfg.option = option
if (dataFilter) cfg.filter = dataFilter
// eslint-disable-next-line no-console

View file

@ -16,7 +16,7 @@ class BaseChart extends React.Component {
}
render() {
const opers = (
const opActions = (
<div className="chart-oper">
{!this.props.builtin && (
<a title={$L('查看来源数据')} href={`${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`}>
@ -46,7 +46,7 @@ class BaseChart extends React.Component {
<div className={`chart-box ${this.props.type}`} ref={(c) => (this._$box = c)}>
<div className="chart-head">
<div className="chart-title text-truncate">{this.state.title}</div>
{opers}
{opActions}
</div>
<div ref={(c) => (this._$body = c)} className={`chart-body rb-loading ${!this.state.chartdata && 'rb-loading-active'}`}>
{this.state.chartdata || <RbSpinner />}
@ -79,7 +79,7 @@ class BaseChart extends React.Component {
resize() {
if (this._echarts) {
$setTimeout(() => this._echarts.resize(), 400, `resize-chart-${this.state.id || ''}`)
$setTimeout(() => this._echarts.resize(), 400, `resize-chart-${this.state.id}`)
}
}
@ -141,7 +141,7 @@ class ChartIndex extends BaseChart {
renderChart(data) {
const chartdata = (
<div className="chart index" ref={(c) => (this._chart = c)}>
<div className="chart index color" ref={(c) => (this._$chart = c)}>
<div className="data-item must-center text-truncate w-auto">
<p>{data.index.label || this.label}</p>
<a href={__PREVIEW ? null : `${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`}>
@ -154,16 +154,20 @@ class ChartIndex extends BaseChart {
}
resize() {
$setTimeout(() => this._resize(), 200, 'resize-chart-index')
$setTimeout(() => this._resize(), 200, `resize-chart-${this.props.id}`)
}
_resize() {
const ch = $(this._chart).height()
const $text = $(this._chart).find('strong')
let zoom = $(this._chart).width() / $text.width() / 3
if (zoom < 1 || ch < 120) zoom = 1
if (zoom > 2 && ch < 200) zoom = 2
$text.css('zoom', Math.min(zoom, 3))
const ch = $(this._$chart).height()
const zoom = ch > 100 ? 1.3 : 1
$(this._$chart).find('strong').css('zoom', zoom)
// const $text = $(this._$chart).find('strong')
// zoom = $(this._$chart).width() / $text.width()
// console.log(this.props.id, zoom)
// if (zoom < 1 || ch < 120) zoom = 1
// if (zoom > 2 && ch < 200) zoom = 2
// $text.css('zoom', Math.min(zoom, 3))
}
}
@ -218,7 +222,7 @@ class ChartTable extends BaseChart {
if (this._$tb) this._$tb.find('.ctable').css('height', this._$tb.height() - 20)
},
400,
'resize-chart-' + this.state.id
`resize-chart-${this.state.id}`
)
}
}
@ -248,6 +252,7 @@ const ECHART_BASE = {
textStyle: {
fontFamily: 'Roboto, "Hiragina Sans GB", San Francisco, "Helvetica Neue", Helvetica, Arial, PingFangSC-Light, "WenQuanYi Micro Hei", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif',
},
color: RBCOLORS,
}
const ECHART_AXIS_LABEL = {
@ -702,7 +707,7 @@ class ApprovalList extends BaseChart {
if (this._$tb) this._$tb.find('.ApprovalList').css('height', this._$tb.height() - 5)
},
400,
'resize-chart-' + this.state.id
`resize-chart-${this.state.id}`
)
}
@ -808,7 +813,7 @@ class FeedsSchedule extends BaseChart {
if (this._$tb) this._$tb.find('.FeedsSchedule').css('height', this._$tb.height() - 13)
},
400,
'resize-chart-' + this.state.id
`resize-chart-${this.state.id}`
)
}
@ -1113,6 +1118,7 @@ const detectChart = function (cfg, id) {
// isManageable = 图表可编辑
// editable = 仪表盘可编辑
const props = { config: cfg, id: id, title: cfg.title, type: cfg.type, isManageable: cfg.isManageable, editable: cfg.editable }
if (cfg.type === 'INDEX') {
return <ChartIndex {...props} />
} else if (cfg.type === 'TABLE') {

View file

@ -55,7 +55,7 @@ $(document).ready(function () {
RbHighbar.success($L('仪表盘已删除'))
location.hash = ''
} else {
const high = $('#chart-' + location.hash.substr(1)).addClass('high')
const high = $(`#chart-${location.hash.substr(1)}`).addClass('high')
if (high.length > 0) {
high.on('mouseleave', () => {
high.removeClass('high').off('mouseleave')
@ -228,13 +228,13 @@ const render_dashboard = function (init) {
}
const add_widget = function (item) {
const chid = 'chart-' + item.chart
if ($('#' + chid).length > 0) return false
const chid = `chart-${item.chart}`
if ($(`#${chid}`)[0]) return false // exsist
const chart_add = $('#chart-add')
if (chart_add.length > 0) gridstack.removeWidget(chart_add.parent())
const gsi = `<div class="grid-stack-item"><div id="${chid}" class="grid-stack-item-content"></div></div>`
const gsi = `<div class="grid-stack-item ${item.color && 'color'}"><div id="${chid}" class="grid-stack-item-content" ${item.color ? `style="background-color:${item.color}` : ''}"></div></div>`
// Use gridstar
if (item.size_x || item.size_y) {
gridstack.addWidget(gsi, (item.col || 1) - 1, (item.row || 1) - 1, item.size_x || 2, item.size_y || 2, true, 2, 12, 2, 24)
@ -309,7 +309,9 @@ class DlgAddChart extends RbFormHandler {
const $entity = $(this.refs['entity'])
$.get('/commons/metadata/entities?detail=true', (res) => {
$(res.data).each(function () {
$('<option value="' + this.name + '">' + this.label + '</option>').appendTo($entity)
if (!$isSysMask(this.label)) {
$(`<option value="${this.name}">${this.label}</option>`).appendTo($entity)
}
})
this.__select2 = $entity.select2({
allowClear: false,
@ -454,7 +456,7 @@ class DlgDashAdd extends RbFormHandler {
if (this.state.copy === true) _data.__copy = gridstack_serialize
$.post('/dashboard/dash-new', JSON.stringify(_data), (res) => {
if (res.error_code === 0) location.href = '?d=' + res.data.id
if (res.error_code === 0) location.href = `?d=${res.data.id}`
else RbHighbar.error(res.error_msg)
})
}

Some files were not shown because too many files have changed in this diff Show more