Better v3.2 (#564)

* jslog

* lang

* bugfix

* replaceUser

* Update @rbv

* feat: AppHome

* enh: clear avatar

* nashorn

Co-authored-by: Zixin <42044143+getrebuild@users.noreply.github.com>
Co-authored-by: RB <getrebuild@sina.com>
This commit is contained in:
devezhao 2023-01-17 17:34:01 +08:00 committed by GitHub
parent 9c45706c39
commit 9fb388ca34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 376 additions and 78 deletions

View file

@ -60,7 +60,7 @@ function _useAssetsHex(file) {
try {
hex = revHash(fs.readFileSync(`${RBV_ROOT}${file}`))
} catch (err1) {
if (file.includes('frontjs-sdk.js')) console.log('No `@rbv` exists :', file)
if (file.includes('frontjs-sdk.js') || file.includes('syscfg-sync.js') || file.includes('rb-filterpane.js')) console.log('No `@rbv` exists :', file)
else console.log('Cannot #revHash :', file, err1)
// Use date

2
@rbv

@ -1 +1 @@
Subproject commit 966c21045abe5cf9c4a14a98dd095662ee80e6ca
Subproject commit f615792a397ab5dce1ad840f16d6406af5918b6a

View file

@ -453,6 +453,14 @@
<artifactId>hutool-core</artifactId>
<version>5.8.11</version>
</dependency>
<!-- Need JDK11+ -->
<!--
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
-->
<!-- fix: CVEs -->
<dependency>

View file

@ -26,12 +26,13 @@ import org.springframework.util.Assert;
public class UserContextHolder {
private static final ThreadLocal<ID> CALLER = new NamedThreadLocal<>("Current user");
private static final ThreadLocal<ID> CALLER_PREV = new NamedThreadLocal<>("Previous user");
private static final ThreadLocal<String> LOCALE = new NamedThreadLocal<>("Request locale");
private static final ThreadLocal<String> REQIP = new NamedThreadLocal<>("Request IP");
private UserContextHolder() { }
private UserContextHolder() {}
/**
* @param user
@ -49,9 +50,9 @@ public class UserContextHolder {
public static void setUser(ID user) {
Assert.notNull(user, "[user] cannot be null");
ID exists = getUser(Boolean.TRUE);
if (exists != null) {
log.warn("Replace user in current session (thread) : " + exists + " > " + user);
ID e = getUser(Boolean.TRUE);
if (e != null) {
log.warn("Replace user in current thread (session) : {} < {}", user, e);
CALLER.remove();
}
CALLER.set(user);
@ -82,7 +83,7 @@ public class UserContextHolder {
if (user != null) return user;
if (allowNull) return null;
else throw new AccessDeniedException("No user found in current session (thread)");
throw new AccessDeniedException("No user found in current session (thread)");
}
/**
@ -108,6 +109,7 @@ public class UserContextHolder {
*/
public static void clearUser() {
CALLER.remove();
CALLER_PREV.remove();
}
/**
@ -116,6 +118,35 @@ public class UserContextHolder {
LOCALE.remove();
}
/**
* @param user
* @see #restoreUser()
* @see #setUser(ID)
*/
public static void replaceUser(ID user) {
Assert.notNull(user, "[user] cannot be null");
ID e = getUser(Boolean.TRUE);
if (e != null) CALLER_PREV.set(e);
else CALLER_PREV.remove();
CALLER.set(user);
}
/**
* @return
* @see #replaceUser(ID)
*/
public static boolean restoreUser() {
ID e = CALLER_PREV.get();
if (e != null) {
clearUser();
setUser(e);
return true;
}
return false;
}
// --
/**

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.configuration;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray;
@ -24,6 +25,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.project.ProjectManager;
import com.rebuild.core.support.License;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.CommonsUtils;
@ -79,7 +81,30 @@ public class NavBuilder extends NavManager {
* @return
*/
public JSONArray getUserNav(ID user) {
ConfigBean config = getLayoutOfNav(user);
return getUserNav(user, null);
}
/**
* @param user
* @param request
* @return
*/
public JSONArray getUserNav(ID user, HttpServletRequest request) {
ConfigBean config = null;
if (request != null) {
String useNav = ServletUtils.readCookie(request, "AppHome.Nav");
ID useNavId;
if ((useNavId = MetadataHelper.isSpecEntityId(useNav, EntityHelper.LayoutConfig)) != null) {
Object[][] cached = getAllConfig(null, TYPE_NAV);
config = findConfigBean(cached, useNavId);
}
}
if (config == null) {
config = getLayoutOfNav(user);
}
if (config == null) {
JSONArray useDefault = replaceLang(NAVS_DEFAULT);
((JSONObject) useDefault.get(1)).put("sub", buildAvailableProjects(user));
@ -260,8 +285,10 @@ public class NavBuilder extends NavManager {
*/
public static String renderNav(HttpServletRequest request, String activeNav) {
if (activeNav == null) activeNav = "dashboard-home";
JSONArray navs = NavBuilder.instance.getUserNav(AppUtils.getRequestUser(request));
JSONArray navs = License.isCommercial()
? NavBuilder.instance.getUserNav(AppUtils.getRequestUser(request), request)
: NavBuilder.instance.getUserNav(AppUtils.getRequestUser(request));
StringBuilder navsHtml = new StringBuilder();
for (Object item : navs) {

View file

@ -43,6 +43,7 @@ public class MetadataHelper {
public static final String SPLITER_RE = CommonsUtils.COMM_SPLITER_RE;
// 实体类型 https://getrebuild.com/docs/admin/meta-entity#%E5%AE%9E%E4%BD%93%E7%B1%BB%E5%9E%8B
public static final int TYPE_BAD = -1;
public static final int TYPE_SYS = 0;
public static final int TYPE_NORMAL = 1;
public static final int TYPE_MAIN = 2;
@ -427,7 +428,8 @@ public class MetadataHelper {
* @return
*/
public static int getEntityType(int entityCode) {
return getEntityType(getEntity(entityCode));
if (containsEntity(entityCode)) return getEntityType(getEntity(entityCode));
return TYPE_BAD;
}
/**

View file

@ -7,7 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service;
import cn.devezhao.persist4j.*;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.PersistManager;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.engine.NullValue;
import com.rebuild.core.metadata.EntityHelper;

View file

@ -238,7 +238,7 @@ public class RecordCheckout {
refEntity.getPrimaryField().getName(), refEntity.getName()));
for (String qf : queryFields) {
sql.append(String.format("%s = '%s' or ",
qf, StringEscapeUtils.escapeSql((String) val2Text)));
qf, StringEscapeUtils.escapeSql(val2Text.toString())));
}
sql = new StringBuilder(sql.substring(0, sql.length() - 4));

View file

@ -9,7 +9,12 @@ package com.rebuild.core.service.general;
import cn.devezhao.bizz.privileges.Permission;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.persist4j.*;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Filter;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import com.rebuild.core.RebuildException;

View file

@ -33,6 +33,7 @@ import java.util.*;
* @see OperatingObserver
* @since 12/28/2018
*/
@SuppressWarnings("deprecation")
@Slf4j
public abstract class ObservableService extends Observable implements ServiceSpec {
@ -90,7 +91,7 @@ public abstract class ObservableService extends Observable implements ServiceSpe
Record deleted = null;
if (countObservers() > 0) {
deleted = EntityHelper.forUpdate(recordId, currentUser);
deleted = EntityHelper.forUpdate(recordId, currentUser, Boolean.FALSE);
deleted = recordSnap(deleted);
// 删除前触发做一些状态保持

View file

@ -7,7 +7,12 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.query;
import cn.devezhao.persist4j.*;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Filter;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.query.NativeQuery;
import com.rebuild.core.Application;

View file

@ -7,13 +7,16 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.commons;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.License;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.web.BaseController;
import com.rebuild.web.IdParam;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
@ -21,6 +24,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
@ -82,4 +86,21 @@ public class CommonPageView extends BaseController {
p = p.split("/p/")[1];
return createModelAndView("/" + p);
}
@GetMapping("/app/home")
public void home(@IdParam(name = "n", required = false) ID useNav, @IdParam(name = "d", required = false) ID useDash,
HttpServletResponse response) throws IOException {
// 设置默认
addCookie("AppHome.Nav", useNav, response);
addCookie("AppHome.Dash", useDash, response);
response.sendRedirect("../dashboard/home");
}
private void addCookie(String name, ID value, HttpServletResponse response) {
Cookie cookie = new Cookie(name, value == null ? "N" : CodecUtils.urlEncode(value.toLiteral()));
cookie.setPath("/");
if (value == null) cookie.setMaxAge(0);
response.addCookie(cookie);
}
}

View file

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@ -55,8 +56,6 @@ public class ErrorPageView extends BaseController {
return mv;
}
// -- Status
@GetMapping("/error/server-status")
public ModelAndView pageServerStatus(HttpServletRequest request) {
boolean realtime = "1".equals(request.getParameter("check"));
@ -116,4 +115,18 @@ public class ErrorPageView extends BaseController {
response.sendRedirect(url);
}
@RequestMapping("/error/jslog")
public void jslog(HttpServletRequest request, HttpServletResponse response) {
String error = ServletUtils.getRequestString(request);
if (error == null) error = getParameter(request, "error", "-");
String errorLog = "\n++ JSLOG TRACE +++++++++++++++++++++++++++++++++++++++++++++" +
"\nUA : " + StringUtils.defaultIfEmpty(request.getHeader("user-agent"), "-") +
"\nMessage : " + error.replace("\\n", "\n") +
"\n";
log.error(errorLog);
ServletUtils.write(response, "{error_code:0}");
}
}

View file

@ -82,11 +82,9 @@ public class UseThemeController extends BaseController {
Etag etag = new Etag(themeHash, response);
if (!etag.isNeedWrite(request)) return;
InputStream is = CommonsUtils.getStreamOfRes(theme);
try {
ServletUtils.setContentType(response, "text/css");
try (InputStream is = CommonsUtils.getStreamOfRes(theme)) {
FileDownloader.writeStream(is, response);
} finally {
IOUtils.closeQuietly(is);
}
}

View file

@ -102,7 +102,7 @@ public class ModelExtrasController extends BaseController {
entity.getName(), entity.getPrimaryField().getName(), id);
Object[] recordMeta = Application.createQueryNoFilter(sql).unique();
if (recordMeta == null) {
return RespBody.errorl("NO_EXISTS");
return RespBody.error("NO_EXISTS");
}
return JSONUtils.toJSONObject(

View file

@ -2379,5 +2379,139 @@
"重置后第三方应用需更换新的 APP SECRET 使用":"重置后第三方应用需更换新的 APP SECRET 使用",
"分组":"分组",
"字段格式":"字段格式",
"分组字段":"分组字段"
"分组字段":"分组字段",
"编辑计算公式":"编辑计算公式",
"最后审批时间":"最后审批时间",
"请添加标签":"请添加标签",
"配置系统名称、LOGO或根据需要配置短信和邮件服务等":"配置系统名称、LOGO或根据需要配置短信和邮件服务等",
"任务卡字段显示":"任务卡字段显示",
"允许审批人加签":"允许审批人加签",
"及以下外部人员":"及以下外部人员",
"可取消共享源实体记录或其关联记录":"可取消共享源实体记录或其关联记录",
"企业微信集成":"企业微信集成",
"转审哪些实体":"转审哪些实体",
"内容 (及标题) 支持字段变量,字段变量如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)":"内容 (及标题) 支持字段变量,字段变量如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)",
"不能转审给自己":"不能转审给自己",
"默认宽度":"默认宽度",
"输入后回车":"输入后回车",
"仅用于断行":"仅用于断行",
"免费版不支持转审/加签功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持转审/加签功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"转审给谁":"转审给谁",
"可转审所有实体或指定实体的审批":"可转审所有实体或指定实体的审批",
"审批通知":"审批通知",
"%s编号":"%s编号",
"选择外部人员的电话 (手机) 或邮箱字段":"选择外部人员的电话 (手机) 或邮箱字段",
"设置数据列表":"设置数据列表",
"无法识别扫描结果":"无法识别扫描结果",
"业务实体是系统的基础与核心,所有业务功能均由此展开":"业务实体是系统的基础与核心,所有业务功能均由此展开",
"已加签":"已加签",
"常用值":"常用值",
"请选择取消共享记录":"请选择取消共享记录",
"日期超出数据库限制":"日期超出数据库限制",
"标签名称":"标签名称",
"审批人已在当前审批步骤中":"审批人已在当前审批步骤中",
"使用情况":"使用情况",
"更多功能":"更多功能",
"REBUILD 技术支持":"REBUILD 技术支持",
"免费版不支持抄送给外部人员功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持抄送给外部人员功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"请添加显示字段":"请添加显示字段",
"选择导出格式":"选择导出格式",
"继续完善":"继续完善",
"不能是同一个用户":"不能是同一个用户",
"插入字段变量":"插入字段变量",
"请先 [编辑图表](###)":"请先 [编辑图表](###)",
"百分比":"百分比",
"打开初始化向导":"打开初始化向导",
"@%s 退回了你的 %s 审批,请重新审批":"@%s 退回了你的 %s 审批,请重新审批",
"本次提交将抄送给":"本次提交将抄送给",
"添加显示字段":"添加显示字段",
"同时禁用离职用户":"同时禁用离职用户",
"开始转审":"开始转审",
"列显示":"列显示",
"批量转审":"批量转审",
"已转审":"已转审",
"最大显示条数":"最大显示条数",
"输入新标签":"输入新标签",
"允许自定义列显示":"允许自定义列显示",
"标签重复":"标签重复",
"添加登录用户并为他们分配角色,角色规定了他们对业务数据的的访问权限":"添加登录用户并为他们分配角色,角色规定了他们对业务数据的的访问权限",
"批量转审完成":"批量转审完成",
"请输入标签":"请输入标签",
"由 %s 转审":"由 %s 转审",
"抄送外部人员":"抄送外部人员",
"启用富文本编辑器":"启用富文本编辑器",
"抄送给外部人员":"抄送给外部人员",
"选择取消共享记录":"选择取消共享记录",
"继续使用":"继续使用",
"TAG":"TAG",
"免费版不支持离职继任功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)":"免费版不支持离职继任功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)",
"已转审 %d 条审批记录":"已转审 %d 条审批记录",
"默认收起":"默认收起",
"允许转审":"允许转审",
"[REBUILD](https://getrebuild.com/) 强力驱动":"[REBUILD](https://getrebuild.com/) 强力驱动",
"请选择继任用户":"请选择继任用户",
"请选择转审给谁":"请选择转审给谁",
"离职用户":"离职用户",
"暂无选项":"暂无选项",
"分派&共享":"分派&共享",
"启用扫码":"启用扫码",
"开始转移":"开始转移",
"@%s 提交了一条 %s 审批,请知悉":"@%s 提交了一条 %s 审批,请知悉",
"数据转移完成":"数据转移完成",
"系统用户与权限":"系统用户与权限",
"跟随初始化向导,帮助你快速完成系统搭建":"跟随初始化向导,帮助你快速完成系统搭建",
"业务实体配置":"业务实体配置",
"更多选项":"更多选项",
"初始化向导":"初始化向导",
"管理用户":"管理用户",
"下次登录不再显示":"下次登录不再显示",
"转审":"转审",
"如数据较多耗时会较长,请耐心等待。确定转移吗?":"如数据较多耗时会较长,请耐心等待。确定转移吗?",
"请选择转审谁的审批":"请选择转审谁的审批",
"由 %s 加签给你":"由 %s 加签给你",
"由 %s 加签":"由 %s 加签",
"@%s 提交的 %s 审批已由 @%s %s请知悉":"@%s 提交的 %s 审批已由 @%s %s请知悉",
"货币":"货币",
"内容支持字段变量,字段变量如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)":"内容支持字段变量,字段变量如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)",
"允许加签":"允许加签",
"系统通用配置":"系统通用配置",
"自动取消共享":"自动取消共享",
"输入名称":"输入名称",
"显示字段":"显示字段",
"请选择加签哪些用户":"请选择加签哪些用户",
"扩展属性":"扩展属性",
"离职继任将把离职用户的业务数据 (所属用户、共享用户)、审批中的记录 (审批人) 转移至继任用户":"离职继任将把离职用户的业务数据 (所属用户、共享用户)、审批中的记录 (审批人) 转移至继任用户",
"管理部门":"管理部门",
"未使用":"未使用",
"分派时":"分派时",
"去使用":"去使用",
"允许审批人转审":"允许审批人转审",
"我已完成":"我已完成",
"默认数据列表视图":"默认数据列表视图",
"由 %s 转审给你":"由 %s 转审给你",
"不选择则取消所有用户的":"不选择则取消所有用户的",
"允许编辑 (不勾选则仅共享读取权限)":"允许编辑 (不勾选则仅共享读取权限)",
"如数据较多耗时会较长,请耐心等待。确定转审吗?":"如数据较多耗时会较长,请耐心等待。确定转审吗?",
"启用快速新建":"启用快速新建",
"请选择图表数据来源":"请选择图表数据来源",
"加签":"加签",
"取消共享记录":"取消共享记录",
"输入%s":"输入%s",
"启用分类级别":"启用分类级别",
"常用标签":"常用标签",
"校验未通过时的提示内容。内容支持字段变量,如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)":"校验未通过时的提示内容。内容支持字段变量,如 `{createdOn}` (其中 createdOn 为源实体的字段内部标识)",
"转审谁的审批":"转审谁的审批",
"离职继任":"离职继任",
"如有多个常用值请使用逗号分开":"如有多个常用值请使用逗号分开",
"继任用户":"继任用户",
"请选择离职用户":"请选择离职用户",
"加签哪些用户":"加签哪些用户",
"数字":"数字",
"统计表":"统计表",
"钉钉集成":"钉钉集成",
"去配置":"去配置",
"页面错误,请稍后重试":"页面错误,请稍后重试",
"分派":"分派",
"REBUILD 拥有众多强大的功能,你可以持续探索":"REBUILD 拥有众多强大的功能,你可以持续探索",
"邮件与短信":"邮件与短信"
}

View file

@ -8,7 +8,8 @@
<!--
<div class="navbar-collapse collapse">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link">TOP-MENU</a></li>
<li class="nav-item"><a class="nav-link" th:href="@{/app/home}">TOP-MENU</a></li>
<li class="nav-item"><a class="nav-link" th:href="@{/app/home?n=&d=}">SPECIFIED</a></li>
</ul>
</div>
-->

View file

@ -32,7 +32,7 @@
<div class="card-body">
<form>
<div class="form-group">
<input class="form-control" id="admin-passwd" type="password" th:placeholder="${bundle.L('输入密码')}" autocomplete="off" />
<input class="form-control" id="admin-passwd" type="password" th:placeholder="${bundle.L('登录密码')}" autocomplete="off" />
</div>
<div class="form-group login-submit">
<button class="btn btn-primary btn-xl J_verify-btn" type="submit">[[${bundle.L('确定')}]]</button>

View file

@ -132,7 +132,7 @@
<th class="text-left" width="25%">[[${bundle.L('业务实体')}]]</th>
<th><a data-action="C" th:title="${bundle.L('批量选择')}">[[${bundle.L('新建')}]]</a></th>
<th><a data-action="R" th:title="${bundle.L('批量选择')}">[[${bundle.L('读取')}]]</a></th>
<th><a data-action="U" th:title="${bundle.L('批量选择')}">[[${bundle.L('更新')}]]</a></th>
<th><a data-action="U" th:title="${bundle.L('批量选择')}">[[${bundle.L('编辑')}]]</a></th>
<th><a data-action="D" th:title="${bundle.L('批量选择')}">[[${bundle.L('删除')}]]</a></th>
<th><a data-action="A" th:title="${bundle.L('批量选择')}">[[${bundle.L('分配')}]]</a></th>
<th><a data-action="S" th:title="${bundle.L('批量选择')}">[[${bundle.L('共享')}]]</a></th>
@ -214,7 +214,7 @@
<tr>
<td class="name"><a data-name="AllowBatchUpdate">[[${bundle.L('允许批量修改')}]]</a></td>
<td><i data-action="Z" class="priv R0"></i></td>
<td colspan="5" class="text-muted text-left">[[${bundle.L('需具备相应实体的更新权限')}]]</td>
<td colspan="5" class="text-muted text-left">[[${bundle.L('需具备相应实体的编辑权限')}]]</td>
</tr>
<tr class="hide">
<td class="name"><a data-name="AllowDataImport">[[${bundle.L('允许数据导入')}]]</a></td>

View file

@ -46,9 +46,9 @@
<thead>
<tr>
<th>[[${bundle.L('名称')}]]</th>
<th>[[${bundle.L('源实体')}]]</th>
<th width="20%">[[${bundle.L('触发类型')}]]</th>
<th width="20%" class="no-sort">[[${bundle.L('触发动作')}]]</th>
<th width="15%">[[${bundle.L('源实体')}]]</th>
<th width="15%">[[${bundle.L('触发类型')}]]</th>
<th width="15%" class="no-sort">[[${bundle.L('触发动作')}]]</th>
<th width="80" class="int-sort">[[${bundle.L('优先级')}]]</th>
<th width="80" class="no-sort">[[${bundle.L('启用')}]]</th>
<th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th>

View file

@ -160,6 +160,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
font-size: 1rem;
}
.chart.ctable .table td > div {
min-height: 16px;
}
.chart.ctable .table td.active,
.chart.ctable .table td.highlight {
outline: 1px solid #4285f4;

View file

@ -71,7 +71,7 @@ button[disabled] {
}
.dropdown-menu.auto-scroller {
max-height: 500px;
max-height: 487px;
overflow-x: auto;
}
@ -1117,29 +1117,37 @@ select.form-control:not([disabled]) {
}
.img-field a.img-upload b,
.img-field label.img-upload b,
.file-field .img-thumbnail b {
position: absolute;
bottom: 0;
width: 100%;
left: 0;
right: 0;
font-size: 14px;
font-size: 1.2rem;
color: #fff;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.3);
color: #fff;
text-align: center;
line-height: 1;
line-height: 18px;
display: none;
}
.img-field label.img-upload b .zmdi {
font-size: 1.2rem;
color: #fff;
}
.img-field a.img-upload:hover b,
.img-field label.img-upload:hover b,
.file-field .img-thumbnail:hover b {
display: block;
}
.img-field a.img-upload:hover b:hover,
.img-field label.img-upload:hover b:hover,
.file-field .img-thumbnail:hover b:hover {
background-color: rgba(0, 0, 0, 0.5);
background-color: rgba(0, 0, 0, 0.6);
}
.img-field.avatar,
@ -3112,7 +3120,7 @@ form {
@media (max-width: 767.98px) {
.rb-top-header .global-search2,
.rb-top-header .global-create2 {
.rb-top-header .global-create2-- {
display: none !important;
}
}
@ -3189,16 +3197,21 @@ form {
padding: 15px !important;
}
.global-create2 .dropdown-menu > em {
padding: 7px 15px !important;
display: block;
.global-create2 .dropdown-menu:empty {
padding: 13px 15px !important;
}
.global-search .dropdown-menu:empty::after,
.global-create2 .dropdown-menu > em {
content: attr(_title);
.global-create2 .dropdown-menu:empty::after {
font-style: italic;
font-size: 1rem;
color: #999;
content: attr(_title) !important;
position: static !important;
display: inline-block !important;
margin: 0 !important;
padding: 0 !important;
border: 0 none !important;
}
.preview-modal {
@ -4677,6 +4690,11 @@ pre.unstyle {
min-width: 48%;
}
.protable .table tr td .custom-control.custom-radio {
margin-right: 10px;
margin-bottom: 0;
}
.protable .type-MULTISELECT {
padding-bottom: 0 !important;
margin-bottom: -2px !important;
@ -5124,3 +5142,14 @@ pre.unstyle {
max-width: 400px;
margin: 0 auto;
}
/* TOP-MENU */
.navbar-collapse .navbar-nav > li > a.nav-link:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.rb-color-header .navbar-collapse,
.rb-offcanvas-menu .navbar-collapse {
display: none !important;
}

View file

@ -646,13 +646,13 @@ class ApproverNodeConfig extends StartNodeConfig {
<div className="form-group mb-0">
<label className="custom-control custom-control-sm custom-checkbox mb-2">
<input className="custom-control-input" type="checkbox" name="allowReferral" checked={this.state.allowReferral === true} onChange={this.handleChange} />
<span className="custom-control-label">{$L('允许审批人转审')}</span>
<span className="custom-control-label">{$L('允许审批人转审')} <sup className="rbv" title={$L('增值功能')} /></span>
</label>
</div>
<div className="form-group mb-0">
<label className="custom-control custom-control-sm custom-checkbox">
<input className="custom-control-input" type="checkbox" name="allowCountersign" checked={this.state.allowCountersign === true} onChange={this.handleChange} />
<span className="custom-control-label">{$L('允许审批人加签')}</span>
<span className="custom-control-label">{$L('允许审批人加签')} <sup className="rbv" title={$L('增值功能')} /></span>
</label>
</div>
@ -758,6 +758,11 @@ class ApproverNodeConfig extends StartNodeConfig {
return
}
if (rb.commercial < 1 && (d.allowReferral || d.allowCountersign)) {
RbHighbar.error(WrapHtml($L('免费版不支持转审/加签功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)')))
return
}
typeof this.props.call === 'function' && this.props.call(d)
this.cancel()
}
@ -810,10 +815,10 @@ class CCNodeConfig extends StartNodeConfig {
<div className="form-group mt-3">
<label className="text-bold">
{$L('抄送给外部人员 (可选)')} <sup className="rbv" title={$L('增值功能')} />
{$L('抄送给外部人员')} <sup className="rbv" title={$L('增值功能')} />
</label>
<UserSelectorWithField ref={(c) => (this._UserSelector2 = c)} userType={2} hideUser hideDepartment hideRole hideTeam />
<p className="form-text">{$L('选择外部人员的电话手机或邮箱字段')}</p>
<p className="form-text">{$L('选择外部人员的电话 (手机) 或邮箱字段')}</p>
</div>
</div>

View file

@ -1147,7 +1147,7 @@ class DataList extends BaseChart {
super.renderError(
<RF>
<span>{$L('当前图表无数据')}</span>
{this.props.isManageable && <div>{$L('请先 [编辑图表](###)')}</div>}
{this.props.isManageable && <div>{WrapHtml($L('请先 [编辑图表](###)'))}</div>}
</RF>,
() => {
$(this._$body)
@ -1294,7 +1294,7 @@ class DataListSettings extends RbModalHandler {
</select>
</div>
</div>
<div className="form-group row pb-1 DataList-showfields">
<div className="form-group row pb-0 DataList-showfields">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示字段')}</label>
<div className="col-sm-7">
<div className="sortable-box rb-scroller h200" ref={(c) => (this._$showfields = c)}>
@ -1323,6 +1323,18 @@ class DataListSettings extends RbModalHandler {
</a>
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('最大显示条数')}</label>
<div className="col-sm-7">
<input type="number" className="form-control form-control-sm" placeholder="20" ref={(c) => (this._$pageSize = c)} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('图表名称')}</label>
<div className="col-sm-7">
<input type="text" className="form-control form-control-sm" placeholder={$L('数据列表')} ref={(c) => (this._$chartTitle = c)} />
</div>
</div>
{rb.isAdminUser && (
<div className="form-group row pb-2 pt-1">
<label className="col-sm-3 col-form-label text-sm-right"></label>
@ -1337,18 +1349,6 @@ class DataListSettings extends RbModalHandler {
</div>
</div>
)}
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('最大显示条数')}</label>
<div className="col-sm-7">
<input type="number" className="form-control form-control-sm" placeholder="20" ref={(c) => (this._$pageSize = c)} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('图表名称')}</label>
<div className="col-sm-7">
<input type="text" className="form-control form-control-sm" placeholder={$L('数据列表')} ref={(c) => (this._$chartTitle = c)} />
</div>
</div>
<div className="form-group row footer">
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._$btn = c)}>

View file

@ -32,23 +32,23 @@ $(document).ready(() => {
return
}
let d = dash_list[0] // default
let use = dash_list[0] // default
if (dash_list.length > 1) {
const dset = $storage.get('DashDefault')
const dset = $.cookie('AppHome.Dash') || $storage.get('DashDefault')
if (dset) {
for (let i = 0; i < res.data.length; i++) {
if (res.data[i][0] === dset) {
d = res.data[i]
use = res.data[i]
break
}
}
}
}
dashid = d[0]
dash_editable = d[2]
render_dashboard(d[3])
$('.dash-list h4').text(d[4])
dashid = use[0]
dash_editable = use[2]
render_dashboard(use[3])
$('.dash-list h4').text(use[4])
if (location.hash && location.hash.length > 20) {
if (location.hash.substr(0, 5) === '#del=') {
@ -71,7 +71,7 @@ $(document).ready(() => {
}
$('.J_dash-new').on('click', () => dlgShow('DlgDashAdd'))
$('.J_dash-edit').on('click', () => dlgShow('DlgDashSettings', { title: d[4], shareTo: d[1] }))
$('.J_dash-edit').on('click', () => dlgShow('DlgDashSettings', { title: use[4], shareTo: use[1] }))
$('.J_chart-new').on('click', () => dlgShow('DlgAddChart'))
$('.J_dash-select').on('click', () => dlgShow('DashSelect', { dashList: dash_list }))
@ -264,8 +264,6 @@ const add_widget = function (item) {
gridstack.addWidget(gsi, item.x, item.y, item.w, item.h, item.x === undefined, 2, 12, 2, 24)
}
console.log(item)
item.editable = dash_editable
// eslint-disable-next-line no-undef
renderRbcomp(detectChart(item, item.chart), chid, function () {

View file

@ -105,6 +105,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
},
})
window.onerror = function () {
$.post('/error/jslog', JSON.stringify(arguments))
}
rb.commercial = ~~rb.commercial
if (rb.commercial < 10) $('.rbv-hide').removeClass('rbv-hide')
if (rb.env === 'dev') $('.dev-show').removeClass('dev-show')

View file

@ -2194,12 +2194,23 @@ class RbFormAvatar extends RbFormElement {
}
renderElement() {
const readonly = this.props.readonly
return (
<div className="img-field avatar">
<span title={this.props.readonly ? null : $L('选择头像')}>
{!this.props.readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._htmlid} accept="image/*" />}
<label htmlFor={this._htmlid} className="img-thumbnail img-upload" disabled={this.props.readonly}>
{!readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._htmlid} accept="image/*" />}
<label htmlFor={this._htmlid} className="img-thumbnail img-upload" disabled={readonly}>
<img src={this._formatUrl(this.state.value)} alt="Avatar" />
{!readonly && this.state.value && (
<b
title={$L('移除')}
onClick={(e) => {
$stopEvent(e, true)
this.handleClear()
}}>
<span className="zmdi zmdi-close" />
</b>
)}
</label>
</span>
</div>

View file

@ -422,7 +422,7 @@ var _initGlobalSearch = function () {
}, 100)
})
$unhideDropdown('.global-search')
// $unhideDropdown('.global-search')
var $gs = $('.global-search .dropdown-menu')
$('.sidebar-elements li').each(function (idx, item) {
@ -475,10 +475,7 @@ var _initGlobalCreate = function () {
var e = $(this).data('entity')
if (e && e !== '$PARENT$' && !entities.contains(e)) entities.push(e)
})
if (entities.length === 0) {
$('<em>' + $L('无可新建项') + '</em>').appendTo('.global-create2 .dropdown-menu')
return
}
if (entities.length === 0) return
$.get('/app/entity/extras/check-creates?entity=' + entities.join(','), function (res) {
var $gc = $('.global-create2 .dropdown-menu')
@ -565,7 +562,7 @@ var $createUploader = function (input, next, complete, error) {
function _qiniuUpload(file) {
$.get('/filex/qiniu/upload-keys?file=' + $encode(file.name) + useToken, function (res) {
var o = qiniu.upload(file, res.data.key, res.data.token, putExtra)
var o = qiniu.upload(file, res.data.key, res.data.token, putExtra, { forceDirect: true })
o.subscribe({
next: function (res) {
typeof next === 'function' && next({ percent: res.total.percent, file: file })