mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 07:25:54 +08:00
Fix 3.1.1 (#544)
* fix: checkInstalled and clearAllCache * enh: api * enh: rbcolor * fix: fieldParent del * enh: table chart mergeCell * J_menuUrl * enh: form _maximize * fix: query VF_ACU * fix: form getValue * fix: SK_DAVATAR * enh: copy detail * $empty * func: SQLQUERY * feat: ProxyTriggerAction * fix: CNVD-C-2022-425724 * fix: putx * data-v
This commit is contained in:
parent
c010f49c88
commit
34e9138faa
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit 017c99f7496cc86adb2e0b5cc4ca277f0dcc6d61
|
||||
Subproject commit f256231e8f0d67ee326d47b65ed9408a0ce9320f
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 3.x | :white_check_mark: |
|
||||
| 1.x | :x: |
|
||||
| 2.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `rebuild@ruifang-tech.com`
|
||||
Please report security issues to `rebuild@ruifang-tech.com`
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.1.1</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<!-- UNCOMMENT USE TOMCAT -->
|
||||
|
|
|
@ -156,7 +156,16 @@ public class ApiGateway extends Controller implements Initialization {
|
|||
|
||||
final ConfigBean apiConfig = RebuildApiManager.instance.getApp(appid);
|
||||
if (apiConfig == null) {
|
||||
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Invalid [appid] " + appid);
|
||||
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Invalid [appid] : " + appid);
|
||||
}
|
||||
|
||||
// v3.1.1
|
||||
final String bindIps = apiConfig.getString("bindIps");
|
||||
if (StringUtils.isNotBlank(bindIps)) {
|
||||
String clientIp = ServletUtils.getRemoteAddr(request);
|
||||
if (!bindIps.contains(clientIp)) {
|
||||
throw new ApiInvokeException(ApiInvokeException.ERR_BADAUTH, "Client ip not in whitelist : " + clientIp);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
|
|
|
@ -61,7 +61,7 @@ public class AuthTokenManager {
|
|||
System.nanoTime());
|
||||
String token = EncryptUtils.toSHA1Hex(desc);
|
||||
|
||||
Application.getCommonsCache().putx(TOKEN_PREFIX + token, desc, expires);
|
||||
Application.getCommonsCache().put(TOKEN_PREFIX + token, desc, expires);
|
||||
return token;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,11 +67,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "3.1.0";
|
||||
public static final String VER = "3.1.1";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 3010008;
|
||||
public static final int BUILD = 3010109;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -7,11 +7,14 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.configuration;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.persist4j.PersistManagerFactory;
|
||||
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.privileges.AdminGuard;
|
||||
import org.apache.commons.lang.math.RandomUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
|
@ -32,6 +35,20 @@ public class RebuildApiService extends BaseConfigurationService implements Admin
|
|||
return EntityHelper.RebuildApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record create(Record record) {
|
||||
record.setString("appId", (100000000 + RandomUtils.nextInt(899999999)) + "");
|
||||
record.setString("appSecret", CodecUtils.randomCode(40));
|
||||
return super.create(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record update(Record record) {
|
||||
record.removeValue("appId");
|
||||
record.removeValue("appSecret");
|
||||
return super.update(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanCache(ID cfgid) {
|
||||
Object[] cfg = Application.createQueryNoFilter(
|
||||
|
|
|
@ -678,6 +678,13 @@ public class FormsBuilder extends FormsManager {
|
|||
fieldParent = dtf.getName() + "." + pfs[0].split("\\.")[1];
|
||||
}
|
||||
|
||||
// v3.1.1 父级已删除
|
||||
Entity entity = MetadataHelper.getEntity(record.getEntityCode());
|
||||
if (MetadataHelper.getLastJoinField(entity, fieldParent) == null) {
|
||||
log.warn("Unknow field : {} in {}", fieldParent, entity.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(record, fieldParent);
|
||||
return o == null ? null : (ID) o[0];
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
final boolean isNew = record.getPrimary() == null;
|
||||
|
||||
// 明细关联主记录
|
||||
if (isNew && isDTF(field)) return true;
|
||||
if (isNew && isDtmField(field)) return true;
|
||||
|
||||
// 公共字段前台可能会布局出来
|
||||
// 此处忽略检查没问题,因为最后还会复写,即 #bindCommonsFieldsValue
|
||||
|
@ -107,7 +107,7 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
}
|
||||
} else {
|
||||
if (field.isCreatable()) {
|
||||
if (!matchsPattern(easyField, hasVal)) {
|
||||
if (!patternMatches(easyField, hasVal)) {
|
||||
notWells.add(easyField.getLabel());
|
||||
}
|
||||
} else {
|
||||
|
@ -135,7 +135,7 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
}
|
||||
} else {
|
||||
if (field.isUpdatable()) {
|
||||
if (!matchsPattern(easyField, hasVal)) {
|
||||
if (!patternMatches(easyField, hasVal)) {
|
||||
notWells.add(easyField.getLabel());
|
||||
}
|
||||
} else {
|
||||
|
@ -157,7 +157,7 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
}
|
||||
|
||||
// 明细关联主记录字段
|
||||
private boolean isDTF(Field field) {
|
||||
private boolean isDtmField(Field field) {
|
||||
if (field.getType() == FieldType.REFERENCE && entity.getMainEntity() != null) {
|
||||
return field.equals(MetadataHelper.getDetailToMainField(entity));
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
|
||||
// 强制可新建
|
||||
private boolean isForceCreateable(Field field) {
|
||||
if (isDTF(field)) return true;
|
||||
if (isDtmField(field)) return true;
|
||||
|
||||
// 自定定位
|
||||
EasyField easyField = EasyMetaFactory.valueOf(field);
|
||||
|
@ -178,7 +178,8 @@ public class EntityRecordCreator extends JsonRecordCreator {
|
|||
}
|
||||
|
||||
// 正则匹配
|
||||
private boolean matchsPattern(EasyField easyField, Object val) {
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean patternMatches(EasyField easyField, Object val) {
|
||||
if (!(easyField instanceof EasyText)) return true;
|
||||
|
||||
Pattern patt = ((EasyText) easyField).getPattern();
|
||||
|
|
|
@ -314,18 +314,17 @@ public class UserHelper {
|
|||
*/
|
||||
public static File generateAvatar(String name, boolean forceMake) {
|
||||
if (StringUtils.isBlank(name)) name = "RB";
|
||||
File avatarFile = RebuildConfiguration.getFileOfData("avatar-" + name + "29.jpg");
|
||||
if (avatarFile.exists()) {
|
||||
|
||||
File avatar = RebuildConfiguration.getFileOfData("avatar-" + name + "29.jpg");
|
||||
if (avatar.exists()) {
|
||||
if (forceMake) {
|
||||
FileUtils.deleteQuietly(avatarFile);
|
||||
FileUtils.deleteQuietly(avatar);
|
||||
} else {
|
||||
return avatarFile;
|
||||
return avatar;
|
||||
}
|
||||
}
|
||||
|
||||
if (name.length() > 2) {
|
||||
name = name.substring(name.length() - 2);
|
||||
}
|
||||
if (name.length() > 2) name = name.substring(name.length() - 2);
|
||||
name = name.toUpperCase();
|
||||
|
||||
BufferedImage bi = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
|
||||
|
@ -347,7 +346,7 @@ public class UserHelper {
|
|||
g2d.drawString("wbr", 0, 62);
|
||||
g2d.dispose();
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(avatarFile)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(avatar)) {
|
||||
ImageIO.write(bi, "png", fos);
|
||||
fos.flush();
|
||||
}
|
||||
|
@ -360,7 +359,7 @@ public class UserHelper {
|
|||
is = CommonsUtils.getStreamOfRes("/web" + DEFAULT_AVATAR);
|
||||
|
||||
bi = ImageIO.read(is);
|
||||
try (FileOutputStream fos = new FileOutputStream(avatarFile)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(avatar)) {
|
||||
ImageIO.write(bi, "png", fos);
|
||||
fos.flush();
|
||||
}
|
||||
|
@ -370,7 +369,7 @@ public class UserHelper {
|
|||
}
|
||||
}
|
||||
|
||||
return avatarFile;
|
||||
return avatar;
|
||||
}
|
||||
|
||||
private static Font createFont() {
|
||||
|
@ -408,7 +407,7 @@ public class UserHelper {
|
|||
* @see #sortUsers(boolean)
|
||||
*/
|
||||
public static User[] sortUsers() {
|
||||
return sortUsers(false);
|
||||
return sortUsers(Boolean.FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -144,11 +144,12 @@ public class ChartManager implements ConfigManager {
|
|||
}
|
||||
|
||||
ch.put("title", e.getString("title"));
|
||||
ch.put("type", e.getString("type"));
|
||||
String type = e.getString("type");
|
||||
ch.put("type", type);
|
||||
|
||||
try {
|
||||
String c = ((JSONObject) e.getJSON("config")).getJSONObject("option").getString("useColor");
|
||||
if (StringUtils.isNotBlank(c)) ch.put("color", c);
|
||||
if ("INDEX".equals(type) && StringUtils.isNotBlank(c)) ch.put("color", c);
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
if (user != null) {
|
||||
|
|
|
@ -90,33 +90,35 @@ public class TableBuilder {
|
|||
}
|
||||
|
||||
// 合并纬度单元格
|
||||
for (int i = 0; i < chart.getDimensions().length; i++) {
|
||||
// 行号
|
||||
if (chart.isShowLineNumber() && i == 0) continue;
|
||||
if (chart.isMergeCell()) {
|
||||
for (int i = 0; i < chart.getDimensions().length; i++) {
|
||||
// 行号
|
||||
if (chart.isShowLineNumber() && i == 0) continue;
|
||||
|
||||
TD last = null;
|
||||
int sumMinus = 0; // 合并的单元格
|
||||
int childrenLen = tbody.children.size();
|
||||
for (TR tr : tbody.children) {
|
||||
TD current = tr.children.get(i);
|
||||
if (last == null || childrenLen-- <= 1) {
|
||||
last = current;
|
||||
continue;
|
||||
TD last = null;
|
||||
int sumMinus = 0; // 合并的单元格
|
||||
int childrenLen = tbody.children.size();
|
||||
for (TR tr : tbody.children) {
|
||||
TD current = tr.children.get(i);
|
||||
if (last == null || childrenLen-- <= 1) {
|
||||
last = current;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last.content.equals(current.content)) {
|
||||
last.rowspan++;
|
||||
current.rowspan = 0;
|
||||
sumMinus++;
|
||||
} else {
|
||||
last = current;
|
||||
}
|
||||
}
|
||||
|
||||
if (last.content.equals(current.content)) {
|
||||
last.rowspan++;
|
||||
current.rowspan = 0;
|
||||
sumMinus++;
|
||||
} else {
|
||||
last = current;
|
||||
if (chart.isShowSums() && sumMinus > 0 && StringUtils.isNotBlank(last.content)) {
|
||||
int num = ObjectUtils.toInt(last.content) - sumMinus;
|
||||
last.content = String.valueOf(num);
|
||||
}
|
||||
}
|
||||
|
||||
if (chart.isShowSums() && sumMinus > 0 && StringUtils.isNotBlank(last.content)) {
|
||||
int num = ObjectUtils.toInt(last.content) - sumMinus;
|
||||
last.content = String.valueOf(num);
|
||||
}
|
||||
}
|
||||
|
||||
String tClazz = (chart.isShowLineNumber() ? "line-number " : "") + (chart.isShowSums() ? "sums" : "");
|
||||
|
|
|
@ -28,6 +28,7 @@ public class TableChart extends ChartData {
|
|||
|
||||
private boolean showLineNumber = false;
|
||||
private boolean showSums = false;
|
||||
private boolean mergeCell = true;
|
||||
|
||||
protected TableChart(JSONObject config) {
|
||||
super(config);
|
||||
|
@ -36,6 +37,7 @@ public class TableChart extends ChartData {
|
|||
if (option != null) {
|
||||
this.showLineNumber = option.getBooleanValue("showLineNumber");
|
||||
this.showSums = option.getBooleanValue("showSums");
|
||||
if (option.containsKey("mergeCell")) this.mergeCell = option.getBooleanValue("mergeCell");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +98,10 @@ public class TableChart extends ChartData {
|
|||
return showSums;
|
||||
}
|
||||
|
||||
protected boolean isMergeCell() {
|
||||
return mergeCell;
|
||||
}
|
||||
|
||||
protected String wrapSumValue(Axis sumAxis, Object value) {
|
||||
if (ChartsHelper.isZero(value)) {
|
||||
return ChartsHelper.VALUE_ZERO;
|
||||
|
|
|
@ -180,11 +180,11 @@ public class RecordCheckout {
|
|||
|
||||
// 支持ID
|
||||
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.ClassificationData) {
|
||||
ID iid = ID.valueOf(val);
|
||||
if (ClassificationManager.instance.getName(iid) != null) {
|
||||
return iid;
|
||||
ID cid = ID.valueOf(val);
|
||||
if (ClassificationManager.instance.getName(cid) != null) {
|
||||
return cid;
|
||||
} else {
|
||||
log.warn("No item of Classification found by ID : " + iid);
|
||||
log.warn("No item of Classification found by ID : " + cid);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -486,7 +486,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)",
|
||||
"(exists (select recordId from RobotApprovalStep where ^%s = recordId and state = 1 and isCanceled = 'F' and %s) and approvalState = 2)",
|
||||
specRootEntity.getPrimaryField().getName(), sb.toString().replace(VF_ACU, "approver"));
|
||||
} else {
|
||||
return sb.toString();
|
||||
|
|
|
@ -73,4 +73,9 @@ public class ActionContext {
|
|||
public ID getConfigId() {
|
||||
return configId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "#" + getConfigId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ public enum ActionType {
|
|||
AUTOASSIGN("自动分派", AutoAssign.class),
|
||||
SENDNOTIFICATION("发送通知", SendNotification.class),
|
||||
HOOKURL("回调 URL", "com.rebuild.rbv.trigger.HookUrl"),
|
||||
PROXYTRIGGERACTION("自定义触发器", "com.rebuild.rbv.trigger.ProxyTriggerAction"),
|
||||
|
||||
;
|
||||
|
||||
|
|
|
@ -49,9 +49,10 @@ public class AviatorUtils {
|
|||
addCustomFunction(new CurrentBizunitFunction());
|
||||
addCustomFunction(new CurrentDateFunction());
|
||||
addCustomFunction(new LocationDistanceFunction());
|
||||
addCustomFunction(new RequestFunctuin());
|
||||
addCustomFunction(new TextFunction());
|
||||
addCustomFunction(new ChineseYuanFunction());
|
||||
addCustomFunction(new TextFunction());
|
||||
addCustomFunction(new RequestFunctuin());
|
||||
addCustomFunction(new SqlQueryFunction());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*!
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
package com.rebuild.core.service.trigger.aviator;
|
||||
|
||||
import cn.devezhao.persist4j.Query;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.googlecode.aviator.runtime.function.AbstractFunction;
|
||||
import com.googlecode.aviator.runtime.type.AviatorDecimal;
|
||||
import com.googlecode.aviator.runtime.type.AviatorNil;
|
||||
import com.googlecode.aviator.runtime.type.AviatorObject;
|
||||
import com.googlecode.aviator.runtime.type.AviatorString;
|
||||
import com.rebuild.core.Application;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usage: SQLQUERY($sql, [ $param...{5} ])
|
||||
* Return: Number|Date|String
|
||||
*
|
||||
* @author RB
|
||||
* @since 2022/11/10
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlQueryFunction extends AbstractFunction {
|
||||
private static final long serialVersionUID = 6408510455471045309L;
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3, AviatorObject arg4, AviatorObject arg5, AviatorObject arg6) {
|
||||
String $sql = arg1.getValue(env).toString();
|
||||
Query query = Application.createQueryNoFilter($sql);
|
||||
|
||||
Object param1 = getValue(env, arg2);
|
||||
if (param1 != null) query.setParameter(1, param1);
|
||||
Object param2 = getValue(env, arg3);
|
||||
if (param2 != null) query.setParameter(2, param2);
|
||||
Object param3 = getValue(env, arg4);
|
||||
if (param3 != null) query.setParameter(3, param3);
|
||||
Object param4 = getValue(env, arg5);
|
||||
if (param4 != null) query.setParameter(4, param4);
|
||||
Object param5 = getValue(env, arg6);
|
||||
if (param5 != null) query.setParameter(5, param5);
|
||||
|
||||
Object[] o = query.unique();
|
||||
if (o == null || o[0] == null) return AviatorNil.NIL;
|
||||
|
||||
final Object value = o[0];
|
||||
|
||||
if (value instanceof Number) {
|
||||
return new AviatorDecimal((Number) value);
|
||||
} else if (value instanceof Date) {
|
||||
return new AviatorDate((Date) value);
|
||||
} else {
|
||||
return new AviatorString(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object getValue(Map<String, Object> env, AviatorObject arg) {
|
||||
Object value = arg.getValue(env);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof Number || value instanceof Date || value instanceof ID) return value;
|
||||
else return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3, AviatorObject arg4, AviatorObject arg5) {
|
||||
return this.call(env, arg1, arg2, arg3, arg4, arg5, AviatorNil.NIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3, AviatorObject arg4) {
|
||||
return this.call(env, arg1, arg2, arg3, arg4, AviatorNil.NIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2, AviatorObject arg3) {
|
||||
return this.call(env, arg1, arg2, arg3, AviatorNil.NIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
|
||||
return this.call(env, arg1, arg2, AviatorNil.NIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
|
||||
return this.call(env, arg1, AviatorNil.NIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SQLQUERY";
|
||||
}
|
||||
}
|
|
@ -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,9 +519,12 @@ public class Installer implements InstallState {
|
|||
public static void clearAllCache() {
|
||||
if (isUseRedis()) {
|
||||
try (Jedis jedis = Application.getCommonsCache().getJedisPool().getResource()) {
|
||||
// // Delete all the keys of the currently selected DB
|
||||
// jedis.flushDB(FlushMode.SYNC);
|
||||
jedis.flushAll();
|
||||
// https://redis.io/commands/flushdb/
|
||||
try {
|
||||
jedis.flushDB(FlushMode.SYNC); // v6.2.0
|
||||
} catch (Exception v620) {
|
||||
jedis.flushDB();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Application.getCommonsCache().getEhcacheCache().clear();
|
||||
|
|
|
@ -92,9 +92,7 @@ public class AppUtils {
|
|||
*/
|
||||
public static ID getRequestUser(HttpServletRequest request, boolean refreshToken) {
|
||||
Object user = request.getSession().getAttribute(WebUtils.CURRENT_USER);
|
||||
if (user == null) {
|
||||
user = getRequestUserViaToken(request, refreshToken);
|
||||
}
|
||||
if (user == null) user = getRequestUserViaToken(request, refreshToken);
|
||||
return user == null ? null : (ID) user;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public class CommonsUtils {
|
|||
private static final char[] SPECIAL_CHARS = "`~!@#$%^&*()_+=-{}|[];':\",./<>?".toCharArray();
|
||||
|
||||
/**
|
||||
* 不含特殊字符。不允许除 数字 字母 中文 及 _ - 以外的字符,包括空格
|
||||
* 不含特殊字符。不允许除 `数字` `字母` `中文` `_` `-` 及空格以外的字符
|
||||
*
|
||||
* @param text
|
||||
* @return
|
||||
|
|
|
@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.web.admin;
|
||||
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.support.ConfigurationItem;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.core.support.setup.DatabaseBackup;
|
||||
|
@ -64,7 +65,7 @@ public class AdminCLI2 {
|
|||
case C_HELP:
|
||||
case "?" : {
|
||||
result = " Usage : " +
|
||||
" \ncache clean" +
|
||||
" \ncache [clean|get] [KEY]" +
|
||||
" \nsyscfg NAME [VALUE]" +
|
||||
" \nsyscfg clean-qiniu|clean-sms|clean-email" +
|
||||
" \nbackup [database|datafile]" +
|
||||
|
@ -108,6 +109,12 @@ public class AdminCLI2 {
|
|||
String name = commands[1];
|
||||
if ("clean".equals(name)) {
|
||||
Installer.clearAllCache();
|
||||
} else if ("get".equals(name)) {
|
||||
if (commands.length < 3) return "WRAN: Bad arguments";
|
||||
String key = commands[2];
|
||||
Object value = Application.getCommonsCache().getx(key);
|
||||
if (value == null) result = "/NULL/";
|
||||
else result = value.toString();
|
||||
} else {
|
||||
result = "WRAN: Bad arguments";
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ import com.rebuild.core.configuration.RebuildApiService;
|
|||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.support.i18n.I18nUtils;
|
||||
import com.rebuild.web.BaseController;
|
||||
import org.apache.commons.lang.math.RandomUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
@ -39,10 +39,10 @@ public class ApisManagerController extends BaseController {
|
|||
return createModelAndView("/admin/integration/apis-manager");
|
||||
}
|
||||
|
||||
@RequestMapping("apis-manager/app-list")
|
||||
@GetMapping("apis-manager/app-list")
|
||||
public RespBody appList() {
|
||||
Object[][] apps = Application.createQueryNoFilter(
|
||||
"select uniqueId,appId,appSecret,bindUser,bindUser.fullName,createdOn,appId from RebuildApi")
|
||||
"select uniqueId,appId,appSecret,bindUser,bindUser.fullName,createdOn,appId,bindIps from RebuildApi order by createdOn")
|
||||
.array();
|
||||
|
||||
// 近30日用量
|
||||
|
@ -60,24 +60,15 @@ public class ApisManagerController extends BaseController {
|
|||
return RespBody.ok(apps);
|
||||
}
|
||||
|
||||
@RequestMapping("apis-manager/app-create")
|
||||
public RespBody appCreate(HttpServletRequest request) {
|
||||
ID user = getRequestUser(request);
|
||||
ID bindUser = getIdParameter(request, "bind");
|
||||
|
||||
Record record = EntityHelper.forNew(EntityHelper.RebuildApi, user);
|
||||
record.setString("appId", (100000000 + RandomUtils.nextInt(899999999)) + "");
|
||||
@PostMapping("apis-manager/reset-secret")
|
||||
public RespBody resetSecret(HttpServletRequest request) {
|
||||
ID appId = getIdParameterNotNull(request, "id");
|
||||
Record record = EntityHelper.forUpdate(appId, getRequestUser(request));
|
||||
record.setString("appSecret", CodecUtils.randomCode(40));
|
||||
record.setID("bindUser", bindUser);
|
||||
Application.getBean(RebuildApiService.class).create(record);
|
||||
Application.getCommonsService().update(record, false);
|
||||
|
||||
return RespBody.ok();
|
||||
}
|
||||
|
||||
@RequestMapping("apis-manager/app-delete")
|
||||
public RespBody appDelete(HttpServletRequest request) {
|
||||
ID id = getIdParameterNotNull(request, "id");
|
||||
Application.getBean(RebuildApiService.class).delete(id);
|
||||
// cache
|
||||
Application.getBean(RebuildApiService.class).update(record);
|
||||
return RespBody.ok();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import cn.devezhao.commons.web.ServletUtils;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.api.RespBody;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.RebuildException;
|
||||
import com.rebuild.core.support.License;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.core.support.setup.InstallState;
|
||||
|
@ -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;
|
||||
|
@ -50,13 +50,17 @@ import java.sql.SQLException;
|
|||
@RequestMapping("/setup/")
|
||||
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;
|
||||
@Override
|
||||
public boolean checkInstalled() {
|
||||
if (InstallState.super.checkInstalled() && Application.isReady()) {
|
||||
throw new RebuildException("NOT ALLOWED");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@GetMapping("install")
|
||||
public ModelAndView index(HttpServletRequest request) throws IOException {
|
||||
checkInstalled();
|
||||
ModelAndView mv = createModelAndView("/admin/setup/install");
|
||||
mv.getModel().put("Version", Application.VER);
|
||||
|
||||
|
@ -68,6 +72,7 @@ public class InstallController extends BaseController implements InstallState {
|
|||
|
||||
@PostMapping("test-connection")
|
||||
public RespBody testConnection(HttpServletRequest request) {
|
||||
checkInstalled();
|
||||
JSONObject dbProps = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
JSONObject props = JSONUtils.toJSONObject("databaseProps", dbProps);
|
||||
|
||||
|
@ -108,6 +113,7 @@ public class InstallController extends BaseController implements InstallState {
|
|||
|
||||
@PostMapping("test-cache")
|
||||
public RespBody testCache(HttpServletRequest request) {
|
||||
checkInstalled();
|
||||
JSONObject cacheProps = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
|
||||
JedisPool pool = new JedisPool(new JedisPoolConfig(),
|
||||
|
@ -133,6 +139,7 @@ public class InstallController extends BaseController implements InstallState {
|
|||
|
||||
@PostMapping("install-rebuild")
|
||||
public RespBody installExec(HttpServletRequest request) {
|
||||
checkInstalled();
|
||||
JSONObject installProps = (JSONObject) ServletUtils.getRequestJson(request);
|
||||
|
||||
try {
|
||||
|
@ -146,6 +153,7 @@ public class InstallController extends BaseController implements InstallState {
|
|||
|
||||
@GetMapping("request-sn")
|
||||
public RespBody requestSn() {
|
||||
checkInstalled();
|
||||
return RespBody.ok(License.SN());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,15 @@ import cn.devezhao.persist4j.Record;
|
|||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.api.user.AuthTokenManager;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.cache.CommonsCache;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.privileges.bizz.User;
|
||||
import com.rebuild.core.support.KVStorage;
|
||||
import com.rebuild.core.support.License;
|
||||
import com.rebuild.core.support.task.TaskExecutors;
|
||||
import com.rebuild.utils.AES;
|
||||
import com.rebuild.web.BaseController;
|
||||
import com.rebuild.web.user.UserAvatar;
|
||||
import eu.bitwalker.useragentutils.DeviceType;
|
||||
import eu.bitwalker.useragentutils.OperatingSystem;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
|
@ -49,7 +50,8 @@ public class LoginAction extends BaseController {
|
|||
protected static final String SK_NEED_VCODE = "needLoginVCode";
|
||||
protected static final String SK_START_TOUR = "needStartTour";
|
||||
|
||||
protected static final String PREFIX_2FA = "2FA";
|
||||
protected static final String PREFIX_2FA = "2FA:";
|
||||
protected static final String PREFIX_ALT = "ALT:";
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
|
@ -63,8 +65,9 @@ public class LoginAction extends BaseController {
|
|||
protected Integer loginSuccessed(HttpServletRequest request, HttpServletResponse response, ID user, boolean autoLogin) {
|
||||
// 自动登录
|
||||
if (autoLogin) {
|
||||
String alt = user + "," + System.currentTimeMillis() + ",v1";
|
||||
ServletUtils.addCookie(response, CK_AUTOLOGIN, AES.encrypt(alt));
|
||||
final String altToken = CodecUtils.randomCode(60);
|
||||
Application.getCommonsCache().putx(PREFIX_ALT + altToken, user, CommonsCache.TS_DAY * 14);
|
||||
ServletUtils.addCookie(response, CK_AUTOLOGIN, altToken);
|
||||
} else {
|
||||
ServletUtils.removeCookie(request, response, CK_AUTOLOGIN);
|
||||
}
|
||||
|
@ -75,6 +78,9 @@ public class LoginAction extends BaseController {
|
|||
ServletUtils.setSessionAttribute(request, SK_USER_THEME, KVStorage.getCustomValue("THEME." + user));
|
||||
Application.getSessionStore().storeLoginSuccessed(request);
|
||||
|
||||
// 头像缓存
|
||||
ServletUtils.setSessionAttribute(request, UserAvatar.SK_DAVATAR, System.currentTimeMillis());
|
||||
|
||||
// TOUR 显示规则
|
||||
Object[] initLoginTimes = Application.createQueryNoFilter(
|
||||
"select count(loginTime) from LoginLog where user = ? and loginTime > '2022-01-01'")
|
||||
|
|
|
@ -8,7 +8,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.web.user.signup;
|
||||
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.commons.ObjectUtils;
|
||||
import cn.devezhao.commons.RegexUtils;
|
||||
import cn.devezhao.commons.web.ServletUtils;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
|
@ -28,9 +27,7 @@ import com.rebuild.core.service.DataSpecificationException;
|
|||
import com.rebuild.core.support.*;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.core.support.integration.SMSender;
|
||||
import com.rebuild.utils.AES;
|
||||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.web.user.UserAvatar;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -85,28 +82,9 @@ public class LoginController extends LoginAction {
|
|||
// 记住登录
|
||||
final String useAlt = ServletUtils.readCookie(request, CK_AUTOLOGIN);
|
||||
if (StringUtils.isNotBlank(useAlt)) {
|
||||
ID altUser = null;
|
||||
try {
|
||||
String[] alts = AES.decrypt(useAlt).split(",");
|
||||
altUser = alts.length == 3 && ID.isId(alts[0]) ? ID.valueOf(alts[0]) : null;
|
||||
final ID altUser = (ID) Application.getCommonsCache().getx(PREFIX_ALT + useAlt);
|
||||
|
||||
// 最大30天有效期
|
||||
if (altUser != null) {
|
||||
long t = ObjectUtils.toLong(alts[1]);
|
||||
if ((System.currentTimeMillis() - t) / 1000 > 30 * 24 * 60 * 60) {
|
||||
altUser = null;
|
||||
}
|
||||
if (altUser != null && !UserHelper.isActive(altUser)) {
|
||||
altUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
ServletUtils.removeCookie(request, response, CK_AUTOLOGIN);
|
||||
log.error("Cannot decode User from alt : {}", useAlt, ex);
|
||||
}
|
||||
|
||||
if (altUser != null && Application.getUserStore().existsUser(altUser)) {
|
||||
if (altUser != null && UserHelper.isActive(altUser)) {
|
||||
Integer ed = loginSuccessed(request, response, altUser, true);
|
||||
|
||||
String nexturl = getParameter(request, "nexturl", homeUrl);
|
||||
|
@ -183,8 +161,6 @@ public class LoginController extends LoginAction {
|
|||
// 清理验证码
|
||||
getLoginRetryTimes(user, -1);
|
||||
ServletUtils.setSessionAttribute(request, SK_NEED_VCODE, null);
|
||||
// 头像缓存
|
||||
ServletUtils.setSessionAttribute(request, UserAvatar.SK_DAVATAR, System.currentTimeMillis());
|
||||
|
||||
final User loginUser = Application.getUserStore().getUser(user);
|
||||
final boolean isRbMobile = AppUtils.isRbMobile(request);
|
||||
|
@ -197,8 +173,8 @@ public class LoginController extends LoginAction {
|
|||
if (faMode > 0 && !faModeSkip) {
|
||||
resMap.put("login2FaMode", faMode);
|
||||
|
||||
String userToken = CodecUtils.randomCode(40);
|
||||
Application.getCommonsCache().putx(PREFIX_2FA + userToken, loginUser.getId(), 15 * 60); // 15m
|
||||
final String userToken = CodecUtils.randomCode(40);
|
||||
Application.getCommonsCache().putx(PREFIX_2FA + userToken, loginUser.getId(), CommonsCache.TS_MINTE * 15);
|
||||
resMap.put("login2FaUserToken", userToken);
|
||||
|
||||
if (isRbMobile) {
|
||||
|
|
|
@ -2369,5 +2369,15 @@
|
|||
"批量添加字段":"批量添加字段",
|
||||
"支持 H5":"支持 H5",
|
||||
"手机登录":"手机登录",
|
||||
"批量添加":"批量添加"
|
||||
"批量添加":"批量添加",
|
||||
"触发类 (TriggerAction)":"触发类 (TriggerAction)",
|
||||
"无 (不限制)":"无 (不限制)",
|
||||
"重置 APP SECRET":"重置 APP SECRET",
|
||||
"自定义触发器":"自定义触发器",
|
||||
"白名单内的 IP 才可以通过此 API 秘钥调用接口,如有多个 IP 请使用逗号或空格分开,留空则不限制":"白名单内的 IP 才可以通过此 API 秘钥调用接口,如有多个 IP 请使用逗号或空格分开,留空则不限制",
|
||||
"配色":"配色",
|
||||
"APP SECRET 已重置":"APP SECRET 已重置",
|
||||
"修改 API 秘钥":"修改 API 秘钥",
|
||||
"自动合并单元格":"自动合并单元格",
|
||||
"重置后第三方应用需更换新的 APP SECRET 使用":"重置后第三方应用需更换新的 APP SECRET 使用"
|
||||
}
|
||||
|
|
|
@ -27,12 +27,13 @@
|
|||
<table class="table table-hover table-striped table-fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">APP ID</th>
|
||||
<th width="30%">APP SECRET</th>
|
||||
<th>[[${bundle.L('绑定用户 (权限)')}]]</th>
|
||||
<th>[[${bundle.L('调用量 (30 天)')}]]</th>
|
||||
<th width="12%">APP ID</th>
|
||||
<th>APP SECRET</th>
|
||||
<th width="12%">[[${bundle.L('绑定用户 (权限)')}]]</th>
|
||||
<th>[[${bundle.L('IP 白名单')}]]</th>
|
||||
<th width="12%">[[${bundle.L('调用量 (30 天)')}]]</th>
|
||||
<th width="120">[[${bundle.L('创建时间')}]]</th>
|
||||
<th width="50"></th>
|
||||
<th width="120"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="appList"></tbody>
|
||||
|
@ -57,6 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<th:block th:replace="~{/_include/footer}" />
|
||||
<script th:src="@{/assets/js/admin/config-comps.js}" type="text/babel"></script>
|
||||
<script th:src="@{/assets/js/admin/apis-manager.js}" type="text/babel"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -17,35 +17,8 @@
|
|||
.unset-list .dd-item:hover a.action {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.colors {
|
||||
line-height: 1;
|
||||
font-size: 0;
|
||||
}
|
||||
.colors > a {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
background-color: #ccc;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
font-size: 1.3rem;
|
||||
padding-top: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.colors > a:hover {
|
||||
opacity: 0.8;
|
||||
color: rgb(117, 0, 234);
|
||||
}
|
||||
.colors > a + a {
|
||||
margin-left: 9px;
|
||||
}
|
||||
.colors > a > i {
|
||||
color: #fff !important;
|
||||
}
|
||||
.colors > a:first-child > i {
|
||||
color: #aaa !important;
|
||||
.rbcolors > a:first-child > i {
|
||||
color: #777 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
@ -64,7 +37,7 @@
|
|||
<button class="btn btn-secondary J_confirm" type="submit" style="min-width: 0">[[${bundle.L('添加')}]]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="colors mt-2">
|
||||
<div class="rbcolors mt-2">
|
||||
<a style="background-color: #fff; border: 1px solid #ccc" th:title="${bundle.L('默认')}"></a>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -429,3 +429,11 @@ a.ui-draggable.ui-draggable-dragging {
|
|||
.chart-type > a.active > i.C243 {
|
||||
background-position: -1290px -1288px;
|
||||
}
|
||||
|
||||
.rbcolors > a:first-child > i {
|
||||
color: #777 !important;
|
||||
}
|
||||
|
||||
.chart-option label {
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -122,6 +122,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
font-size: 1.1rem;
|
||||
margin-bottom: 1px;
|
||||
opacity: 0.8;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.chart.index > .data-item strong {
|
||||
|
@ -129,6 +130,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
font-weight: 400;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen .chart.index > .data-item p {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.grid-stack-item.fullscreen .chart.index > .data-item strong {
|
||||
font-size: 8.8rem;
|
||||
zoom: 1 !important;
|
||||
}
|
||||
|
||||
.chart.index > .data-item a {
|
||||
color: #404040;
|
||||
}
|
||||
|
@ -453,17 +463,30 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
/* useColor */
|
||||
/* Colors */
|
||||
|
||||
.grid-stack-item.color .grid-stack-item-content,
|
||||
.grid-stack-item.color .chart.index > .data-item strong {
|
||||
.grid-stack-item.bgcolor .grid-stack-item-content,
|
||||
.grid-stack-item.bgcolor .chart.index > .data-item strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid-stack-item.color .rb-loading:after {
|
||||
.grid-stack-item.bgcolor .rb-loading:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.grid-stack-item.color .rb-spinner svg {
|
||||
.grid-stack-item.bgcolor .rb-spinner svg {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
body.fullscreen {
|
||||
background: #0a1b3b url(../img/datav-bg.png) no-repeat 0 0;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
body.fullscreen .grid-stack .grid-stack-item .grid-stack-item-content {
|
||||
background-color: rgba(6, 30, 93, 0.5);
|
||||
}
|
||||
|
||||
body.fullscreen .grid-stack .grid-stack-item.fullscreen .grid-stack-item-content {
|
||||
background-color: #0a1b3b;
|
||||
}
|
|
@ -4979,3 +4979,34 @@ pre.unstyle {
|
|||
.modal-title > .support-plat2 {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.rbcolors {
|
||||
line-height: 1;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.rbcolors > a {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
background-color: #ccc;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
font-size: 1.3rem;
|
||||
padding-top: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rbcolors > a:hover {
|
||||
opacity: 0.8;
|
||||
color: rgb(117, 0, 234);
|
||||
}
|
||||
|
||||
.rbcolors > a + a {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.rbcolors > a > i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
|
|
@ -194,6 +194,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
padding: 4px 9px 0;
|
||||
margin-right: 4px;
|
||||
margin-top: 1px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.task-tags > span.tag-value > a {
|
||||
|
@ -219,12 +220,13 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
.task-tags a.tag-add {
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
padding: 2px;
|
||||
font-size: 1.231rem;
|
||||
background-color: #eee;
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.task-tags a.tag-add:hover {
|
||||
|
@ -249,28 +251,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tags-editor .colors > a {
|
||||
.tags-editor .rbcolors > a {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: inline-block;
|
||||
background-color: #ccc;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
font-size: 1.3rem;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.tags-editor .colors > a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tags-editor .colors > a + a {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.tags-editor .colors > a > i {
|
||||
color: #fff !important;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.dropdown-menu.tags {
|
||||
|
@ -326,6 +310,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
padding-left: 4px;
|
||||
font-size: 1.25rem;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tags-list .dropdown-item:hover > a {
|
||||
|
|
BIN
src/main/resources/web/assets/img/datav-bg.png
Normal file
BIN
src/main/resources/web/assets/img/datav-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
|
@ -4,41 +4,50 @@ Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights re
|
|||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
/* global dlgActionAfter */
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.J_add').on('click', () => renderRbcomp(<DlgEdit />))
|
||||
$('.J_add').on('click', () => renderRbcomp(<AppEdit />))
|
||||
renderRbcomp(<AppList />, 'appList')
|
||||
})
|
||||
|
||||
class AppList extends React.Component {
|
||||
class AppList extends ConfigList {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { ...props, secretShows: [] }
|
||||
this.requestUrl = '/admin/apis-manager/app-list'
|
||||
this.state.secretShows = []
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(this.state.list || []).map((item) => {
|
||||
let secret = item[2].substr(0, 8) + '...' + item[2].substr(32)
|
||||
{(this.state.data || []).map((item) => {
|
||||
let secret = `${item[2].substr(0, 8)}...${item[2].substr(32)}`
|
||||
secret = (
|
||||
<a href="#" title={$L('点击显示')} onClick={() => this.showSecret(item[2])}>
|
||||
<a href="###" title={$L('点击显示')} onClick={(e) => this.showSecret(e, item[2])}>
|
||||
{secret}
|
||||
</a>
|
||||
)
|
||||
if (this.state.secretShows.includes(item[2])) secret = item[2]
|
||||
|
||||
return (
|
||||
<tr key={'api-' + item[0]}>
|
||||
<tr key={item[0]}>
|
||||
<td>{item[1]}</td>
|
||||
<td>{secret}</td>
|
||||
<td>{item[4] || $L('无 (拥有全部权限)')}</td>
|
||||
<td>{item[7] || $L('无 (不限制)')}</td>
|
||||
<td>{item[6] || 0}</td>
|
||||
<td>
|
||||
<DateShow date={item[5]} />
|
||||
</td>
|
||||
<td className="actions">
|
||||
<a className="icon danger-hover" onClick={() => this.delete(item)}>
|
||||
<a className="icon" title={$L('重置 APP SECRET')} onClick={() => this.resetSecret(item[0])}>
|
||||
<i className="mdi mdi-lock-reset" />
|
||||
</a>
|
||||
<a className="icon" title={$L('修改')} onClick={() => this.handleEdit(item)}>
|
||||
<i className="zmdi zmdi-edit" />
|
||||
</a>
|
||||
<a className="icon danger-hover" title={$L('删除')} onClick={() => this.handleDelete(item)}>
|
||||
<i className="zmdi zmdi-delete" />
|
||||
</a>
|
||||
</td>
|
||||
|
@ -49,84 +58,85 @@ class AppList extends React.Component {
|
|||
)
|
||||
}
|
||||
|
||||
componentDidMount = () => this._componentDidMount()
|
||||
|
||||
_componentDidMount() {
|
||||
$.get('/admin/apis-manager/app-list', (res) => {
|
||||
const _data = res.data || []
|
||||
this.setState({ list: _data }, () => {
|
||||
$('.rb-loading-active').removeClass('rb-loading-active')
|
||||
$('.dataTables_info').text($L('共 %d 项', _data.length))
|
||||
if (_data.length === 0) $('.list-nodata').removeClass('hide')
|
||||
})
|
||||
})
|
||||
handleEdit(app) {
|
||||
renderRbcomp(<AppEdit id={app[0]} bindIps={app[7]} bindUser={app[3]} />)
|
||||
}
|
||||
|
||||
showSecret(s) {
|
||||
event.preventDefault()
|
||||
const shows = this.state.secretShows
|
||||
shows.push(s)
|
||||
this.setState({ secretShows: shows })
|
||||
}
|
||||
|
||||
delete(app) {
|
||||
const that = this
|
||||
handleDelete(app) {
|
||||
const handle = super.handleDelete
|
||||
RbAlert.create($L('删除后,使用此 API 秘钥的第三方应用功能将会失败'), {
|
||||
type: 'danger',
|
||||
confirmText: $L('删除'),
|
||||
confirm: function () {
|
||||
onConfirm: function () {
|
||||
this.disabled(true)
|
||||
$.post(`/admin/apis-manager/app-delete?id=${app[0]}`, (res) => {
|
||||
if (res.error_code === 0) {
|
||||
RbHighbar.success($L('删除成功'))
|
||||
that._componentDidMount()
|
||||
this.hide()
|
||||
} else {
|
||||
RbHighbar.error(res.error_msg)
|
||||
}
|
||||
handle(app[0], () => dlgActionAfter(this))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
resetSecret(id) {
|
||||
RbAlert.create($L('重置后第三方应用需更换新的 APP SECRET 使用'), {
|
||||
type: 'danger',
|
||||
confirmText: $L('重置'),
|
||||
onConfirm: function () {
|
||||
this.disabled(true)
|
||||
$.post(`/admin/apis-manager/reset-secret?id=${id}`, () => {
|
||||
RbHighbar.success($L('APP SECRET 已重置'))
|
||||
dlgActionAfter(this)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
showSecret(e, s) {
|
||||
$stopEvent(e, true)
|
||||
const shows = this.state.secretShows
|
||||
shows.push(s)
|
||||
this.setState({ secretShows: shows })
|
||||
}
|
||||
}
|
||||
|
||||
class DlgEdit extends RbFormHandler {
|
||||
class AppEdit extends ConfigFormDlg {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { ...props }
|
||||
this.title = props.id ? $L('修改 API 秘钥') : $L('添加 API 秘钥')
|
||||
}
|
||||
|
||||
render() {
|
||||
renderFrom() {
|
||||
return (
|
||||
<RbModal title={$L('添加 API 秘钥')} ref={(c) => (this._dlg = c)}>
|
||||
<div className="form">
|
||||
<div className="form-group row">
|
||||
<label className="col-sm-3 col-form-label text-sm-right">{$L('绑定用户 (权限)')}</label>
|
||||
<div className="col-sm-7">
|
||||
<UserSelector hideDepartment={true} hideRole={true} hideTeam={true} multiple={false} ref={(c) => (this._UserSelector = c)} />
|
||||
<p className="form-text mb-0">{$L('强烈建议为 API 秘钥绑定一个用户,此秘钥将拥有和其一样的权限。如不绑定则拥有全部权限')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group row footer">
|
||||
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}>
|
||||
<button className="btn btn-primary" type="button" onClick={this.save}>
|
||||
{$L('确定')}
|
||||
</button>
|
||||
<a className="btn btn-link" onClick={this.hide}>
|
||||
{$L('取消')}
|
||||
</a>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<div className="form-group row">
|
||||
<label className="col-sm-3 col-form-label text-sm-right">{$L('绑定用户 (权限)')}</label>
|
||||
<div className="col-sm-7">
|
||||
<UserSelector hideDepartment hideRole hideTeam multiple={false} ref={(c) => (this._UserSelector = c)} defaultValue={this.props.bindUser} />
|
||||
<p className="form-text">{$L('强烈建议为 API 秘钥绑定一个用户,此秘钥将拥有和其一样的权限。如不绑定则拥有全部权限')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</RbModal>
|
||||
<div className="form-group row">
|
||||
<label className="col-sm-3 col-form-label text-sm-right">{$L('IP 白名单')}</label>
|
||||
<div className="col-sm-7">
|
||||
<textarea className="form-control form-control-sm row2x" ref={(c) => (this._$bindIps = c)} defaultValue={this.props.bindIps} placeholder={$L('(可选)')} />
|
||||
<p className="form-text">{$L('白名单内的 IP 才可以通过此 API 秘钥调用接口,如有多个 IP 请使用逗号或空格分开,留空则不限制')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
save = () => {
|
||||
const bindUser = this._UserSelector.val()
|
||||
confirm = () => {
|
||||
const post = {
|
||||
bindUser: (this._UserSelector.val() || [])[0] || null,
|
||||
bindIps: $val(this._$bindIps) || null,
|
||||
}
|
||||
|
||||
post.metadata = {
|
||||
entity: 'RebuildApi',
|
||||
id: this.props.id || null,
|
||||
}
|
||||
|
||||
this.disabled(true)
|
||||
$.post(`/admin/apis-manager/app-create?bind=${bindUser || ''}`, (res) => {
|
||||
if (res.error_code === 0) location.reload()
|
||||
$.post('/app/entity/common-save', JSON.stringify(post), (res) => {
|
||||
if (res.error_code === 0) dlgActionAfter(this)
|
||||
else RbHighbar.error(res.error_msg)
|
||||
this.disabled()
|
||||
})
|
||||
|
|
|
@ -68,7 +68,8 @@ $(document).ready(() => {
|
|||
}
|
||||
|
||||
let _AdvFilter
|
||||
$('.J_filter').on('click', () => {
|
||||
$('.J_filter').on('click', (e) => {
|
||||
$stopEvent(e, true)
|
||||
if (_AdvFilter) {
|
||||
_AdvFilter.show()
|
||||
} else {
|
||||
|
@ -116,6 +117,17 @@ $(document).ready(() => {
|
|||
.find('.zmdi')
|
||||
.addClass('zmdi-arrow-left')
|
||||
|
||||
// Colors
|
||||
const $cs = $('.rbcolors')
|
||||
RBCOLORS.forEach((c) => {
|
||||
$(`<a style="background-color:${c}" data-color="${c}"></a>`).appendTo($cs)
|
||||
})
|
||||
$cs.find('>a').on('click', function () {
|
||||
$cs.find('>a .zmdi').remove()
|
||||
$('<i class="zmdi zmdi-check"></i>').appendTo(this)
|
||||
render_preview()
|
||||
})
|
||||
|
||||
// Load
|
||||
if (wpc.chartConfig && wpc.chartConfig.axis) {
|
||||
$(wpc.chartConfig.axis.dimension).each((idx, item) => add_axis('.J_axis-dim', item))
|
||||
|
@ -137,6 +149,10 @@ $(document).ready(() => {
|
|||
opt.val(option[k])
|
||||
}
|
||||
}
|
||||
|
||||
if (k === 'useColor') {
|
||||
$cs.find(`a[data-color="${option[k]}"]`).trigger('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +397,10 @@ const build_config = () => {
|
|||
const name = $(this).data('name')
|
||||
if (name) option[name] = $val(this)
|
||||
})
|
||||
if (option.useColor === '#000000') delete option.useColor
|
||||
|
||||
const color = $('.rbcolors >a>i').parent().data('color') || ''
|
||||
option.useColor = color || ''
|
||||
|
||||
cfg.option = option
|
||||
|
||||
if (dataFilter) cfg.filter = dataFilter
|
||||
|
|
|
@ -140,12 +140,15 @@ class ChartIndex extends BaseChart {
|
|||
}
|
||||
|
||||
renderChart(data) {
|
||||
const color = __PREVIEW ? this.props.config.option.useColor : this.props.config.color
|
||||
const style2 = { color: color || null }
|
||||
|
||||
const chartdata = (
|
||||
<div className="chart index color" ref={(c) => (this._$chart = c)}>
|
||||
<div className="chart index" ref={(c) => (this._$chart = c)}>
|
||||
<div className="data-item must-center text-truncate w-auto">
|
||||
<p>{data.index.label || this.label}</p>
|
||||
<p style={style2}>{data.index.label || this.label}</p>
|
||||
<a href={__PREVIEW ? null : `${rb.baseUrl}/dashboard/view-chart-source?id=${this.props.id}`}>
|
||||
<strong>{data.index.data}</strong>
|
||||
<strong style={style2}>{data.index.data}</strong>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159,7 +162,7 @@ class ChartIndex extends BaseChart {
|
|||
|
||||
_resize() {
|
||||
const ch = $(this._$chart).height()
|
||||
const zoom = ch > 100 ? 1.3 : 1
|
||||
const zoom = ch > 100 ? (ch > 330 ? 2.831 : 1.3) : 1
|
||||
$(this._$chart).find('strong').css('zoom', zoom)
|
||||
|
||||
// const $text = $(this._$chart).find('strong')
|
||||
|
|
|
@ -234,7 +234,7 @@ const add_widget = function (item) {
|
|||
const chart_add = $('#chart-add')
|
||||
if (chart_add.length > 0) gridstack.removeWidget(chart_add.parent())
|
||||
|
||||
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>`
|
||||
const gsi = `<div class="grid-stack-item ${item.bgcolor && 'bgcolor'}"><div id="${chid}" class="grid-stack-item-content" ${item.bgcolor ? `style="background-color:${item.bgcolor}` : ''}"></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)
|
||||
|
|
|
@ -6,8 +6,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
*/
|
||||
/* global InitModels */
|
||||
|
||||
$(document).ready(function () {
|
||||
const $new = $('.btn-primary.new').on('click', function () {
|
||||
$(document).ready(() => {
|
||||
const $bnew = $('.btn-primary.new').on('click', () => {
|
||||
const entityLabel = $val('#entityLabel'),
|
||||
comments = $val('#comments')
|
||||
if (!entityLabel) return RbHighbar.create($L('请输入实体名称'))
|
||||
|
@ -22,14 +22,14 @@ $(document).ready(function () {
|
|||
if (!data.mainEntity) return RbHighbar.create($L('请选择主实体'))
|
||||
}
|
||||
|
||||
$new.button('loading')
|
||||
$bnew.button('loading')
|
||||
$.post(`/admin/entity/entity-new?nameField=${$val('#nameField')}&seriesField=${$val('#seriesField')}`, JSON.stringify(data), (res) => {
|
||||
if (res.error_code === 0) parent.location.href = `${rb.baseUrl}/admin/entity/${res.data}/base`
|
||||
else RbHighbar.error(res.error_msg)
|
||||
})
|
||||
})
|
||||
|
||||
const $copy = $('.btn-primary.copy').on('click', () => {
|
||||
const $bcopy = $('.btn-primary.copy').on('click', () => {
|
||||
const sourceEntity = $val('#copySourceEntity')
|
||||
if (!sourceEntity) return RbHighbar.create($L('请选择从哪个实体复制'))
|
||||
|
||||
|
@ -43,7 +43,7 @@ $(document).ready(function () {
|
|||
keepConfig: [],
|
||||
}
|
||||
|
||||
$copy.button('loading')
|
||||
$bcopy.button('loading')
|
||||
$.post('/admin/entity/entity-copy', JSON.stringify(data), (res) => {
|
||||
if (res.error_code === 0) parent.location.href = `${rb.baseUrl}/admin/entity/${res.data}/base`
|
||||
else RbHighbar.error(res.error_msg)
|
||||
|
@ -51,19 +51,19 @@ $(document).ready(function () {
|
|||
})
|
||||
|
||||
let entities
|
||||
function _loadEntities(call) {
|
||||
function _loadEntities(c) {
|
||||
if (entities) {
|
||||
typeof call === 'function' && call(entities)
|
||||
typeof c === 'function' && c(entities)
|
||||
} else {
|
||||
$.get('/admin/entity/entity-list', (res) => {
|
||||
$.get('/admin/entity/entity-list?detail=true', (res) => {
|
||||
entities = res.data
|
||||
typeof call === 'function' && call(entities)
|
||||
typeof c === 'function' && c(entities)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_loadEntities((e) => {
|
||||
e.forEach(function (item) {
|
||||
e.forEach((item) => {
|
||||
$(`<option value="${item.entityName}" data-detail="${item.detailEntity || ''}">${item.entityLabel}</option>`).appendTo('#copySourceEntity')
|
||||
})
|
||||
|
||||
|
@ -80,17 +80,19 @@ $(document).ready(function () {
|
|||
if (e.length === 0) $(`<option value="">${$L('无可用实体')}</option>`).appendTo('#copySourceEntity')
|
||||
})
|
||||
|
||||
$('#isDetail').on('click', function () {
|
||||
$('#isDetail').on('click', () => {
|
||||
$('.J_mainEntity').toggleClass('hide')
|
||||
parent.RbModal.resize()
|
||||
|
||||
if ($('#mainEntity option').length === 0) {
|
||||
_loadEntities((e) => {
|
||||
e.forEach(function (item) {
|
||||
if (!item.detailEntity) $(`<option value="${item.entityName}">${item.entityLabel}</option>`).appendTo('#mainEntity')
|
||||
e.forEach((item) => {
|
||||
if (!item.detailEntity && !item.mainEntity) {
|
||||
$(`<option value="${item.entityName}">${item.entityLabel}</option>`).appendTo('#mainEntity')
|
||||
}
|
||||
})
|
||||
|
||||
if (e.length === 0) $(`<option value="">${$L('无可用实体')}</option>`).appendTo('#mainEntity')
|
||||
if ($('#mainEntity option').length === 0) $(`<option value="">${$L('无可用实体')}</option>`).appendTo('#mainEntity')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -81,7 +81,7 @@ $(document).ready(function () {
|
|||
if (dt === 'REFERENCE' || dt === 'N2NREFERENCE') {
|
||||
if (referenceLoaded === false) {
|
||||
referenceLoaded = true
|
||||
$.get('/admin/entity/entity-list?detail=true&bizz=false', (res) => {
|
||||
$.get('/admin/entity/entity-list?detail=true', (res) => {
|
||||
const d = res.data || []
|
||||
d.push({ entityName: 'User', entityLabel: $L('用户') })
|
||||
d.push({ entityName: 'Department', entityLabel: $L('部门') })
|
||||
|
|
|
@ -235,7 +235,7 @@ const render_item = function (data) {
|
|||
fieldLabelOld: $item.find('.dd-handle').data('label'),
|
||||
fieldHeight: $item.find('.dd-handle').attr('data-height') || null,
|
||||
}
|
||||
if (ov.fieldLabelOld === ov.fieldLabel) ov.fieldLabel = null
|
||||
// if (ov.fieldLabelOld === ov.fieldLabel) ov.fieldLabel = null
|
||||
|
||||
renderRbcomp(<DlgEditField onConfirm={_onConfirm} {...ov} displayType={data.displayTypeName} />)
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ const maxOptions = isMulti ? 20 : 100
|
|||
$(document).ready(function () {
|
||||
const query = `entity=${$urlp('entity')}&field=${$urlp('field')}`
|
||||
|
||||
const $cs = $('.colors')
|
||||
const $cs = $('.rbcolors')
|
||||
RBCOLORS.forEach((c) => {
|
||||
$(`<a style="background-color:${c}" data-color="${c}"></a>`).appendTo($cs)
|
||||
})
|
||||
|
@ -46,7 +46,7 @@ $(document).ready(function () {
|
|||
}
|
||||
|
||||
const id = $('.J_text').attr('attr-id')
|
||||
const color = $('.colors >a>i').parent().data('color') || ''
|
||||
const color = $('.rbcolors >a>i').parent().data('color') || ''
|
||||
|
||||
let exists = null
|
||||
$('.J_config .dd3-content, .unset-list .dd-handle>span').each(function () {
|
||||
|
|
|
@ -775,7 +775,7 @@ class ValueTagsEditor extends React.Component {
|
|||
onKeyDown={(e) => e.keyCode === 13 && this._saveTag()}
|
||||
/>
|
||||
</div>
|
||||
<div className="colors pt-2 pb-2 text-center">
|
||||
<div className="rbcolors pt-2 pb-2 text-center">
|
||||
{RBCOLORS.map((color) => {
|
||||
return (
|
||||
<a key={color} style={{ backgroundColor: color }} onClick={() => this.setState({ useColor: color })}>
|
||||
|
|
|
@ -419,13 +419,14 @@ var $same = function (a, b) {
|
|||
var $is = $same
|
||||
|
||||
/**
|
||||
* 是否为空。兼容对象或数组
|
||||
* 值是否为空(兼容对象和数组)
|
||||
*/
|
||||
var $empty = function (a) {
|
||||
if (a === undefined || a === null || a === '') return true
|
||||
var type = $.type(a)
|
||||
if (type === 'array' && a.length === 0) return true
|
||||
else return type === 'object' && Object.keys(a).length === 0
|
||||
if (a === null || a === '' || typeof a === 'undefined') return true
|
||||
var atype = $.type(a)
|
||||
if (atype === 'array' && a.length === 0) return true
|
||||
if (atype === 'object' && Object.keys(a).length === 0) return true
|
||||
return !$.trim(a + '')
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -200,6 +200,9 @@ class RbViewModal extends React.Component {
|
|||
}
|
||||
|
||||
// -- Usage
|
||||
|
||||
static mode = 1
|
||||
|
||||
/**
|
||||
* @param {*} props
|
||||
* @param {Boolean} subView
|
||||
|
|
|
@ -21,6 +21,10 @@ class RbFormModal extends React.Component {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { ...props, inLoad: true, _maximize: false }
|
||||
|
||||
this.__maximizeKey = `FormMaximize-${props.entity}`
|
||||
this.state._maximize = $isTrue($storage.get(this.__maximizeKey))
|
||||
|
||||
if (!props.id) this.state.id = null
|
||||
}
|
||||
|
||||
|
@ -44,7 +48,15 @@ class RbFormModal extends React.Component {
|
|||
<span className="zmdi zmdi-settings up-1" />
|
||||
</a>
|
||||
)}
|
||||
<button className="close md-close" type="button" title={this.state._maximize ? $L('向下还原') : $L('最大化')} onClick={() => this.setState({ _maximize: !this.state._maximize })}>
|
||||
<button
|
||||
className="close md-close"
|
||||
type="button"
|
||||
title={this.state._maximize ? $L('向下还原') : $L('最大化')}
|
||||
onClick={() => {
|
||||
this.setState({ _maximize: !this.state._maximize }, () => {
|
||||
$storage.set(this.__maximizeKey, this.state._maximize)
|
||||
})
|
||||
}}>
|
||||
<span className={`mdi ${this.state._maximize ? 'mdi mdi-window-restore' : 'mdi mdi-window-maximize'}`} />
|
||||
</button>
|
||||
<button className="close md-close" type="button" title={$L('关闭')} onClick={() => this.hide()}>
|
||||
|
@ -784,9 +796,7 @@ class RbFormElement extends React.Component {
|
|||
*/
|
||||
isValueError() {
|
||||
if (this.props.nullable === false) {
|
||||
const v = this.state.value
|
||||
if (v && $.type(v) === 'array') return v.length === 0 ? $L('不能为空') : null
|
||||
else return !v ? $L('不能为空') : null
|
||||
return $empty(this.state.value) ? $L('不能为空') : null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -857,6 +867,13 @@ class RbFormText extends RbFormElement {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
getValue() {
|
||||
const v = super.getValue()
|
||||
// fix: 3.1.1
|
||||
if (v && v === $L('自动值')) return null
|
||||
else return v
|
||||
}
|
||||
}
|
||||
|
||||
class RbFormUrl extends RbFormText {
|
||||
|
|
|
@ -17,6 +17,7 @@ const RBV_TRIGGERS = {
|
|||
'DATAVALIDATE': $L('数据校验'),
|
||||
'AUTOREVOKE': $L('自动撤销'),
|
||||
'AUTODELETE': $L('自动删除'),
|
||||
'PROXYTRIGGERACTION': $L('自定义触发器'),
|
||||
}
|
||||
|
||||
const WHENS = {
|
||||
|
@ -204,7 +205,12 @@ class TriggerEdit extends ConfigFormDlg {
|
|||
this.__select2 = []
|
||||
// #1
|
||||
$.get('/admin/robot/trigger/available-actions', (res) => {
|
||||
this.setState({ actions: res.data }, () => {
|
||||
let actions = res.data || []
|
||||
if (!$urlp('bosskey-show')) {
|
||||
actions = actions.filter((item) => item[0] !== 'PROXYTRIGGERACTION')
|
||||
}
|
||||
|
||||
this.setState({ actions }, () => {
|
||||
const s2ot = $(this._$actionType)
|
||||
.select2({
|
||||
placeholder: $L('选择触发类型'),
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
<div class="data-info J_c-type">
|
||||
<h5>[[${bundle.L('图表类型')}]]</h5>
|
||||
<div class="chart-type">
|
||||
<a th:title="${bundle.L('表格')}" data-type="TABLE" data-allow-dims="0|3" data-allow-nums="0|9"><i class="C200"></i></a>
|
||||
<a th:title="${bundle.L('表格')}" data-type="TABLE" data-allow-dims="0|5" data-allow-nums="0|9"><i class="C200"></i></a>
|
||||
<a th:title="${bundle.L('指标卡')}" data-type="INDEX" data-allow-dims="0|0" data-allow-nums="1|1"><i class="C310"></i></a>
|
||||
<a th:title="${bundle.L('折线图')}" data-type="LINE" data-allow-dims="1|1" data-allow-nums="1|9"><i class="C220"></i></a>
|
||||
<a th:title="${bundle.L('柱状图')}" data-type="BAR" data-allow-dims="1|1" data-allow-nums="1|9"><i class="C210"></i></a>
|
||||
|
@ -69,55 +69,62 @@
|
|||
<div class="J_opt-UNDEF">[[${bundle.L('当前图表无选项')}]]</div>
|
||||
<div class="hide J_opt-ALL">
|
||||
<label>
|
||||
<a class="J_filter" href="javascript:;">
|
||||
<a class="J_filter" href="###">
|
||||
<i class="zmdi zmdi-filter-list down-2" style="font-size: 1.43rem; width: 22px; padding-left: 2px; color: #888"></i>
|
||||
<span>[[${bundle.L('附加过滤条件')}]]</span>
|
||||
<span></span>
|
||||
</a>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2 admin-show">
|
||||
<label class="custom-control custom-control-sm custom-checkbox admin-show">
|
||||
<input class="custom-control-input" type="checkbox" data-name="noPrivileges" />
|
||||
<span class="custom-control-label"> [[${bundle.L('使用全部数据')}]] <i class="zmdi zmdi-help zicon" th:title="${bundle.L('不启用则仅能查看登录用户权限内的数据')}"></i></span>
|
||||
<span class="custom-control-label">[[${bundle.L('使用全部数据')}]] <i class="zmdi zmdi-help zicon" th:title="${bundle.L('不启用则仅能查看登录用户权限内的数据')}"></i></span>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="shareChart" />
|
||||
<span class="custom-control-label"> [[${bundle.L('共享此图表')}]] <i class="zmdi zmdi-help zicon" th:title="${bundle.L('共享后其他用户也可以使用 (不能修改)')}"></i></span>
|
||||
<span class="custom-control-label">[[${bundle.L('共享此图表')}]] <i class="zmdi zmdi-help zicon" th:title="${bundle.L('共享后其他用户也可以使用 (不能修改)')}"></i></span>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2 hide">
|
||||
<label class="custom-control custom-control-sm custom-checkbox hide">
|
||||
<input class="custom-control-input" type="checkbox" data-name="noZero" />
|
||||
<span class="custom-control-label"> [[${bundle.L('排除空数据 (数值为 0 不显示)')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('排除空数据 (数值为 0 不显示)')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hide J_opt-TABLE">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showLineNumber" />
|
||||
<span class="custom-control-label"> [[${bundle.L('显示行号')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('显示行号')}]]</span>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showSums" />
|
||||
<span class="custom-control-label"> [[${bundle.L('显示汇总')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('显示汇总')}]]</span>
|
||||
</label>
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="mergeCell" checked />
|
||||
<span class="custom-control-label">[[${bundle.L('自动合并单元格')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hide J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-TREEMAP J_opt-RADAR J_opt-SCATTER">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showNumerical" />
|
||||
<span class="custom-control-label"> [[${bundle.L('在图表上显示数值')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('在图表上显示数值')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hide J_opt-LINE J_opt-BAR J_opt-SCATTER">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showGrid" />
|
||||
<span class="custom-control-label"> [[${bundle.L('显示参考线')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('显示参考线')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hide J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-RADAR J_opt-SCATTER">
|
||||
<label class="custom-control custom-control-sm custom-checkbox mb-2">
|
||||
<label class="custom-control custom-control-sm custom-checkbox">
|
||||
<input class="custom-control-input" type="checkbox" data-name="showLegend" />
|
||||
<span class="custom-control-label"> [[${bundle.L('显示图例')}]]</span>
|
||||
<span class="custom-control-label">[[${bundle.L('显示图例')}]]</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hide J_opt-INDEX bosskey-show">
|
||||
<input type="color" data-name="useColor" />
|
||||
<div class="hide J_opt-INDEX">
|
||||
<label style="color: #444">[[${bundle.L('配色')}]]</label>
|
||||
<div id="useColor" class="rbcolors">
|
||||
<a style="background-color: #ddd; border: 1px solid #ccc" th:title="${bundle.L('默认')}"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<script th:src="@{/assets/js/rb-forms.append.js}" type="text/babel"></script>
|
||||
<script type="text/babel">
|
||||
CellRenders.clickView = function (v, e) {
|
||||
if (parent && parent.RbViewModal) {
|
||||
if (parent && parent.RbViewModal && parent.RbViewModal.mode === 1) {
|
||||
parent.RbViewModal.create({ id: v.id, entity: v.entity })
|
||||
} else {
|
||||
const openUrl = `${rb.baseUrl}/app/${v.entity}/list/#!/View/${v.entity}/${v.id}`
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="tab-pane" id="URL">
|
||||
<input type="text" class="form-control form-control-sm J_menuUrl" th:placeholder="${bundle.L('输入外部地址')}" />
|
||||
<input type="text" class="form-control form-control-sm J_menuUrl" th:placeholder="${bundle.L('输入外部地址')}" maxlength="600" />
|
||||
<div class="form-text">[[${bundle.L('支持绝对地址或相对地址')}]]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue