Merge pull request #98 from getrebuild/batch-modify-106

RB-106 Batch updates
This commit is contained in:
Fangfang Zhao 2019-12-04 21:27:47 +08:00 committed by GitHub
commit 0a531a5e4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
103 changed files with 2527 additions and 1212 deletions

View file

@ -52,6 +52,7 @@
"$same": true,
"$unmount": true,
"$initUserSelect2": true,
"$initReferenceSelect2": true,
"$keepModalOpen": true,
"rb": true,
"renderRbcomp": true,

View file

@ -127,7 +127,7 @@
<dependency>
<groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId>
<version>5422daff8e</version>
<version>a3a3ffc9f3</version>
</dependency>
<dependency>
<groupId>junit</groupId>

View file

@ -98,7 +98,7 @@ public class LoginToken extends BaseApi {
User loginUser = Application.getUserStore().getUser(user);
if (!loginUser.isActive()
|| !Application.getSecurityManager().allowed(loginUser.getId(), ZeroEntry.AllowLogin)) {
|| !Application.getSecurityManager().allow(loginUser.getId(), ZeroEntry.AllowLogin)) {
return Languages.lang("UnactiveUserTip");
}

View file

@ -18,6 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server;
import cn.devezhao.commons.excel.Cell;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.engine.ID;
@ -91,6 +92,7 @@ public final class Application {
SerializeConfig.getGlobalInstance().put(Date.class, RbDateCodec.instance);
SerializeConfig.getGlobalInstance().put(StandardRecord.class, RbRecordCodec.instance);
SerializeConfig.getGlobalInstance().put(QueryedRecord.class, RbRecordCodec.instance);
SerializeConfig.getGlobalInstance().put(Cell.class, ToStringSerializer.instance);
}
// 调试模式/开发模式

View file

@ -0,0 +1,45 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server;
/**
* 业务安全异常
*
* @author ZHAO
* @since 2019/12/3
*/
public class SecurityException extends RebuildException {
private static final long serialVersionUID = 8786241299394987035L;
public SecurityException() {
super();
}
public SecurityException(String msg, Throwable cause) {
super(msg, cause);
}
public SecurityException(String msg) {
super(msg);
}
public SecurityException(Throwable cause) {
super(cause);
}
}

View file

@ -64,7 +64,7 @@ public class ChartsFactory {
}
Entity entity = MetadataHelper.getEntity(e);
if (user == null || !Application.getSecurityManager().allowedR(user, entity.getEntityCode())) {
if (user == null || !Application.getSecurityManager().allowRead(user, entity.getEntityCode())) {
throw new ChartsException("没有读取 [" + EasyMeta.getLabel(entity) + "] 的权限");
}

View file

@ -51,16 +51,20 @@ import java.util.List;
*/
public class DataExporter extends SetUser<DataExporter> {
private JSONObject query;
/**
* 最大行数
*/
public static final int MAX_ROWS = 65535 - 1;
final private JSONObject queryData;
// 字段
private List<Field> headFields = new ArrayList<>();
/**
* @param query
* @param queryData
*/
public DataExporter(JSONObject query) {
this.query = query;
public DataExporter(JSONObject queryData) {
this.queryData = queryData;
}
/**
@ -80,7 +84,7 @@ public class DataExporter extends SetUser<DataExporter> {
* @param dest
*/
public void export(File dest) {
DefaultDataListControl control = new DefaultDataListControl(query, getUser());
DefaultDataListControl control = new DefaultDataListControl(queryData, getUser());
EasyExcel.write(dest)
.registerWriteHandler(new ColumnWidthStrategy())
.registerWriteHandler(this.buildStyle())
@ -106,7 +110,7 @@ public class DataExporter extends SetUser<DataExporter> {
}
/**
* 数据
* 构建数据
*
* @param control
* @return

View file

@ -18,7 +18,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.business.dataimport;
import cn.devezhao.commons.RegexUtils;
import cn.devezhao.commons.excel.Cell;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
@ -26,21 +25,13 @@ import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.portals.ClassificationManager;
import com.rebuild.server.configuration.portals.PickListManager;
import com.rebuild.server.helper.state.StateManager;
import com.rebuild.server.helper.task.HeavyTask;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.ExtRecordCreator;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.entity.DisplayType;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -60,105 +51,78 @@ public class DataImporter extends HeavyTask<Integer> {
private static final ThreadLocal<ID> IN_IMPORTING = new ThreadLocal<>();
final private ImportRule rule;
final private ID owningUser;
private int successed = 0;
private Map<Integer, Object> iLogging = new LinkedHashMap<>();
private ID owningUser;
// 记录每一行的错误日志
private Map<Integer, Object> eachLogs = new LinkedHashMap<>();
/**
* @param rule
*/
public DataImporter(ImportRule rule) {
this(rule, Application.getCurrentUser());
}
/**
* @param rule
* @param owningUser
*/
public DataImporter(ImportRule rule, ID owningUser) {
this.rule = rule;
this.owningUser = rule.getDefaultOwningUser() == null ? owningUser : rule.getDefaultOwningUser();
}
@Override
protected Integer exec() throws Exception {
try {
final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse();
this.setTotal(rows.size() - 1);
final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse();
this.setTotal(rows.size() - 1);
// 指定的所属用户
setUser(this.owningUser);
IN_IMPORTING.set(owningUser);
owningUser = rule.getDefaultOwningUser() != null ? rule.getDefaultOwningUser() : getUser();
IN_IMPORTING.set(owningUser);
int rowNo = 0;
for (final Cell[] row : rows) {
if (isInterrupt()) {
this.setInterrupted();
break;
}
if (rowNo++ == 0 || row == null) {
continue;
}
try {
Record record = checkoutRecord(row);
if (record != null) {
record = Application.getEntityService(rule.getToEntity().getEntityCode()).createOrUpdate(record);
this.successed++;
iLogging.put(rowNo, record.getPrimary());
}
} catch (Exception ex) {
iLogging.put(rowNo, ex.getLocalizedMessage());
LOG.warn(rowNo + " > " + ex);
} finally {
this.addCompleted();
}
for (final Cell[] row : rows) {
if (isInterrupt()) {
this.setInterrupted();
break;
}
} finally {
IN_IMPORTING.remove();
Cell firstCell = row == null || row.length == 0 ? null : row[0];
if (firstCell == null || firstCell.getRowNo() == 0) {
continue;
}
try {
Record record = checkoutRecord(row);
if (record != null) {
record = Application.getEntityService(rule.getToEntity().getEntityCode()).createOrUpdate(record);
this.addSucceeded();
eachLogs.put(firstCell.getRowNo(), record.getPrimary());
}
} catch (Exception ex) {
eachLogs.put(firstCell.getRowNo(), ex.getLocalizedMessage());
LOG.error(firstCell.getRowNo() + " > " + ex);
}
this.addCompleted();
}
return this.successed;
return this.getSucceeded();
}
/**
* @return
*/
public int getSuccessed() {
return successed;
@Override
protected void completedAfter() {
super.completedAfter();
IN_IMPORTING.remove();
}
/**
* 获取错误日志按错误行
*
* @return
*/
public Map<Integer, Object> getLogging() {
return iLogging;
public Map<Integer, Object> getEachLogs() {
return eachLogs;
}
/**
* @param cells
* @param row
* @return
*/
protected Record checkoutRecord(Cell[] cells) {
protected Record checkoutRecord(Cell[] row) {
Record recordNew = EntityHelper.forNew(rule.getToEntity().getEntityCode(), this.owningUser);
for (Map.Entry<Field, Integer> e : rule.getFiledsMapping().entrySet()) {
int cellIndex = e.getValue();
if (cells.length > cellIndex) {
Field field = e.getKey();
Cell cellValue = cells[cellIndex];
Object value = checkoutFieldValue(field, cellValue, true);
if (value != null) {
recordNew.setObjectValue(field.getName(), value);
} else if (cellValue != Cell.NULL && !cellValue.isEmpty()) {
LOG.warn("Invalid value of cell : " + cellValue + " > " + field.getName());
}
}
}
// 新建
Record record = recordNew;
Record record = new RecordCheckout(rule.getFiledsMapping()).checkout(recordNew, row);
// 检查重复
if (rule.getRepeatOpt() < ImportRule.REPEAT_OPT_IGNORE) {
@ -173,9 +137,7 @@ public class DataImporter extends HeavyTask<Integer> {
record = EntityHelper.forUpdate(repeat, this.owningUser);
for (Iterator<String> iter = recordNew.getAvailableFieldIterator(); iter.hasNext(); ) {
String field = iter.next();
if (EntityHelper.OwningUser.equals(field) || EntityHelper.OwningDept.equals(field)
|| EntityHelper.CreatedBy.equals(field) || EntityHelper.CreatedOn.equals(field)
|| EntityHelper.ModifiedBy.equals(field) || EntityHelper.ModifiedOn.equals(field)) {
if (MetadataHelper.isCommonsField(field)) {
continue;
}
record.setObjectValue(field, recordNew.getObjectValue(field));
@ -191,182 +153,6 @@ public class DataImporter extends HeavyTask<Integer> {
return record;
}
/**
* @param field
* @param cell
* @param validate
* @return
*/
protected Object checkoutFieldValue(Field field, Cell cell, boolean validate) {
final DisplayType dt = EasyMeta.getDisplayType(field);
if (dt == DisplayType.NUMBER) {
return cell.asLong();
} else if (dt == DisplayType.DECIMAL) {
return cell.asDouble();
} else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
return checkoutDateValue(field, cell);
} else if (dt == DisplayType.PICKLIST) {
return checkoutPickListValue(field, cell);
} else if (dt == DisplayType.CLASSIFICATION) {
return checkoutClassificationValue(field, cell);
} else if (dt == DisplayType.REFERENCE) {
return checkoutReferenceValue(field, cell);
} else if (dt == DisplayType.BOOL) {
return cell.asBool();
} else if (dt == DisplayType.STATE) {
return checkoutStateValue(field, cell);
}
// 格式验证
if (validate) {
if (dt == DisplayType.EMAIL) {
String email = cell.asString();
return RegexUtils.isEMail(email) ? email : null;
} else if (dt == DisplayType.URL) {
String url = cell.asString();
return RegexUtils.isUrl(url) ? url : null;
} else if (dt == DisplayType.PHONE) {
String tel = cell.asString();
return RegexUtils.isCNMobile(tel) || RegexUtils.isTel(tel)? tel : null;
}
}
return cell.asString();
}
/**
* @param field
* @param cell
* @return
*/
private ID checkoutPickListValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
// 支持ID
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.PickList) {
ID iid = ID.valueOf(val);
if (PickListManager.instance.getLabel(iid) != null) {
return iid;
} else {
LOG.warn("No item of PickList found by ID : " + iid);
return null;
}
} else {
return PickListManager.instance.findItemByLabel(val, field);
}
}
/**
* @param field
* @param cell
* @return
*/
private Integer checkoutStateValue(Field field, Cell cell) {
final String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
Integer state = StateManager.instance.getState(field, val);
if (state != null) {
return state;
}
// 兼容状态值
if (NumberUtils.isNumber(val)) {
return NumberUtils.toInt(val);
}
return null;
}
/**
* @param field
* @param cell
* @return
*/
private ID checkoutClassificationValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
// 支持ID
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.ClassificationData) {
ID iid = ID.valueOf(val);
if (ClassificationManager.instance.getName(iid) != null) {
return iid;
} else {
LOG.warn("No item of Classification found by ID : " + iid);
return null;
}
} else {
return ClassificationManager.instance.findItemByName(val, field);
}
}
/**
* @param field
* @param cell
* @return
*/
private ID checkoutReferenceValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
Entity oEntity = field.getReferenceEntity();
// 支持ID导入
if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == oEntity.getEntityCode()) {
return ID.valueOf(val);
}
Object textVal = checkoutFieldValue(oEntity.getNameField(), cell, false);
if (textVal == null) {
return null;
}
Query query;
if (oEntity.getEntityCode() == EntityHelper.User) {
String sql = MessageFormat.format(
"select userId from User where loginName = ''{0}'' or email = ''{0}'' or fullName = ''{0}''",
StringEscapeUtils.escapeSql(textVal.toString()));
query = Application.createQueryNoFilter(sql);
} else {
String sql = String.format("select %s from %s where %s = ?",
oEntity.getPrimaryField().getName(), oEntity.getName(), oEntity.getNameField().getName());
query = Application.createQueryNoFilter(sql).setParameter(1, textVal);
}
Object[] found = query.unique();
return found != null ? (ID) found[0] : null;
}
/**
* @param field
* @param cell
* @return
*/
private Date checkoutDateValue(Field field, Cell cell) {
Date date = cell.asDate();
if (date != null) {
return date;
}
if (cell.isEmpty()) {
return null;
}
String date2str = cell.asString();
// 2017/11/19 11:07
if (date2str.contains("/")) {
return cell.asDate(new String[] { "yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d" });
}
return null;
}
/**
* @param repeatFields
* @param data
@ -405,11 +191,11 @@ public class DataImporter extends HeavyTask<Integer> {
// --
/**
* 是否导入模式
* 是否为导入状态
*
* @return
*/
public static boolean isInImporting() {
public static boolean inImportingState() {
return IN_IMPORTING.get() != null;
}
}

View file

@ -0,0 +1,267 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.business.dataimport;
import cn.devezhao.commons.RegexUtils;
import cn.devezhao.commons.excel.Cell;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.portals.ClassificationManager;
import com.rebuild.server.configuration.portals.PickListManager;
import com.rebuild.server.helper.state.StateManager;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.entity.DisplayType;
import com.rebuild.server.metadata.entity.EasyMeta;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Map;
/**
* Cell[] 中解析结果 Record
*
* @author devezhao
* @since 2019/12/4
*/
public class RecordCheckout {
private static final Log LOG = LogFactory.getLog(RecordCheckout.class);
final private Map<Field, Integer> fieldsMapping;
/**
* @param fieldsMapping
*/
protected RecordCheckout(Map<Field, Integer> fieldsMapping) {
this.fieldsMapping = fieldsMapping;
}
/**
* @param row
* @return
*/
public Record checkout(Record record, Cell[] row) {
for (Map.Entry<Field, Integer> e : this.fieldsMapping.entrySet()) {
int cellIndex = e.getValue();
if (cellIndex > row.length) {
continue;
}
Field field = e.getKey();
Cell cellValue = row[cellIndex];
Object value = checkoutFieldValue(field, cellValue, true);
if (value != null) {
record.setObjectValue(field.getName(), value);
} else if (cellValue != Cell.NULL && !cellValue.isEmpty()) {
LOG.warn("Invalid value of cell : " + cellValue + " > " + field.getName());
}
}
return record;
}
/**
* @param field
* @param cell
* @param validate 验证格式如邮箱/URL等
* @return
*/
protected Object checkoutFieldValue(Field field, Cell cell, boolean validate) {
final DisplayType dt = EasyMeta.getDisplayType(field);
if (dt == DisplayType.NUMBER) {
return cell.asLong();
} else if (dt == DisplayType.DECIMAL) {
return cell.asDouble();
} else if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
return checkoutDateValue(field, cell);
} else if (dt == DisplayType.PICKLIST) {
return checkoutPickListValue(field, cell);
} else if (dt == DisplayType.CLASSIFICATION) {
return checkoutClassificationValue(field, cell);
} else if (dt == DisplayType.REFERENCE) {
return checkoutReferenceValue(field, cell);
} else if (dt == DisplayType.BOOL) {
return cell.asBool();
} else if (dt == DisplayType.STATE) {
return checkoutStateValue(field, cell);
}
// 格式验证
if (validate) {
if (dt == DisplayType.EMAIL) {
String email = cell.asString();
return RegexUtils.isEMail(email) ? email : null;
} else if (dt == DisplayType.URL) {
String url = cell.asString();
return RegexUtils.isUrl(url) ? url : null;
} else if (dt == DisplayType.PHONE) {
String tel = cell.asString();
return RegexUtils.isCNMobile(tel) || RegexUtils.isTel(tel)? tel : null;
}
}
return cell.asString();
}
/**
* @param field
* @param cell
* @return
* @see PickListManager
*/
protected ID checkoutPickListValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
// 支持ID
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.PickList) {
ID iid = ID.valueOf(val);
if (PickListManager.instance.getLabel(iid) != null) {
return iid;
} else {
LOG.warn("No item of PickList found by ID : " + iid);
return null;
}
} else {
return PickListManager.instance.findItemByLabel(val, field);
}
}
/**
* @param field
* @param cell
* @return
* @see StateManager
*/
protected Integer checkoutStateValue(Field field, Cell cell) {
final String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
Integer state = StateManager.instance.getState(field, val);
if (state != null) {
return state;
}
// 兼容状态值
if (NumberUtils.isNumber(val)) {
return NumberUtils.toInt(val);
}
return null;
}
/**
* @param field
* @param cell
* @return
* @see ClassificationManager
*/
protected ID checkoutClassificationValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
// 支持ID
if (ID.isId(val) && ID.valueOf(val).getEntityCode() == EntityHelper.ClassificationData) {
ID iid = ID.valueOf(val);
if (ClassificationManager.instance.getName(iid) != null) {
return iid;
} else {
LOG.warn("No item of Classification found by ID : " + iid);
return null;
}
} else {
return ClassificationManager.instance.findItemByName(val, field);
}
}
/**
* @param field
* @param cell
* @return
*/
protected ID checkoutReferenceValue(Field field, Cell cell) {
String val = cell.asString();
if (StringUtils.isBlank(val)) {
return null;
}
final Entity refEntity = field.getReferenceEntity();
// 支持ID导入
if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == refEntity.getEntityCode()) {
return ID.valueOf(val);
}
Object val2Text = checkoutFieldValue(refEntity.getNameField(), cell, false);
if (val2Text == null) {
return null;
}
Query query;
if (refEntity.getEntityCode() == EntityHelper.User) {
String sql = MessageFormat.format(
"select userId from User where loginName = ''{0}'' or email = ''{0}'' or fullName = ''{0}''",
StringEscapeUtils.escapeSql(val2Text.toString()));
query = Application.createQueryNoFilter(sql);
} else {
// 查找引用实体的名称字段
String sql = String.format("select %s from %s where %s = ?",
refEntity.getPrimaryField().getName(), refEntity.getName(), refEntity.getNameField().getName());
query = Application.createQueryNoFilter(sql).setParameter(1, val2Text);
}
Object[] found = query.unique();
return found != null ? (ID) found[0] : null;
}
/**
* @param field
* @param cell
* @return
*/
protected Date checkoutDateValue(Field field, Cell cell) {
Date date = cell.asDate();
if (date != null) {
return date;
}
if (cell.isEmpty()) {
return null;
}
String date2str = cell.asString();
// 2017/11/19 11:07
if (date2str.contains("/")) {
return cell.asDate(new String[] { "yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d" });
}
return null;
}
}

View file

@ -85,7 +85,7 @@ public class RBStore {
}
if (d == null) {
throw new RebuildException("无法读取远程数据");
throw new RebuildException("无法读取 RB 仓库数据");
}
return d;
}

View file

@ -75,7 +75,7 @@ public class AutoAssign implements TriggerAction {
final ID recordId = operatingContext.getAnyRecord().getPrimary();
if (!allowNoPermissionAssign) {
if (!Application.getSecurityManager().allowed(
if (!Application.getSecurityManager().allow(
operatingContext.getOperator(), recordId, BizzPermission.ASSIGN)) {
LOG.warn("No privileges to assign record of target: " + recordId);
return;

View file

@ -61,7 +61,7 @@ public class AutoShare extends AutoAssign {
final ID recordId = operatingContext.getAnyRecord().getPrimary();
if (!allowNoPermissionShare) {
if (!Application.getSecurityManager().allowed(
if (!Application.getSecurityManager().allow(
operatingContext.getOperator(), recordId, BizzPermission.SHARE)) {
LOG.warn("No privileges to share record of target: " + recordId);
return;

View file

@ -107,7 +107,7 @@ public class FieldAggregation implements TriggerAction {
// 如果当前用户对目标记录无修改权限
if (!allowNoPermissionUpdate) {
if (!Application.getSecurityManager().allowed(
if (!Application.getSecurityManager().allow(
operatingContext.getOperator(), targetRecordId, BizzPermission.UPDATE)) {
LOG.warn("No privileges to update record of target: " + this.targetRecordId);
return;

View file

@ -111,16 +111,7 @@ public class BaseLayoutManager extends ShareToManager<ID> {
}
Object[][] cached = getAllConfig(belongEntity, applyType);
for (Object[] c : cached) {
if (!c[0].equals(detected)) {
continue;
}
return new ConfigEntry()
.set("id", c[0])
.set("shareTo", c[1])
.set("config", JSON.parse((String) c[3]));
}
return null;
return findEntry(cached, detected);
}
/**
@ -137,17 +128,26 @@ public class BaseLayoutManager extends ShareToManager<ID> {
}
Object[][] cached = getAllConfig((String) o[0], (String) o[1]);
for (Object[] c : cached) {
if (!c[0].equals(cfgid)) {
return findEntry(cached, cfgid);
}
/**
* @param cached
* @param cfgid
* @return
*/
private ConfigEntry findEntry(Object[][] cached, ID cfgid) {
for (Object[] c : cached) {
if (!c[0].equals(cfgid)) {
continue;
}
return new ConfigEntry()
.set("id", c[0])
.set("shareTo", c[1])
.set("config", JSON.parse((String) c[3]));
}
return null;
}
return new ConfigEntry()
.set("id", c[0])
.set("shareTo", c[1])
.set("config", JSON.parse((String) c[3]));
}
return null;
}
@Override
public void clean(ID cacheKey) {

View file

@ -112,7 +112,7 @@ public class DataListManager extends BaseLayoutManager {
Field parentField = entityMeta.getField(fieldPath[0]);
if (!filter) {
formatted = formatField(lastField, parentField);
} else if (Application.getSecurityManager().allowedR(user, lastField.getOwnEntity().getEntityCode())) {
} else if (Application.getSecurityManager().allowRead(user, lastField.getOwnEntity().getEntityCode())) {
formatted = formatField(lastField, parentField);
}
}

View file

@ -146,10 +146,10 @@ public class FormsBuilder extends FormsManager {
// 明细无需审批
approvalState = null;
if (!Application.getSecurityManager().allowedU(user, masterRecordId)) {
if (!Application.getSecurityManager().allowUpdate(user, masterRecordId)) {
return formatModelError("你没有权限向此记录添加明细");
}
} else if (!Application.getSecurityManager().allowedC(user, entityMeta.getEntityCode())) {
} else if (!Application.getSecurityManager().allowCreate(user, entityMeta.getEntityCode())) {
return formatModelError("没有新建权限");
} else {
approvalState = getHadApproval(entityMeta, null);
@ -158,7 +158,7 @@ public class FormsBuilder extends FormsManager {
}
// 查看视图
else if (viewMode) {
if (!Application.getSecurityManager().allowedR(user, record)) {
if (!Application.getSecurityManager().allowRead(user, record)) {
return formatModelError("你无权读取此记录或记录已被删除");
}
@ -167,7 +167,7 @@ public class FormsBuilder extends FormsManager {
}
// 编辑
else {
if (!Application.getSecurityManager().allowedU(user, record)) {
if (!Application.getSecurityManager().allowUpdate(user, record)) {
return formatModelError("你没有编辑此记录的权限");
}

View file

@ -133,7 +133,7 @@ public class NavManager extends BaseLayoutManager {
}
Entity entityMeta = MetadataHelper.getEntity(entity);
return !Application.getSecurityManager().allowedR(user, entityMeta.getEntityCode());
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
}
return false;
}

View file

@ -93,7 +93,7 @@ public class ViewAddonsManager extends BaseLayoutManager {
if (e.getMasterEntity() != null) {
continue;
}
if (Application.getSecurityManager().allowed(user, e.getEntityCode(), useAction)) {
if (Application.getSecurityManager().allow(user, e.getEntityCode(), useAction)) {
refs.add(EasyMeta.getEntityShow(e));
}
}
@ -105,7 +105,7 @@ public class ViewAddonsManager extends BaseLayoutManager {
String e = (String) o;
if (MetadataHelper.containsEntity(e)) {
Entity entityMeta = MetadataHelper.getEntity(e);
if (Application.getSecurityManager().allowed(user, entityMeta.getEntityCode(), useAction)) {
if (Application.getSecurityManager().allow(user, entityMeta.getEntityCode(), useAction)) {
addons.add(EasyMeta.getEntityShow(entityMeta));
}
}

View file

@ -83,7 +83,7 @@ public class RecentlyUsedCache {
List<ID> data = new ArrayList<>();
for (int i = 0; i < limit && i < exists.size(); i++) {
final ID raw = exists.get(i);
if (!Application.getSecurityManager().allowedR(user, raw)) {
if (!Application.getSecurityManager().allowRead(user, raw)) {
continue;
}

View file

@ -0,0 +1,154 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.helper.datalist;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.helper.SetUser;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.query.ParserTokens;
import java.util.HashSet;
import java.util.Set;
/**
* 批量选择记录在列表中查询器
*
* @author ZHAO
* @since 2019/12/1
*/
public class BatchOperatorQuery extends SetUser<BatchOperatorQuery> {
/**
* 选中数据
*/
public static final int DR_SELECTED = 1;
/**
* 当前页数据
*/
public static final int DR_PAGED = 2;
/**
* 查询后数据
*/
public static final int DR_QUERYED = 3;
/**
* 全部数据
*/
public static final int DR_ALL = 4;
private int dataRange;
private JSONObject queryData;
/**
* @param dataRange
* @param queryData
*/
public BatchOperatorQuery(int dataRange, JSONObject queryData) {
this.dataRange = dataRange;
this.queryData = queryData;
}
/**
* 对查询数据进行包装根据数据范围
*
* @param maxRows
* @return
*/
public JSONObject wrapQueryData(int maxRows) {
if (this.dataRange != DR_PAGED) {
queryData.put("pageNo", 1);
queryData.put("pageSize", maxRows); // Max
}
if (this.dataRange == DR_SELECTED || this.dataRange == DR_ALL) {
queryData.remove("filter");
queryData.remove("advFilter");
}
if (this.dataRange == DR_SELECTED) {
JSONObject idsItem = new JSONObject();
idsItem.put("op", ParserTokens.IN);
idsItem.put("field", getEntity().getPrimaryField().getName());
idsItem.put("value", queryData.getString("_selected"));
JSONArray items = new JSONArray();
items.add(idsItem);
JSONObject filter = new JSONObject();
filter.put("items", items);
queryData.put("filter", filter);
}
queryData.put("reload", Boolean.FALSE);
queryData.put("fields", new String[] { getEntity().getPrimaryField().getName() });
return queryData;
}
/**
* 获取 SQL from 后面的子句 from
*
* @return
* @see QueryParser
*/
protected String getFromClauseSql() {
QueryParser queryParser = new QueryParser(wrapQueryData(Integer.MAX_VALUE));
String fullSql = queryParser.toSql();
return fullSql.substring(fullSql.indexOf(" from ")).trim();
}
/**
* 直接获取记录 ID[]
*
* @return
*/
public ID[] getQueryedRecords() {
if (this.dataRange == DR_SELECTED) {
String selected = queryData.getString("_selected");
Set<ID> ids = new HashSet<>();
for (String s : selected.split("\\|")) {
if (ID.isId(s)) {
ids.add(ID.valueOf(s));
}
}
return ids.toArray(new ID[0]);
}
String sql = String.format("select %s %s",
getEntity().getPrimaryField().getName(), getFromClauseSql());
int pageNo = queryData.getIntValue("pageNo");
int pageSize = queryData.getIntValue("pageSize");
Object[][] array = Application.getQueryFactory().createQuery(sql, getUser())
.setLimit(pageSize, pageNo * pageSize - pageSize)
.setTimeout(60)
.array();
Set<ID> ids = new HashSet<>();
for (Object[] o : array) {
ids.add((ID) o[0]);
}
return ids.toArray(new ID[0]);
}
private Entity getEntity() {
String entityName = queryData.getString("entity");
return MetadataHelper.getEntity(entityName);
}
}

View file

@ -186,6 +186,6 @@ public class DataListWrapper {
int fieldIndex = queryJoinFields.get(fieldPath[0]);
Object check = original[fieldIndex];
return check == null || Application.getSecurityManager().allowedR(user, (ID) check);
return check == null || Application.getSecurityManager().allowRead(user, (ID) check);
}
}

View file

@ -73,7 +73,7 @@ public class QueryParser {
* @param queryExpr
* @param dataListControl
*/
public QueryParser(JSONObject queryExpr, DataListControl dataListControl) {
protected QueryParser(JSONObject queryExpr, DataListControl dataListControl) {
this.queryExpr = queryExpr;
this.dataListControl = dataListControl;
this.entity = dataListControl != null ?
@ -91,11 +91,11 @@ public class QueryParser {
/**
* @return
*/
public String toCountSql() {
protected String toCountSql() {
doParseIfNeed();
return countSql;
}
/**
* @return
*/
@ -145,7 +145,7 @@ public class QueryParser {
return;
}
StringBuilder sqlBase = new StringBuilder("select ");
StringBuilder fullSql = new StringBuilder("select ");
JSONArray fieldsNode = queryExpr.getJSONArray("fields");
int fieldIndex = -1;
@ -153,7 +153,7 @@ public class QueryParser {
for (Object o : fieldsNode) {
// DataListManager 中已验证字段有效此处不再次验证
String field = o.toString().trim();
sqlBase.append(field).append(',');
fullSql.append(field).append(',');
fieldIndex++;
if (field.split("\\.").length > 1) {
@ -165,24 +165,24 @@ public class QueryParser {
// 最后增加一个主键列
String pkName = entity.getPrimaryField().getName();
sqlBase.append(pkName);
fullSql.append(pkName);
fieldIndex++;
// NOTE 查询出关联记录 ID 以便验证权限
if (!queryJoinFields.isEmpty()) {
this.queryJoinFields = new HashMap<>();
for (String field : queryJoinFields) {
sqlBase.append(',').append(field);
fullSql.append(',').append(field);
fieldIndex++;
this.queryJoinFields.put(field, fieldIndex);
}
}
sqlBase.append(" from ").append(entity.getName());
fullSql.append(" from ").append(entity.getName());
// 过滤器
StringBuilder sqlWhere = new StringBuilder(" where (1=1)");
StringBuilder sqlWhere = new StringBuilder("(1=1)");
// Default
String defaultFilter = dataListControl == null ? null : dataListControl.getDefaultFilter();
@ -208,7 +208,7 @@ public class QueryParser {
sqlWhere.append(" and ").append(where);
}
}
sqlBase.append(sqlWhere);
fullSql.append(" where ").append(sqlWhere);
// 排序
@ -223,17 +223,18 @@ public class QueryParser {
sqlSort.append(EntityHelper.CreatedOn + " desc");
}
if (sqlSort.length() > 10) {
sqlBase.append(sqlSort);
fullSql.append(sqlSort);
}
this.sql = sqlBase.toString();
this.sql = fullSql.toString();
this.countSql = new StringBuilder("select ")
.append("count(").append(pkName).append(')')
.append(" from ")
.append(entity.getName())
.append(" where ")
.append(sqlWhere)
.toString();
int pageNo = NumberUtils.toInt(queryExpr.getString("pageNo"), 1);
int pageSize = NumberUtils.toInt(queryExpr.getString("pageSize"), 20);
this.limit = new int[] { pageSize, pageNo * pageSize - pageSize };

View file

@ -28,8 +28,8 @@ import org.apache.commons.logging.LogFactory;
import java.util.Date;
/**
* 耗时操作可通过此类进行例如大批量删除/修改等此类提供了进度相关的约定如总计执行条目已完成条目/百分比
* 集成此类应该处理线程的 <code>isInterrupted</code> 方法以便任务可以被终止
* 耗时操作可通过此类进行例如大批量删除/修改等此类提供了进度相关的约定如总计执行条目已完成条目/百分比
* 继承此类应该处理线程的 <code>isInterrupted</code> 方法以便任务可以被终止
* 使用此类应该总是使用 TaskExecutors 调用
*
* @author devezhao
@ -37,19 +37,23 @@ import java.util.Date;
*
* @see TaskExecutors
*/
public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnable {
public abstract class HeavyTask<T> extends SetUser<HeavyTask<T>> implements Runnable {
protected static final Log LOG = LogFactory.getLog(HeavyTask.class);
volatile private boolean interrupt = false;
volatile private boolean interruptState = false;
/**
* @see SetUser
*/
private ID threadUser;
private int total = -1;
private int completed = 0;
private Date beginTime;
private int succeeded = 0;
final private Date beginTime;
private Date completedTime;
private String errorMessage;
@ -59,7 +63,7 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
}
@Override
public HeavyTask setUser(ID user) {
public HeavyTask<T> setUser(ID user) {
this.threadUser = user;
return super.setUser(user);
}
@ -76,12 +80,12 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
this.completed++;
}
protected Date getBeginTime() {
return beginTime;
}
protected Date getCompletedTime() {
return completedTime;
protected Date getCompletedTime() {
return completedTime;
}
protected void addSucceeded() {
succeeded++;
}
/**
@ -91,13 +95,15 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
*/
public long getElapsedTime() {
if (getCompletedTime() != null) {
return getCompletedTime().getTime() - getBeginTime().getTime();
return getCompletedTime().getTime() - beginTime.getTime();
} else {
return CalendarUtils.now().getTime() - getBeginTime().getTime();
return CalendarUtils.now().getTime() - beginTime.getTime();
}
}
/**
* 总计数量
*
* @return
*/
public int getTotal() {
@ -105,6 +111,8 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
}
/**
* 完成数量
*
* @return
*/
public int getCompleted() {
@ -112,6 +120,8 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
}
/**
* 完成进度百分比
*
* @return
*/
public double getCompletedPercent() {
@ -125,11 +135,31 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
}
/**
* 是否完成
*
* @return
*/
public boolean isCompleted() {
return completedTime != null || (total != -1 && getCompleted() >= getTotal());
return getCompletedTime() != null || (total != -1 && getCompleted() >= getTotal());
}
/**
* 成功数量
*
* @return
*/
public int getSucceeded() {
return succeeded;
}
/**
* 错误消息如有
*
* @return
*/
public String getErrorMessage() {
return errorMessage;
}
// 中断处理是否允许中断由子类决定
@ -181,18 +211,4 @@ public abstract class HeavyTask<T> extends SetUser<HeavyTask> implements Runnabl
Application.getSessionStore().clean();
}
}
/**
* @return
*/
public String getErrorMessage() {
return errorMessage;
}
/**
* @return
*/
public boolean hasError() {
return errorMessage != null;
}
}

View file

@ -22,7 +22,9 @@ import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.engine.NullValue;
import cn.devezhao.persist4j.record.JsonRecordCreator;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
@ -31,26 +33,48 @@ import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.server.service.DataSpecificationException;
import com.rebuild.server.service.bizz.privileges.User;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 标准 Record 解析
*
* @author Zhao Fangfang
* @since 1.0, 2013-6-26
*/
public class ExtRecordCreator extends JsonRecordCreator {
private static final Log LOG = LogFactory.getLog(ExtRecordCreator.class);
/**
* 更新时是移除不允许更新的字段还是抛出异常
*/
private boolean strictMode;
/**
* @param entity
* @param source
* @param editor
*/
public ExtRecordCreator(Entity entity, JSONObject source, ID editor) {
super(entity, source, editor);
this(entity, source, editor, false);
}
/**
* @param entity
* @param source
* @param editor
* @param strictMode
*/
public ExtRecordCreator(Entity entity, JSONObject source, ID editor, boolean strictMode) {
super(entity, source, editor);
this.strictMode = strictMode;
}
@Override
protected void afterCreate(Record record, boolean isNew) {
super.afterCreate(record, isNew);
@ -93,29 +117,52 @@ public class ExtRecordCreator extends JsonRecordCreator {
@Override
public void verify(Record record, boolean isNew) {
if (!isNew) {
return;
List<String> notAllowed = new ArrayList<>();
// 新建
if (isNew) {
for (Field field : entity.getFields()) {
if (MetadataHelper.isSystemField(field)) {
continue;
}
EasyMeta easy = EasyMeta.valueOf(field);
if (easy.getDisplayType() == DisplayType.SERIES) {
continue;
}
Object hasVal = record.getObjectValue(field.getName());
if ((hasVal == null || NullValue.is(hasVal)) && !field.isNullable()) {
notAllowed.add(easy.getLabel());
}
}
if (!notAllowed.isEmpty()) {
throw new DataSpecificationException(StringUtils.join(notAllowed, "/") + " 不允许为空");
}
}
List<String> notNulls = new ArrayList<>();
for (Field field : entity.getFields()) {
if (MetadataHelper.isSystemField(field)) {
continue;
}
EasyMeta easy = EasyMeta.valueOf(field);
if (easy.getDisplayType() == DisplayType.SERIES) {
continue;
}
Object hasVal = record.getObjectValue(field.getName());
if (hasVal == null && !field.isNullable()) {
notNulls.add(easy.getLabel());
}
}
if (notNulls.isEmpty()) {
return;
}
throw new DataSpecificationException(StringUtils.join(notNulls, "/") + " 不能为空");
// 更新
else {
for (String fieldName : record.getAvailableFields()) {
Field field = record.getEntity().getField(fieldName);
if (EntityHelper.ModifiedOn.equalsIgnoreCase(fieldName)
|| EntityHelper.ModifiedBy.equalsIgnoreCase(fieldName)
|| field.getType() == FieldType.PRIMARY) {
continue;
}
EasyMeta easy = EasyMeta.valueOf(field);
if (!easy.isUpdatable()) {
if (strictMode) {
notAllowed.add(easy.getLabel());
} else {
record.removeValue(fieldName);
LOG.warn("Remove non-updatable field : " + fieldName);
}
}
}
if (!notAllowed.isEmpty()) {
throw new DataSpecificationException(StringUtils.join(notAllowed, "/") + " 不允许修改");
}
}
}
}

View file

@ -86,7 +86,7 @@ public class MetadataSorter {
if (user == null) {
list.add(entity);
} else if (Application.getSecurityManager().allowedR(user, entity.getEntityCode())) {
} else if (Application.getSecurityManager().allowRead(user, entity.getEntityCode())) {
list.add(entity);
}
}

View file

@ -27,10 +27,14 @@ import cn.devezhao.persist4j.metadata.BaseMeta;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.RebuildException;
import com.rebuild.server.configuration.RobotTriggerManager;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import java.util.Set;
/**
* 元数据元素封装
@ -104,10 +108,7 @@ public class EasyMeta implements BaseMeta {
* @return
*/
public DisplayType getDisplayType() {
if (!isField()) {
throw new UnsupportedOperationException("Field only");
}
Assert.isTrue(isField(), "Field supports only");
Object[] ext = getMetaExt();
if (ext != null) {
return (DisplayType) ext[2];
@ -180,10 +181,7 @@ public class EasyMeta implements BaseMeta {
* @return
*/
public String getIcon() {
if (isField()) {
throw new UnsupportedOperationException("Entity only");
}
Assert.isTrue(!isField(), "Entity supports only");
String customIcon = null;
Object[] ext = getMetaExt();
if (ext != null) {
@ -197,14 +195,12 @@ public class EasyMeta implements BaseMeta {
/**
* 字段扩展配置
*
*
* @return
* @see FieldExtConfigProps
*/
public JSONObject getFieldExtConfig() {
if (!isField()) {
throw new UnsupportedOperationException("Field only");
}
Assert.isTrue(isField(), "Field supports only");
Object[] ext = getMetaExt();
if (ext == null || StringUtils.isBlank((String) ext[3])) {
JSONObject extConfig = getExtraAttrsJson().getJSONObject("extConfig");
@ -212,11 +208,48 @@ public class EasyMeta implements BaseMeta {
}
return JSON.parseObject((String) ext[3]);
}
/**
* 字段扩展配置
*
* @param name
* @return
*
* @see #getFieldExtConfig()
* @see FieldExtConfigProps
*/
public Object getPropOfFieldExtConfig(String name) {
return getFieldExtConfig().get(name);
}
/**
* 此方法除了判断元数据还会判断其他业务规则
*
* @return
* @see Field#isUpdatable()
* @see RobotTriggerManager#getAutoReadonlyFields(String)
*/
public boolean isUpdatable() {
Assert.isTrue(isField(), "Field supports only");
final Field field = (Field) baseMeta;
if (!field.isUpdatable()) {
return false;
}
Set<String> set = RobotTriggerManager.instance.getAutoReadonlyFields(field.getOwnEntity().getName());
return set.contains(field.getName()) ? false : true;
}
/**
* @return
*/
private boolean isField() {
return baseMeta instanceof Field;
}
/**
* @return
*/
private Object[] getMetaExt() {
Object[] ext;
if (isField()) {
@ -226,13 +259,21 @@ public class EasyMeta implements BaseMeta {
}
return ext;
}
/**
* @return
*/
private JSONObject getExtraAttrsJson() {
return StringUtils.isBlank(getExtraAttrs())
? JSONUtils.EMPTY_OBJECT : JSON.parseObject(getExtraAttrs());
}
// 将字段类型转成 DisplayType
/**
* 将字段类型转成 DisplayType
*
* @param field
* @return
*/
private DisplayType converBuiltinFieldType(Field field) {
Type ft = field.getType();
if (ft == FieldType.PRIMARY) {
@ -313,24 +354,26 @@ public class EasyMeta implements BaseMeta {
}
/**
* 获取字段 Label支持两级字段 owningUser.fullName
*
* @param entity
* @param joinFields
* @param fieldPath
* @return
*/
public static String getLabel(Entity entity, String joinFields) {
String[] fieldPath = joinFields.split("\\.");
Field firstField = entity.getField(fieldPath[0]);
if (fieldPath.length == 1) {
public static String getLabel(Entity entity, String fieldPath) {
String[] fieldPathSplit = fieldPath.split("\\.");
Field firstField = entity.getField(fieldPathSplit[0]);
if (fieldPathSplit.length == 1) {
return getLabel(firstField);
}
Entity refEntity = firstField.getReferenceEntity();
Field secondField = refEntity.getField(fieldPath[1]);
Field secondField = refEntity.getField(fieldPathSplit[1]);
return String.format("%s.%s", getLabel(firstField), getLabel(secondField));
}
/**
* @return [Name, Label, Icon]
* @return returns [Name, Label, Icon]
*/
public static String[] getEntityShow(Entity entity) {
EasyMeta em = valueOf(entity);

View file

@ -0,0 +1,74 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.metadata.entity;
/**
* 字段扩展属性常量
*
* @author ZHAO
* @since 2019/12/3
*/
public class FieldExtConfigProps {
/**
* 是否允许负数
*/
public static final String NUMBER_NOTNEGATIVE = "notNegative";
/**
* 是否允许负数
*/
public static final String DECIMAL_NOTNEGATIVE = NUMBER_NOTNEGATIVE;
/**
* 日期格式
*/
public static final String DATE_DATEFORMAT = "dateFormat";
/**
* 日期格式
*/
public static final String DATETIME_DATEFORMAT = DATE_DATEFORMAT;
/**
* 允许上传数量
*/
public static final String FILE_UPLOADNUMBER = "uploadNumber";
/**
* 允许上传数量
*/
public static final String IMAGE_UPLOADNUMBER = FILE_UPLOADNUMBER;
/**
* 自动编号规则
*/
public static final String SERIES_SERIESFORMAT = "seriesFormat";
/**
* 自动编号归零方式
*/
public static final String SERIES_SERIESZERO = "seriesZero";
/**
* 使用哪个分类数据
*/
public static final String CLASSIFICATION_USECLASSIFICATION = "useClassification";
/**
* 使用哪个状态类
*/
public static final String STATE_STATECLASS = "stateClass";
}

View file

@ -25,6 +25,7 @@ import com.rebuild.server.Application;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.service.OperatingContext;
import com.rebuild.server.service.notification.NotificationObserver;
import com.rebuild.server.service.notification.NotificationOnce;
import java.util.Set;
@ -36,23 +37,22 @@ import java.util.Set;
*/
public class BulkAssign extends BulkOperator {
public BulkAssign(BulkContext context, GeneralEntityService ges) {
protected BulkAssign(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
protected Integer exec() {
ID[] records = prepareRecords();
final ID[] records = prepareRecords();
this.setTotal(records.length);
int assigned = 0;
ID firstAssigned = null;
BulkOperatorTx.begin();
NotificationOnce.begin();
for (ID id : records) {
if (Application.getSecurityManager().allowedA(context.getOpUser(), id)) {
if (Application.getSecurityManager().allowAssign(context.getOpUser(), id)) {
int a = ges.assign(id, context.getToUser(), context.getCascades());
if (a > 0) {
assigned += a;
this.addSucceeded();
if (firstAssigned == null) {
firstAssigned = id;
}
@ -63,9 +63,8 @@ public class BulkAssign extends BulkOperator {
this.addCompleted();
}
Set<ID> affected = BulkOperatorTx.getInTxSet();
BulkOperatorTx.end();
// 合并通知发送
Set<ID> affected = NotificationOnce.end();
if (firstAssigned != null && !affected.isEmpty()) {
Record notificationNeeds = EntityHelper.forUpdate(firstAssigned, context.getOpUser());
notificationNeeds.setID(EntityHelper.OwningUser, context.getToUser());
@ -75,6 +74,6 @@ public class BulkAssign extends BulkOperator {
new NotificationObserver().update(null, operatingContext);
}
return assigned;
return getSucceeded();
}
}

View file

@ -0,0 +1,108 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.service.base;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.record.JsonRecordCreator;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.helper.datalist.BatchOperatorQuery;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.service.DataSpecificationException;
import org.apache.commons.lang.StringUtils;
/**
* 批量修改
*
* @author devezhao
* @since 2019/12/2
*/
public class BulkBacthUpdate extends BulkOperator {
/**
* 修改为
*/
public static final String OP_SET = "SET";
/**
* 置空
*/
public static final String OP_NULL = "NULL";
protected BulkBacthUpdate(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
protected Integer exec() throws Exception {
final ID[] willUpdates = prepareRecords();
this.setTotal(willUpdates.length);
JSONArray updateContents = context.getCustomData().getJSONArray("updateContents");
// 转化成标准 FORM 格式
JSONObject formJson = new JSONObject();
for (Object o : updateContents) {
JSONObject item = (JSONObject) o;
String field = item.getString("field");
String op = item.getString("op");
String value = item.getString("value");
if (OP_NULL.equalsIgnoreCase(op)) {
formJson.put(field, StringUtils.EMPTY);
} else if (OP_SET.equalsIgnoreCase(op)) {
formJson.put(field, value);
}
}
JSONObject metadata = new JSONObject();
metadata.put("entity", context.getMainEntity().getName());
formJson.put(JsonRecordCreator.META_FIELD, metadata);
if (LOG.isDebugEnabled()) {
LOG.debug("Converter to : " + formJson);
}
for (ID id : willUpdates) {
if (Application.getSecurityManager().allowUpdate(context.getOpUser(), id)) {
// 更新记录
formJson.getJSONObject(JsonRecordCreator.META_FIELD).put("id", id.toLiteral());
try {
Record record = EntityHelper.parse(formJson, context.getOpUser());
ges.update(record);
this.addSucceeded();
} catch (DataSpecificationException ex) {
LOG.warn("Couldn't update : " + id + " Ex : " + ex);
}
} else {
LOG.warn("No have privileges to UPDATE : " + context.getOpUser() + " > " + id);
}
this.addCompleted();
}
return getSucceeded();
}
@Override
protected ID[] prepareRecords() {
JSONObject customData = context.getCustomData();
int dataRange = customData.getIntValue("_dataRange");
BatchOperatorQuery query = new BatchOperatorQuery(dataRange, customData.getJSONObject("queryData"));
return query.getQueryedRecords();
}
}

View file

@ -40,16 +40,19 @@ public class BulkContext {
private Permission action;
// [目标用户]
private ID toUser;
// 待操作记录
private ID[] records;
// 待操作记录通过过滤条件获得
private JSONObject filterExp;
// [待操作记录所依附的主记录]
private ID targetRecord;
// [级联操作实体]
private String[] cascades;
final private Entity mainEntity;
// [特定数据] 默认为高级查询表达式
// 如果为查询条件其必须含有查询项否则将抛出异常
private JSONObject customData;
final private Entity mainEntity;
/**
* @param opUser
@ -57,15 +60,15 @@ public class BulkContext {
* @param toUser
* @param cascades
* @param records
* @param filterExp
* @param customData
* @param recordMaster
*/
private BulkContext(ID opUser, Permission action, ID toUser, String[] cascades, ID[] records, JSONObject filterExp, ID recordMaster) {
private BulkContext(ID opUser, Permission action, ID toUser, String[] cascades, ID[] records, JSONObject customData, ID recordMaster) {
this.opUser = opUser;
this.action = action;
this.toUser = toUser;
this.records = records;
this.filterExp = filterExp;
this.customData = customData;
this.targetRecord = recordMaster;
this.cascades = cascades;
this.mainEntity = detecteMainEntity();
@ -96,6 +99,15 @@ public class BulkContext {
this(opUser, action, null, null, records, null, targetRecord);
}
/**
* @param opUser
* @param action
* @param customData
*/
public BulkContext(ID opUser, Permission action, JSONObject customData) {
this(opUser, action, null, null, null, customData, null);
}
public ID getOpUser() {
return opUser;
}
@ -116,8 +128,8 @@ public class BulkContext {
return records;
}
public JSONObject getFilterExp() {
return filterExp;
public JSONObject getCustomData() {
return customData;
}
public ID getTargetRecord() {
@ -133,8 +145,8 @@ public class BulkContext {
return MetadataHelper.getEntity(targetRecord.getEntityCode());
} else if (records != null && records.length > 0) {
return MetadataHelper.getEntity(records[0].getEntityCode());
} else if (filterExp != null) {
return MetadataHelper.getEntity(filterExp.getString("entity"));
} else if (customData != null) {
return MetadataHelper.getEntity(customData.getString("entity"));
}
throw new RebuildException("No record for operate");
}

View file

@ -30,21 +30,20 @@ import com.rebuild.server.service.DataSpecificationException;
*/
public class BulkDelete extends BulkOperator {
public BulkDelete(BulkContext context, GeneralEntityService ges) {
protected BulkDelete(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
protected Integer exec() {
ID[] records = prepareRecords();
final ID[] records = prepareRecords();
this.setTotal(records.length);
int affected = 0;
for (ID id : records) {
if (Application.getSecurityManager().allowedD(context.getOpUser(), id)) {
if (Application.getSecurityManager().allowDelete(context.getOpUser(), id)) {
try {
int a = ges.delete(id, context.getCascades());
affected += (a > 0 ? 1 : 0);
ges.delete(id, context.getCascades());
this.addSucceeded();
} catch (DataSpecificationException ex) {
LOG.warn("Couldn't delete : " + id + " Ex : " + ex);
}
@ -53,8 +52,7 @@ public class BulkDelete extends BulkOperator {
}
this.addCompleted();
}
this.completedAfter();
return affected;
return getSucceeded();
}
}

View file

@ -19,17 +19,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.service.base;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Query;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.util.support.QueryHelper;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.helper.task.HeavyTask;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.query.AdvFilterParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.HashSet;
import java.util.Set;
/**
* 批量操作
*
*
* @author devezhao
* @since 10/16/2018
*/
@ -39,9 +45,9 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
final protected BulkContext context;
final protected GeneralEntityService ges;
private ID[] records;
/**
* @param context
* @param ges 可避免多次经由拦截器检查
@ -51,7 +57,7 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
this.context = context;
this.ges = ges;
}
/**
* 获取待操作记录
*
@ -67,17 +73,26 @@ public abstract class BulkOperator extends HeavyTask<Integer> {
setTotal(this.records.length);
return this.records;
}
JSONObject filterExp = context.getFilterExp();
AdvFilterParser filterParser = new AdvFilterParser(filterExp);
String sqlWhere = filterParser.toSqlWhere();
Entity entity = MetadataHelper.getEntity(filterExp.getString("entity"));
String sql = "select %s from %s where (1=1) and " + sqlWhere;
sql = String.format(sql, entity.getPrimaryField().getName(), entity.getName());
// TODO 解析过滤并查询结果
throw new UnsupportedOperationException();
JSONObject asFilterExp = context.getCustomData();
AdvFilterParser filterParser = new AdvFilterParser(asFilterExp);
String sqlWhere = filterParser.toSqlWhere();
// `(1=1)`.length < 10
if (sqlWhere.length() < 10) {
throw new SecurityException("Must specify filter items : " + sqlWhere);
}
Entity entity = MetadataHelper.getEntity(asFilterExp.getString("entity"));
String sql = String.format("select %s from %s where (1=1) and %s",
entity.getPrimaryField().getName(), entity.getName(), sqlWhere);
// NOTE 注意没有分页
Query query = Application.getQueryFactory().createQuery(sql, context.getOpUser());
Object[][] array = QueryHelper.readArray(query);
Set<ID> ids = new HashSet<>();
for (Object[] o : array) {
ids.add((ID) o[0]);
}
return ids.toArray(new ID[0]);
}
}

View file

@ -1,65 +0,0 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.service.base;
import cn.devezhao.persist4j.engine.ID;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Using:
* <tt>begin</tt>
* <tt>[getInTxSet]</tt>
* <tt>[isInTx]</tt>
* <tt>end</tt>
*
* @author devezhao zhaofang123@gmail.com
* @since 2019/03/23
*/
public class BulkOperatorTx {
private static final ThreadLocal<Set<ID>> STATE = new ThreadLocal<>();
/**
*/
public static void begin() {
STATE.set(new LinkedHashSet<>());
}
/**
* @return
*/
public static Set<ID> getInTxSet() {
return STATE.get();
}
/**
* @return
*/
public static boolean isInTx() {
return STATE.get() != null;
}
/**
*/
public static void end() {
STATE.remove();
}
}

View file

@ -25,6 +25,7 @@ import com.rebuild.server.Application;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.service.OperatingContext;
import com.rebuild.server.service.notification.NotificationObserver;
import com.rebuild.server.service.notification.NotificationOnce;
import java.util.Set;
@ -36,23 +37,22 @@ import java.util.Set;
*/
public class BulkShare extends BulkOperator {
public BulkShare(BulkContext context, GeneralEntityService ges) {
protected BulkShare(BulkContext context, GeneralEntityService ges) {
super(context, ges);
}
@Override
protected Integer exec() {
ID[] records = prepareRecords();
final ID[] records = prepareRecords();
this.setTotal(records.length);
int shared = 0;
ID firstShared = null;
BulkOperatorTx.begin();
NotificationOnce.begin();
for (ID id : records) {
if (Application.getSecurityManager().allowedS(context.getOpUser(), id)) {
if (Application.getSecurityManager().allowShare(context.getOpUser(), id)) {
int a = ges.share(id, context.getToUser(), context.getCascades());
if (a > 0) {
shared += a;
this.addSucceeded();
if (firstShared == null) {
firstShared = id;
}
@ -62,10 +62,9 @@ public class BulkShare extends BulkOperator {
}
this.addCompleted();
}
Set<ID> affected = BulkOperatorTx.getInTxSet();
BulkOperatorTx.end();
// 合并通知发送
Set<ID> affected = NotificationOnce.end();
if (firstShared != null && !affected.isEmpty()) {
Record notificationNeeds = EntityHelper.forNew(EntityHelper.ShareAccess, context.getOpUser());
notificationNeeds.setID("shareTo", context.getToUser());
@ -75,8 +74,7 @@ public class BulkShare extends BulkOperator {
context.getOpUser(), BizzPermission.SHARE, null, notificationNeeds, affected.toArray(new ID[0]));
new NotificationObserver().update(null, operatingContext);
}
this.completedAfter();
return shared;
return getSucceeded();
}
}

View file

@ -35,25 +35,25 @@ public class BulkUnshare extends BulkOperator {
@Override
protected Integer exec() {
ID[] records = prepareRecords();
final ID[] records = prepareRecords();
this.setTotal(records.length);
int unshared = 0;
ID realTarget = context.getTargetRecord();
final ID realTarget = context.getTargetRecord();
// 只需要验证主记录权限
if (!Application.getSecurityManager().allowedS(context.getOpUser(), realTarget)) {
if (!Application.getSecurityManager().allowShare(context.getOpUser(), realTarget)) {
this.setCompleted(records.length);
return unshared;
return 0;
}
for (ID id : records) {
int a = ges.unshare(realTarget, id);
unshared += (a > 0 ? 1 : 0);
if (a > 0) {
this.addSucceeded();
}
this.addCompleted();
}
this.completedAfter();
return unshared;
return getSucceeded();
}
}

View file

@ -151,7 +151,7 @@ public class GeneralEntityService extends ObservableService {
}
for (ID id : e.getValue()) {
if (Application.getSecurityManager().allowedD(currentUser, id)) {
if (Application.getSecurityManager().allowDelete(currentUser, id)) {
if (recycleBin != null) {
recycleBin.add(id, record);
}
@ -381,7 +381,9 @@ public class GeneralEntityService extends ObservableService {
return new BulkShare(context, this);
} else if (context.getAction() == UNSHARE) {
return new BulkUnshare(context, this);
}
} else if (context.getAction() == BizzPermission.UPDATE) {
return new BulkBacthUpdate(context, this);
}
throw new UnsupportedOperationException("Unsupported bulk action : " + context.getAction());
}
@ -467,9 +469,7 @@ public class GeneralEntityService extends ObservableService {
Assert.isNull(recordOfNew.getPrimary(), "Must be new record");
Entity entity = recordOfNew.getEntity();
if (MetadataHelper.isBizzEntity(entity.getEntityCode())
|| !MetadataHelper.hasPrivilegesField(entity)) {
LOG.warn("Could't append Bizz and non-business entities : " + entity.getName());
if (MetadataHelper.isBizzEntity(entity.getEntityCode()) || !MetadataHelper.hasPrivilegesField(entity)) {
return;
}
@ -494,7 +494,7 @@ public class GeneralEntityService extends ObservableService {
Field[] seriesFields = MetadataSorter.sortFields(record.getEntity(), DisplayType.SERIES);
for (Field field : seriesFields) {
// 导入模式不强制生成
if (record.hasValue(field.getName()) && DataImporter.isInImporting()) {
if (record.hasValue(field.getName()) && DataImporter.inImportingState()) {
continue;
}
record.setString(field.getName(), SeriesGeneratorFactory.generate(field));

View file

@ -75,7 +75,7 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
if (AdminGuard.class.isAssignableFrom(invocationClass) && !UserHelper.isAdmin(caller)) {
throw new AccessDeniedException("非法操作请求 (E" + ((ServiceSpec) invocation.getThis()).getEntityCode() + ")");
}
// EntityService 或子类需要继续验证角色权限
// EntityService 或子类验证角色权限
if (!EntityService.class.isAssignableFrom(invocationClass)) {
return;
}
@ -88,9 +88,8 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
}
BulkContext context = (BulkContext) first;
Entity entity = context.getMainEntity();
if (!Application.getSecurityManager().allowed(caller, entity.getEntityCode(), context.getAction())) {
if (!Application.getSecurityManager().allow(caller, entity.getEntityCode(), context.getAction())) {
LOG.error("User [ " + caller + " ] not allowed execute action [ " + context.getAction() + " ]. Entity : " + context.getMainEntity());
throw new AccessDeniedException(formatHumanMessage(context.getAction(), entity, null));
}
@ -99,8 +98,8 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
Object idOrRecord = invocation.getArguments()[0];
ID recordId = null;
Entity entity = null;
ID recordId;
Entity entity;
if (idOrRecord instanceof Record) {
recordId = ((Record) idOrRecord).getPrimary();
@ -114,18 +113,20 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
Permission action = getPermissionByMethod(invocation.getMethod(), recordId == null);
boolean isAllowed = false;
boolean allowed;
if (action == BizzPermission.CREATE) {
// 明细实体
if (entity.getMasterEntity() != null) {
Field field = MetadataHelper.getSlaveToMasterField(entity);
Assert.notNull(field, "No STM field found : " + entity);
ID masterId = ((Record) idOrRecord).getID(field.getName());
if (masterId == null || !Application.getSecurityManager().allowedU(caller, masterId)) {
if (masterId == null || !Application.getSecurityManager().allowUpdate(caller, masterId)) {
throw new AccessDeniedException("你没有添加明细的权限");
}
isAllowed = true;
allowed = true;
} else {
isAllowed = Application.getSecurityManager().allowed(caller, entity.getEntityCode(), action);
allowed = Application.getSecurityManager().allow(caller, entity.getEntityCode(), action);
}
} else {
@ -133,17 +134,17 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
throw new IllegalArgumentException("No primary in record!");
}
isAllowed = Application.getSecurityManager().allowed(caller, recordId, action);
allowed = Application.getSecurityManager().allow(caller, recordId, action);
}
// 无权限操作
if (!isAllowed && IN_NOPERMISSION_PASS.get() != null) {
isAllowed = true;
if (!allowed && IN_NOPERMISSION_PASS.get() != null) {
allowed = true;
IN_NOPERMISSION_PASS.remove();
LOG.warn("Allow no permission passed : " + recordId);
}
if (!isAllowed) {
if (!allowed) {
LOG.error("User [ " + caller + " ] not allowed execute action [ " + action + " ]. " + (recordId == null ? "Entity : " + entity : "Record : " + recordId));
throw new AccessDeniedException(formatHumanMessage(action, entity, recordId));
}

View file

@ -26,6 +26,7 @@ import cn.devezhao.bizz.privileges.impl.BizzDepthEntry;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.bizz.security.member.Role;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Filter;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application;
@ -35,8 +36,6 @@ import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.EntityService;
import com.rebuild.server.service.bizz.RoleService;
import com.rebuild.server.service.bizz.UserService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
@ -51,10 +50,8 @@ import org.springframework.util.Assert;
*/
public class SecurityManager {
private static final Log LOG = LogFactory.getLog(SecurityManager.class);
final private UserStore theUserStore;
final private RecordOwningCache theRecordOwning;
final private RecordOwningCache theRecordOwningCache;
/**
* @param us
@ -62,7 +59,7 @@ public class SecurityManager {
*/
protected SecurityManager(UserStore us, RecordOwningCache roc) {
this.theUserStore = us;
this.theRecordOwning = roc;
this.theRecordOwningCache = roc;
}
/**
@ -70,7 +67,7 @@ public class SecurityManager {
* @return
*/
public ID getOwningUser(ID record) {
return theRecordOwning.getOwningUser(record);
return theRecordOwningCache.getOwningUser(record);
}
/**
@ -87,18 +84,7 @@ public class SecurityManager {
} else if (u.isAdmin()) {
return Privileges.ROOT;
}
return u.getOwningRole().getPrivileges(entity);
}
/**
* 获取真实的权限实体
*
* @param entity
* @return
*/
public int getPrivilegesEntity(int entity) {
Entity em = MetadataHelper.getEntity(entity);
return em.getMasterEntity() == null ? entity : em.getMasterEntity().getEntityCode();
return u.getOwningRole().getPrivileges(convert2MasterEntity(entity));
}
/**
@ -108,8 +94,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedC(ID user, int entity) {
return allowed(user, entity, BizzPermission.CREATE);
public boolean allowCreate(ID user, int entity) {
return allow(user, entity, BizzPermission.CREATE);
}
/**
@ -119,8 +105,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedD(ID user, int entity) {
return allowed(user, entity, BizzPermission.DELETE);
public boolean allowDelete(ID user, int entity) {
return allow(user, entity, BizzPermission.DELETE);
}
/**
@ -130,8 +116,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedU(ID user, int entity) {
return allowed(user, entity, BizzPermission.UPDATE);
public boolean allowUpdate(ID user, int entity) {
return allow(user, entity, BizzPermission.UPDATE);
}
/**
@ -141,8 +127,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedR(ID user, int entity) {
return allowed(user, entity, BizzPermission.READ);
public boolean allowRead(ID user, int entity) {
return allow(user, entity, BizzPermission.READ);
}
/**
@ -152,8 +138,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedA(ID user, int entity) {
return allowed(user, entity, BizzPermission.ASSIGN);
public boolean allowAssign(ID user, int entity) {
return allow(user, entity, BizzPermission.ASSIGN);
}
/**
@ -163,8 +149,8 @@ public class SecurityManager {
* @param entity
* @return
*/
public boolean allowedS(ID user, int entity) {
return allowed(user, entity, BizzPermission.SHARE);
public boolean allowShare(ID user, int entity) {
return allow(user, entity, BizzPermission.SHARE);
}
/**
@ -174,8 +160,8 @@ public class SecurityManager {
* @param target
* @return
*/
public boolean allowedD(ID user, ID target) {
return allowed(user, target, BizzPermission.DELETE);
public boolean allowDelete(ID user, ID target) {
return allow(user, target, BizzPermission.DELETE);
}
/**
@ -185,8 +171,8 @@ public class SecurityManager {
* @param target
* @return
*/
public boolean allowedU(ID user, ID target) {
return allowed(user, target, BizzPermission.UPDATE);
public boolean allowUpdate(ID user, ID target) {
return allow(user, target, BizzPermission.UPDATE);
}
/**
@ -196,8 +182,8 @@ public class SecurityManager {
* @param target
* @return
*/
public boolean allowedR(ID user, ID target) {
return allowed(user, target, BizzPermission.READ);
public boolean allowRead(ID user, ID target) {
return allow(user, target, BizzPermission.READ);
}
/**
@ -207,8 +193,8 @@ public class SecurityManager {
* @param target
* @return
*/
public boolean allowedA(ID user, ID target) {
return allowed(user, target, BizzPermission.ASSIGN);
public boolean allowAssign(ID user, ID target) {
return allow(user, target, BizzPermission.ASSIGN);
}
/**
@ -218,8 +204,8 @@ public class SecurityManager {
* @param target
* @return
*/
public boolean allowedS(ID user, ID target) {
return allowed(user, target, BizzPermission.SHARE);
public boolean allowShare(ID user, ID target) {
return allow(user, target, BizzPermission.SHARE);
}
/**
@ -230,8 +216,8 @@ public class SecurityManager {
* @param action 权限动作
* @return
*/
public boolean allowed(ID user, int entity, Permission action) {
Boolean a = allowedUser(user);
public boolean allow(ID user, int entity, Permission action) {
Boolean a = userAllow(user);
if (a != null) {
return a;
}
@ -261,7 +247,7 @@ public class SecurityManager {
action = convert2MasterAction(action);
}
Privileges ep = role.getPrivileges(getPrivilegesEntity(entity));
Privileges ep = role.getPrivileges(convert2MasterEntity(entity));
return ep.allowed(action);
}
@ -273,8 +259,8 @@ public class SecurityManager {
* @param action 权限动作
* @return
*/
public boolean allowed(ID user, ID target, Permission action) {
Boolean a = allowedUser(user);
public boolean allow(ID user, ID target, Permission action) {
Boolean a = userAllow(user);
if (a != null) {
return a;
}
@ -302,7 +288,7 @@ public class SecurityManager {
action = convert2MasterAction(action);
}
Privileges ep = role.getPrivileges(getPrivilegesEntity(entity));
Privileges ep = role.getPrivileges(convert2MasterEntity(entity));
boolean allowed = ep.allowed(action);
if (!allowed) {
@ -317,7 +303,7 @@ public class SecurityManager {
return true;
}
ID targetUserId = theRecordOwning.getOwningUser(target);
ID targetUserId = theRecordOwningCache.getOwningUser(target);
if (targetUserId == null) {
return false;
}
@ -325,7 +311,7 @@ public class SecurityManager {
if (BizzDepthEntry.PRIVATE.equals(depth)) {
allowed = user.equals(targetUserId);
if (!allowed) {
return allowedViaShare(user, target, action);
return allowViaShare(user, target, action);
}
return true;
}
@ -337,7 +323,7 @@ public class SecurityManager {
if (BizzDepthEntry.LOCAL.equals(depth)) {
allowed = accessUserDept.equals(targetUser.getOwningDept());
if (!allowed) {
return allowedViaShare(user, target, action);
return allowViaShare(user, target, action);
}
return true;
} else if (BizzDepthEntry.DEEPDOWN.equals(depth)) {
@ -347,7 +333,7 @@ public class SecurityManager {
allowed = accessUserDept.isChildrenAll(targetUser.getOwningDept());
if (!allowed) {
return allowedViaShare(user, target, action);
return allowViaShare(user, target, action);
}
return true;
}
@ -363,7 +349,7 @@ public class SecurityManager {
* @param action
* @return
*/
public boolean allowedViaShare(ID user, ID target, Permission action) {
public boolean allowViaShare(ID user, ID target, Permission action) {
// TODO 目前只共享了读取权限
// TODO 性能优化-缓存
@ -392,6 +378,17 @@ public class SecurityManager {
int rightsVal = rights == null ? 0 : (int) rights[0];
return (rightsVal & BizzPermission.READ.getMask()) != 0;
}
/**
* 获取真实的权限实体如明细的权限依赖主实体
*
* @param entity
* @return
*/
private int convert2MasterEntity(int entity) {
Entity em = MetadataHelper.getEntity(entity);
return em.getMasterEntity() == null ? entity : em.getMasterEntity().getEntityCode();
}
/**
* 转换明细实体的权限<tt>删除/新建/更新</tt>明细记录等于修改主实体因此要转换成<tt>更新</tt>权限
@ -414,14 +411,52 @@ public class SecurityManager {
*/
private ID getMasterRecordId(ID slaveId) {
Entity entity = MetadataHelper.getEntity(slaveId.getEntityCode());
Entity masterEntity = entity.getMasterEntity();
Assert.isTrue(masterEntity != null, "Non slave entty : " + slaveId);
String sql = "select %s from %s where %s = '%s'";
sql = String.format(sql, masterEntity.getPrimaryField().getName(), entity.getName(), entity.getPrimaryField().getName(), slaveId.toLiteral());
Object[] primary = Application.getQueryFactory().createQueryNoFilter(sql).unique();
Field stmField = MetadataHelper.getSlaveToMasterField(entity);
Assert.isTrue(stmField != null, "Non slave entty : " + slaveId);
Object[] primary = Application.getQueryFactory().uniqueNoFilter(slaveId, stmField.getName());
return primary == null ? null : (ID) primary[0];
}
/**
* 扩展权限
*
* @param user
* @param entry
* @return
* @see ZeroPrivileges
* @see ZeroPermission
*/
public boolean allow(ID user, ZeroEntry entry) {
Boolean a = userAllow(user);
if (a != null) {
return a;
}
Role role = theUserStore.getUser(user).getOwningRole();
if (RoleService.ADMIN_ROLE.equals(role.getIdentity())) {
return true;
}
if (role.hasPrivileges(entry.name())) {
return role.getPrivileges(entry.name()).allowed(ZeroPermission.ZERO);
}
return entry.getDefaultVal();
}
/**
* @param user
* @returny
*/
private Boolean userAllow(ID user) {
if (UserService.ADMIN_USER.equals(user)) {
return true;
}
if (!theUserStore.getUser(user).isActive()) {
return false;
}
return null;
}
/**
* 创建查询过滤器
@ -447,44 +482,4 @@ public class SecurityManager {
}
return new EntityQueryFilter(theUser, action);
}
/**
* 扩展权限
*
* @param user
* @param entry
* @return
* @see ZeroPrivileges
* @see ZeroPermission
*/
public boolean allowed(ID user, ZeroEntry entry) {
Boolean a = allowedUser(user);
if (a != null) {
return a;
}
Role role = theUserStore.getUser(user).getOwningRole();
if (RoleService.ADMIN_ROLE.equals(role.getIdentity())) {
return true;
}
if (role.hasPrivileges(entry.name())) {
return role.getPrivileges(entry.name()).allowed(ZeroPermission.ZERO);
}
return entry.getDefaultVal();
}
/**
* @param user
* @return
*/
private Boolean allowedUser(ID user) {
if (UserService.ADMIN_USER.equals(user)) {
return true;
}
if (!theUserStore.getUser(user).isActive()) {
return false;
}
return null;
}
}

View file

@ -26,11 +26,29 @@ package com.rebuild.server.service.bizz.privileges;
*/
public enum ZeroEntry {
/**
* 允许登录
*/
AllowLogin(true),
/**
* 允许批量修改
*/
AllowBatchUpdate(false),
/**
* 允许导入
*/
AllowDataImport(false),
/**
* 允许导出
*/
AllowDataExport(false),
/**
* 允许自定义导航菜单
*/
AllowCustomNav(true),
/**
* 允许自定义列表显示列
*/
AllowCustomDataList(true),
;

View file

@ -24,7 +24,6 @@ import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.server.service.OperatingContext;
import com.rebuild.server.service.OperatingObserver;
import com.rebuild.server.service.base.BulkOperatorTx;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
@ -39,8 +38,8 @@ public class NotificationObserver extends OperatingObserver {
@Override
public void onAssign(OperatingContext context) {
final ID related = context.getAfterRecord().getPrimary();
if (BulkOperatorTx.isInTx()) {
BulkOperatorTx.getInTxSet().add(related);
if (NotificationOnce.didBegin()) {
NotificationOnce.getMergeSet().add(related);
return;
}
@ -56,8 +55,8 @@ public class NotificationObserver extends OperatingObserver {
@Override
public void onShare(OperatingContext context) {
final ID related = context.getAfterRecord().getID("recordId");
if (BulkOperatorTx.isInTx()) {
BulkOperatorTx.getInTxSet().add(related);
if (NotificationOnce.didBegin()) {
NotificationOnce.getMergeSet().add(related);
return;
}

View file

@ -0,0 +1,77 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.server.service.notification;
import cn.devezhao.persist4j.engine.ID;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 消息合并发送用于批量操作时合并消息
* Using:
* <tt>begin</tt>
* <tt>[didBegin]</tt>
* <tt>[getMergeSet]</tt>
* <tt>end</tt>
*
* @author ZHAO
* @since 2019/12/2
*/
public class NotificationOnce {
private static final ThreadLocal<Set<ID>> STATE = new ThreadLocal<>();
/**
* 启动合并此后线程中的消息发送将忽略
*/
public static void begin() {
STATE.set(new LinkedHashSet<>());
}
/**
* 是否开始了
*
* @return
*/
public static boolean didBegin() {
return STATE.get() != null;
}
/**
* 合并结束
*
* @return
*/
public static Set<ID> end() {
Set<ID> set = getMergeSet();
STATE.remove();
return Collections.unmodifiableSet(set);
}
/**
* 被合并到一次发送的记录
*
* @return
*/
protected static Set<ID> getMergeSet() {
return STATE.get();
}
}

View file

@ -170,8 +170,8 @@ public class AppUtils {
* @return
* @see com.rebuild.server.service.bizz.privileges.SecurityManager
*/
public static boolean allowed(HttpServletRequest request, ZeroEntry entry) {
return Application.getSecurityManager().allowed(getRequestUser(request), entry);
public static boolean allow(HttpServletRequest request, ZeroEntry entry) {
return Application.getSecurityManager().allow(getRequestUser(request), entry);
}
public static final String SK_LOCALE = WebUtils.KEY_PREFIX + ".LOCALE";

View file

@ -70,7 +70,7 @@ public class JSONUtils {
* @param valuesArray
* @return
*/
public static JSONArray toJSONArray(String[] keys, Object[][] valuesArray) {
public static JSONArray toJSONObjectArray(String[] keys, Object[][] valuesArray) {
List<Map<String, Object>> array = new ArrayList<>();
for (Object[] o : valuesArray) {
Map<String, Object> map = new HashMap<>(keys.length);

View file

@ -100,7 +100,7 @@ public abstract class BaseEntityControll extends BasePageControll {
};
Map<String, Boolean> actionMap = new HashMap<>();
for (Permission act : actions) {
actionMap.put(act.getName(), Application.getSecurityManager().allowed(user, record, act));
actionMap.put(act.getName(), Application.getSecurityManager().allow(user, record, act));
}
mv.getModel().put("entityPrivileges", JSON.toJSONString(actionMap));
} else {

View file

@ -70,7 +70,7 @@ public class SysConfigurationControll extends BasePageControll {
return mv;
}
@RequestMapping("systems/submail")
@RequestMapping("integration/submail")
public ModelAndView pageIntegrationSubmail() {
ModelAndView mv = createModelAndView("/admin/integration/submail.jsp");
mv.getModel().put("smsAccount",

View file

@ -85,7 +85,7 @@ public class RolePrivilegesControll extends BaseEntityControll {
@RequestMapping("role-list")
public void roleList(HttpServletRequest request, HttpServletResponse response) throws IOException {
Object[][] array = Application.createQuery("select roleId,name,isDisabled from Role").array();
JSON retJson = JSONUtils.toJSONArray(new String[] { "id", "name", "disabled" }, array);
JSON retJson = JSONUtils.toJSONObjectArray(new String[] { "id", "name", "disabled" }, array);
writeSuccess(response, retJson);
}
@ -107,7 +107,7 @@ public class RolePrivilegesControll extends BaseEntityControll {
}
}
JSON retJson = JSONUtils.toJSONArray(new String[] { "name", "definition" }, array);
JSON retJson = JSONUtils.toJSONObjectArray(new String[] { "name", "definition" }, array);
writeSuccess(response, retJson);
}

View file

@ -19,7 +19,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.web.admin.entityhub;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ThreadPool;
import cn.devezhao.commons.excel.Cell;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity;
@ -27,14 +26,11 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.ToStringSerializer;
import com.rebuild.server.Application;
import com.rebuild.server.business.dataimport.DataFileParser;
import com.rebuild.server.business.dataimport.DataImporter;
import com.rebuild.server.business.dataimport.ImportRule;
import com.rebuild.server.helper.SysConfiguration;
import com.rebuild.server.helper.task.HeavyTask;
import com.rebuild.server.helper.task.TaskExecutors;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
@ -57,20 +53,15 @@ import java.util.List;
import java.util.Map;
/**
*
* @author devezhao
* @since 01/03/2019
*/
@Controller
@RequestMapping("/admin/datas/")
public class DataImportControll extends BasePageControll {
static {
SerializeConfig.getGlobalInstance().put(Cell.class, ToStringSerializer.instance);
}
@RequestMapping("/data-importer")
public ModelAndView pageDataImports(HttpServletRequest request) {
public ModelAndView pageDataImports() {
return createModelAndView("/admin/entityhub/data-importer.jsp");
}
@ -84,9 +75,9 @@ public class DataImportControll extends BasePageControll {
return;
}
DataFileParser parser = null;
DataFileParser parser;
int count = -1;
List<Cell[]> preview = null;
List<Cell[]> preview;
try {
parser = new DataFileParser(tmp);
preview = parser.parse(11);
@ -100,15 +91,16 @@ public class DataImportControll extends BasePageControll {
new String[] { "count", "preview" }, new Object[] { count, preview });
writeSuccess(response, ret);
}
@RequestMapping("/data-importer/check-user-privileges")
// 检查所属用户权限
@RequestMapping("/data-importer/check-user")
public void checkUserPrivileges(HttpServletRequest request, HttpServletResponse response) throws IOException {
ID ouser = getIdParameterNotNull(request, "ouser");
ID user = getIdParameterNotNull(request, "user");
String entity = getParameterNotNull(request, "entity");
Entity entityMeta = MetadataHelper.getEntity(entity);
boolean canCreated = Application.getSecurityManager().allowedC(ouser, entityMeta.getEntityCode());
boolean canUpdated = Application.getSecurityManager().allowedU(ouser, entityMeta.getEntityCode());
boolean canCreated = Application.getSecurityManager().allowCreate(user, entityMeta.getEntityCode());
boolean canUpdated = Application.getSecurityManager().allowUpdate(user, entityMeta.getEntityCode());
JSON ret = JSONUtils.toJSONObject(
new String[] { "canCreate", "canUpdate" }, new Object[] { canCreated, canUpdated });
@ -123,10 +115,10 @@ public class DataImportControll extends BasePageControll {
List<Map<String, Object>> list = new ArrayList<>();
for (Field field : MetadataSorter.sortFields(entityBase)) {
String fieldName = field.getName();
if (EntityHelper.OwningDept.equals(fieldName)) {
if (EntityHelper.OwningDept.equals(fieldName)
|| MetadataHelper.isApprovalField(fieldName) || MetadataHelper.isSystemField(fieldName)) {
continue;
}
EasyMeta easyMeta = new EasyMeta(field);
if (easyMeta.getDisplayType() == DisplayType.FILE || easyMeta.getDisplayType() == DisplayType.IMAGE) {
continue;
@ -136,7 +128,7 @@ public class DataImportControll extends BasePageControll {
map.put("name", fieldName);
map.put("label", easyMeta.getLabel());
map.put("type", easyMeta.getDisplayType().getDisplayName());
map.put("isNullable", field.isNullable());
map.put("nullable", field.isNullable());
String defaultValue = null;
if (EntityHelper.CreatedOn.equals(fieldName) || EntityHelper.ModifiedOn.equals(fieldName)) {
@ -158,7 +150,7 @@ public class DataImportControll extends BasePageControll {
@RequestMapping("/data-importer/import-submit")
public void importSubmit(HttpServletRequest request, HttpServletResponse response) throws IOException {
JSONObject idata = (JSONObject) ServletUtils.getRequestJson(request);
ImportRule irule = null;
ImportRule irule;
try {
irule = ImportRule.parse(idata);
} catch (IllegalArgumentException ex) {
@ -166,7 +158,7 @@ public class DataImportControll extends BasePageControll {
return;
}
DataImporter importer = new DataImporter(irule, getRequestUser(request));
DataImporter importer = new DataImporter(irule);
if (getBoolParameter(request, "preview")) {
// TODO 导入预览
} else {
@ -175,57 +167,7 @@ public class DataImportControll extends BasePageControll {
writeSuccess(response, ret);
}
}
// 导入状态
@RequestMapping("/data-importer/import-state")
public void importState(HttpServletRequest request, HttpServletResponse response) throws IOException {
String taskid = getParameterNotNull(request, "taskid");
HeavyTask<?> task = TaskExecutors.getTask(taskid);
if (task == null) {
writeFailure(response, "无效任务 : " + taskid);
return;
}
writeSuccess(response, formatTaskState((DataImporter) task));
}
// 导入取消
@RequestMapping("/data-importer/import-cancel")
public void importCancel(HttpServletRequest request, HttpServletResponse response) throws IOException {
String taskid = getParameterNotNull(request, "taskid");
HeavyTask<?> task = TaskExecutors.getTask(taskid);
if (task == null) {
writeFailure(response, "无效任务 : " + taskid);
return;
}
if (task.isCompleted()) {
writeFailure(response, "无法终止,因为导入任务已执行完成");
return;
}
task.interrupt();
for (int i = 0; i < 10; i++) {
if (task.isInterrupted()) {
writeSuccess(response, formatTaskState((DataImporter) task));
return;
}
ThreadPool.waitFor(200);
}
writeFailure(response);
}
/**
* @param task
* @return
*/
private JSON formatTaskState(DataImporter task) {
JSON state = JSONUtils.toJSONObject(
new String[] { "total", "complete", "success", "isCompleted", "isInterrupted", "elapsedTime" },
new Object[] { task.getTotal(), task.getCompleted(),
task.getSuccessed(), task.isCompleted(), task.isInterrupted(), task.getElapsedTime() });
return state;
}
/**
* @param file
* @return

View file

@ -18,33 +18,86 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.web.base;
import cn.devezhao.commons.ThreadPool;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.helper.task.HeavyTask;
import com.rebuild.server.helper.task.TaskExecutors;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseControll;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* 任务操作入口类
*
* @author devezhao
* @since 09/29/2018
*
* @see HeavyTask
* @see TaskExecutors
*/
@RequestMapping("/commons/task/")
@Controller
public class HeavyTaskControll extends BaseControll {
// 任务状态
@RequestMapping("state")
public void checkState(HttpServletRequest request, HttpServletResponse response) {
String taskid = getParameterNotNull(request, "taskid");
HeavyTask<?> task = TaskExecutors.getTask(taskid);
JSON ret = JSONUtils.toJSONObject(
new String[] { "taskid", "completed", "hasError" },
new Object[] { taskid, task.getCompletedPercent(), task.getErrorMessage() });
writeSuccess(response, ret);
if (task == null) {
writeFailure(response, "无效任务 : " + taskid);
return;
}
JSON state = formatTaskState(task);
writeSuccess(response, state);
}
// 中断任务
@RequestMapping("cancel")
public void importCancel(HttpServletRequest request, HttpServletResponse response) throws IOException {
String taskid = getParameterNotNull(request, "taskid");
HeavyTask<?> task = TaskExecutors.getTask(taskid);
if (task == null) {
writeFailure(response, "无效任务 : " + taskid);
return;
}
if (task.isCompleted()) {
writeFailure(response, "无法终止,因为任务已经完成");
return;
}
task.interrupt();
for (int i = 0; i < 10; i++) {
if (task.isInterrupted()) {
writeSuccess(response, formatTaskState(task));
return;
}
ThreadPool.waitFor(200);
}
writeFailure(response);
}
/**
* 格式化任务状态信息
*
* @param task
* @return
*/
public static JSON formatTaskState(HeavyTask<?> task) {
JSONObject state = new JSONObject();
state.put("progress", task.getCompletedPercent());
state.put("completed", task.getCompleted());
state.put("succeeded", task.getSucceeded());
state.put("isCompleted", task.isCompleted());
state.put("isInterrupted", task.isInterrupted());
state.put("elapsedTime", task.getElapsedTime());
state.put("hasError", task.getErrorMessage());
return state;
}
}

View file

@ -22,13 +22,7 @@ import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.portals.ClassificationManager;
import com.rebuild.server.configuration.portals.FieldPortalAttrs;
import com.rebuild.server.configuration.portals.MultiSelectManager;
import com.rebuild.server.configuration.portals.PickListManager;
import com.rebuild.server.helper.state.StateManager;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.MetadataSorter;
@ -97,14 +91,9 @@ public class MetadataGetting extends BaseControll {
int entityCode = field.getReferenceEntity().getEntityCode();
if (!(MetadataHelper.isBizzEntity(entityCode) || entityCode == EntityHelper.RobotApprovalConfig)) {
// 显示父级字段
Map<String, Object> parent = new HashMap<>();
parent.put("name", field.getName());
parent.put("label", easyField.getLabel());
parent.put("type", easyField.getDisplayType().name());
parent.put("creatable", field.isCreatable());
list.add(parent);
// 父级引用字段
list.add(buildField(field));
// 引用实体字段
putFields(list, field.getReferenceEntity(), false, easyField, fromType);
}
}
@ -127,14 +116,9 @@ public class MetadataGetting extends BaseControll {
continue;
}
Map<String, Object> map = new HashMap<>();
map.put("name", field.getName());
map.put("creatable", field.isCreatable());
EasyMeta easyMeta = new EasyMeta(field);
map.put("label", easyMeta.getLabel());
DisplayType dt = easyMeta.getDisplayType();
map.put("type", dt.name());
if (dt == DisplayType.REFERENCE) {
Map<String, Object> map = buildField(field);
// 引用字段处理
if (EasyMeta.getDisplayType(field) == DisplayType.REFERENCE) {
Entity refEntity = field.getReferenceEntity();
// Bizz 字段前台有特殊处理
boolean isBizzField = MetadataHelper.isBizzEntity(refEntity.getEntityCode());
@ -158,6 +142,22 @@ public class MetadataGetting extends BaseControll {
dest.add(map);
}
}
/**
* @param field
* @return
*/
public static Map<String, Object> buildField(Field field) {
Map<String, Object> map = new HashMap<>();
EasyMeta easyMeta = new EasyMeta(field);
map.put("name", field.getName());
map.put("label", easyMeta.getLabel());
map.put("type", easyMeta.getDisplayType().name());
map.put("nullable", field.isNullable());
map.put("creatable", field.isCreatable());
map.put("updatable", field.isUpdatable());
return map;
}
// 哪些实体引用了指定实体
@RequestMapping("references")
@ -172,7 +172,7 @@ public class MetadataGetting extends BaseControll {
references.add(own);
}
}
List<String[]> list = new ArrayList<>();
for (Entity e : references) {
EasyMeta easy = new EasyMeta(e);
@ -180,66 +180,4 @@ public class MetadataGetting extends BaseControll {
}
writeSuccess(response, list);
}
// --
// PickList/State 值列表
@RequestMapping({ "picklist", "field-options" })
public void fetchPicklist(HttpServletRequest request, HttpServletResponse response) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
Field fieldMeta = getRealField(entity, field);
DisplayType dt = EasyMeta.getDisplayType(fieldMeta);
JSON options = null;
if (dt == DisplayType.STATE) {
options = StateManager.instance.getStateOptions(fieldMeta);
}
else if (dt == DisplayType.MULTISELECT) {
options = MultiSelectManager.instance.getSelectList(fieldMeta);
}
else {
options = PickListManager.instance.getPickList(fieldMeta);
}
writeSuccess(response, options);
}
// Classification 值列表
@RequestMapping("classification")
public void fetchClassification(HttpServletRequest request, HttpServletResponse response) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
Field fieldMeta = getRealField(entity, field);
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, true);
if (useClassification == null) {
writeFailure(response, "分类字段配置有误");
return;
}
ID parent = getIdParameter(request, "parent");
String sql = "select itemId,name from ClassificationData where dataId = ? and isHide = 'F' and ";
if (parent != null) {
sql += "parent = '" + parent + "'";
} else {
sql += "parent is null";
}
sql += " order by code, name";
Object[][] data = Application.createQueryNoFilter(sql)
.setParameter(1, useClassification)
.setLimit(500) // 最多显示
.array();
writeSuccess(response, data);
}
/**
* @param entity
* @param field
* @return
*/
private Field getRealField(String entity, String field) {
Entity entityMeta = MetadataHelper.getEntity(entity);
return MetadataHelper.getLastJoinField(entityMeta, field);
}
}

View file

@ -0,0 +1,104 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.web.base;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.portals.ClassificationManager;
import com.rebuild.server.configuration.portals.MultiSelectManager;
import com.rebuild.server.configuration.portals.PickListManager;
import com.rebuild.server.helper.state.StateManager;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.entity.DisplayType;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.web.BaseControll;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 下拉列表型字段值列表
*
* @author ZHAO
* @since 2019/12/1
*/
@Controller
@RequestMapping("/commons/metadata/")
public class PicklistDataControll extends BaseControll {
// for PickList/State
@RequestMapping({ "picklist", "field-options" })
public void fetchPicklist(HttpServletRequest request, HttpServletResponse response) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
Field fieldMeta = getRealField(entity, field);
DisplayType dt = EasyMeta.getDisplayType(fieldMeta);
JSON options;
if (dt == DisplayType.STATE) {
options = StateManager.instance.getStateOptions(fieldMeta);
}
else if (dt == DisplayType.MULTISELECT) {
options = MultiSelectManager.instance.getSelectList(fieldMeta);
}
else {
options = PickListManager.instance.getPickList(fieldMeta);
}
writeSuccess(response, options);
}
// for Classification
@RequestMapping("classification")
public void fetchClassification(HttpServletRequest request, HttpServletResponse response) {
String entity = getParameterNotNull(request, "entity");
String field = getParameterNotNull(request, "field");
Field fieldMeta = getRealField(entity, field);
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, true);
if (useClassification == null) {
writeFailure(response, "分类字段配置有误");
return;
}
ID parent = getIdParameter(request, "parent");
String sql = "select itemId,name from ClassificationData where dataId = ? and isHide = 'F' and ";
if (parent != null) {
sql += "parent = '" + parent + "'";
} else {
sql += "parent is null";
}
sql += " order by code, name";
Object[][] data = Application.createQueryNoFilter(sql)
.setParameter(1, useClassification)
.setLimit(500) // 最多显示
.array();
writeSuccess(response, data);
}
private Field getRealField(String entity, String fieldPath) {
Entity entityMeta = MetadataHelper.getEntity(entity);
return MetadataHelper.getLastJoinField(entityMeta, fieldPath);
}
}

View file

@ -71,7 +71,7 @@ public class DataListSettingsControll extends BaseControll implements PortalsCon
public void sets(@PathVariable String entity,
HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomDataList), "没有权限");
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomDataList), "没有权限");
JSON config = ServletUtils.getRequestJson(request);
ID cfgid = getIdParameter(request, "id");
@ -116,7 +116,7 @@ public class DataListSettingsControll extends BaseControll implements PortalsCon
}
Entity refEntity = field.getReferenceEntity();
// 无权限的不返回
if (!Application.getSecurityManager().allowedR(user, refEntity.getEntityCode())) {
if (!Application.getSecurityManager().allowRead(user, refEntity.getEntityCode())) {
continue;
}

View file

@ -59,7 +59,7 @@ public class NavSettings extends BaseControll implements PortalsConfiguration {
@RequestMapping(value = "nav-settings", method = RequestMethod.POST)
public void sets(HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomNav), "没有权限");
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomNav), "没有权限");
JSON config = ServletUtils.getRequestJson(request);
ID cfgid = getIdParameter(request, "id");

View file

@ -0,0 +1,142 @@
/*
rebuild - Building your business-systems freely.
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rebuild.web.base.general;
import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.portals.MultiSelectManager;
import com.rebuild.server.configuration.portals.PickListManager;
import com.rebuild.server.helper.state.StateManager;
import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.metadata.MetadataSorter;
import com.rebuild.server.metadata.entity.DisplayType;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.server.metadata.entity.FieldExtConfigProps;
import com.rebuild.server.service.EntityService;
import com.rebuild.server.service.ServiceSpec;
import com.rebuild.server.service.base.BulkContext;
import com.rebuild.server.service.bizz.privileges.ZeroEntry;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseControll;
import com.rebuild.web.base.MetadataGetting;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 批量修改
*
* @author ZHAO
* @since 2019/12/1
*/
@Controller
@RequestMapping("/app/{entity}/")
public class BatchUpdateControll extends BaseControll {
@RequestMapping("batch-update/submit")
public void submit(@PathVariable String entity,
HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowBatchUpdate), "没有权限");
JSONObject requestData = (JSONObject) ServletUtils.getRequestJson(request);
int dataRange = getIntParameter(request, "dr", 2);
requestData.put("_dataRange", dataRange);
requestData.put("entity", entity);
BulkContext bulkContext = new BulkContext(user, BizzPermission.UPDATE, requestData);
Entity entityMeta = MetadataHelper.getEntity(entity);
ServiceSpec ies = Application.getService(entityMeta.getEntityCode());
String taskid = ((EntityService) ies).bulkAsync(bulkContext);
writeSuccess(response, taskid);
}
// 获取可更新字段
@RequestMapping("batch-update/fields")
public void getFields(@PathVariable String entity, HttpServletResponse response) throws IOException {
Entity entityMeta = MetadataHelper.getEntity(entity);
List<Map<String, Object>> updatableFields = new ArrayList<>();
for (Field field : MetadataSorter.sortFields(entityMeta)) {
if (MetadataHelper.isSystemField(field) || !field.isUpdatable()) {
continue;
}
EasyMeta easyMeta = EasyMeta.valueOf(field);
if (!easyMeta.isUpdatable()) {
continue;
}
DisplayType dt = easyMeta.getDisplayType();
// 不支持的字段
if (dt == DisplayType.FILE || dt == DisplayType.IMAGE || dt == DisplayType.AVATAR
|| dt == DisplayType.LOCATION || dt == DisplayType.SERIES || dt == DisplayType.ANYREFERENCE
|| dt == DisplayType.NTEXT) {
continue;
}
updatableFields.add(this.buildField(field, dt));
}
writeSuccess(response, updatableFields);
}
/**
* @param field
* @param dt
* @return
*/
private Map<String, Object> buildField(Field field, DisplayType dt) {
Map<String, Object> map = MetadataGetting.buildField(field);
// 字段选项
if (dt == DisplayType.PICKLIST) {
map.put("options", PickListManager.instance.getPickList(field));
} else if (dt == DisplayType.STATE) {
map.put("options", StateManager.instance.getStateOptions(field));
} else if (dt == DisplayType.MULTISELECT) {
map.put("options", MultiSelectManager.instance.getSelectList(field));
} else if (dt == DisplayType.BOOL) {
JSONArray options = new JSONArray();
options.add(JSONUtils.toJSONObject(new String[] { "id", "text" }, new Object[] { true, ""}));
options.add(JSONUtils.toJSONObject(new String[] { "id", "text" }, new Object[] { false, ""}));
map.put("options", options);
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
map.put(FieldExtConfigProps.NUMBER_NOTNEGATIVE,
EasyMeta.valueOf(field).getPropOfFieldExtConfig(FieldExtConfigProps.NUMBER_NOTNEGATIVE));
}
return map;
}
}

View file

@ -23,10 +23,12 @@ import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.business.dataimport.DataExporter;
import com.rebuild.server.helper.datalist.BatchOperatorQuery;
import com.rebuild.server.service.bizz.privileges.ZeroEntry;
import com.rebuild.web.BaseControll;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@ -41,25 +43,18 @@ import java.io.IOException;
@Controller
public class DataExportControll extends BaseControll {
@RequestMapping("/app/entity/data-export-submit")
public void export(HttpServletRequest request, HttpServletResponse response) throws IOException {
@RequestMapping("/app/{entity}/data-export/submit")
public void export(@PathVariable String entity,
HttpServletRequest request, HttpServletResponse response) throws IOException {
ID user = getRequestUser(request);
Assert.isTrue(Application.getSecurityManager().allowed(user, ZeroEntry.AllowDataExport), "没有权限");
Assert.isTrue(Application.getSecurityManager().allow(user, ZeroEntry.AllowDataExport), "没有权限");
int dataRange = getIntParameter(request, "dr", 2);
JSONObject query = (JSONObject) ServletUtils.getRequestJson(request);
if (query == null) {
query = new JSONObject();
}
if (dataRange == 3 || dataRange == 2) {
query.put("pageSize", 65535); // Max rows
query.put("pageNo", 1);
query.put("reload", false);
}
JSONObject queryData = (JSONObject) ServletUtils.getRequestJson(request);
queryData = new BatchOperatorQuery(dataRange, queryData).wrapQueryData(DataExporter.MAX_ROWS);
try {
File file = new DataExporter(query).setUser(user).export();
File file = new DataExporter(queryData).setUser(user).export();
writeSuccess(response, file.getName());
} catch (Exception ex) {
writeFailure(response, ex.getLocalizedMessage());

View file

@ -62,7 +62,7 @@ public class GeneralDataListControll extends BaseEntityControll {
}
Entity thatEntity = MetadataHelper.getEntity(entity);
if (!Application.getSecurityManager().allowedR(user, thatEntity.getEntityCode())) {
if (!Application.getSecurityManager().allowRead(user, thatEntity.getEntityCode())) {
response.sendError(403, "你没有访问此实体的权限");
return null;
}
@ -79,9 +79,11 @@ public class GeneralDataListControll extends BaseEntityControll {
// 列表相关权限
mv.getModel().put(ZeroEntry.AllowCustomDataList.name(),
Application.getSecurityManager().allowed(user, ZeroEntry.AllowCustomDataList));
Application.getSecurityManager().allow(user, ZeroEntry.AllowCustomDataList));
mv.getModel().put(ZeroEntry.AllowDataExport.name(),
Application.getSecurityManager().allowed(user, ZeroEntry.AllowDataExport));
Application.getSecurityManager().allow(user, ZeroEntry.AllowDataExport));
mv.getModel().put(ZeroEntry.AllowBatchUpdate.name(),
Application.getSecurityManager().allow(user, ZeroEntry.AllowBatchUpdate));
// 展开 WIDGET 面板
String asideCollapsed = ServletUtils.readCookie(request, "rb.asideCollapsed");

View file

@ -53,7 +53,7 @@ public class GeneralModelControll extends BaseEntityControll {
final ID user = getRequestUser(request);
Entity thatEntity = MetadataHelper.getEntity(entity);
if (!Application.getSecurityManager().allowedR(user, thatEntity.getEntityCode())) {
if (!Application.getSecurityManager().allowRead(user, thatEntity.getEntityCode())) {
response.sendError(403, "你没有访问此实体的权限");
return null;
}

View file

@ -33,6 +33,7 @@ import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BasePageControll;
import com.rebuild.web.common.FileDownloader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@ -42,22 +43,21 @@ import java.io.File;
import java.io.IOException;
/**
* 视图打印
* 报表/打印
*
* @author devezhao
* @since 2019/8/3
*/
@Controller
@RequestMapping("/app/entity/")
@RequestMapping("/app/{entity}/")
public class ReportsControll extends BasePageControll {
@RequestMapping("print")
public ModelAndView printPreview(HttpServletRequest request) {
public ModelAndView printPreview(@PathVariable String entity, HttpServletRequest request) {
ID user = getRequestUser(request);
ID recordId = getIdParameterNotNull(request, "id");
Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
JSON model = FormsBuilder.instance.buildView(entity.getName(), user, recordId);
JSON model = FormsBuilder.instance.buildView(entity, user, recordId);
ModelAndView mv = createModelAndView("/general-entity/print-preview.jsp");
mv.getModel().put("contentBody", model);
@ -67,17 +67,16 @@ public class ReportsControll extends BasePageControll {
return mv;
}
@RequestMapping("available-reports")
public void availableReports(HttpServletRequest request, HttpServletResponse response) {
String entity = getParameterNotNull(request, "entity");
@RequestMapping("reports/available")
public void availableReports(@PathVariable String entity, HttpServletResponse response) {
Entity entityMeta = MetadataHelper.getEntity(entity);
JSONArray reports = DataReportManager.instance.getReports(entityMeta);
writeSuccess(response, reports);
}
@RequestMapping({ "report-generate", "report-export" })
public void reportGenerate(HttpServletRequest request, HttpServletResponse response) throws IOException {
@RequestMapping({ "reports/generate", "reports/export" })
public void reportGenerate(@PathVariable String entity,
HttpServletRequest request, HttpServletResponse response) throws IOException {
ID reportId = getIdParameterNotNull(request, "report");
ID recordId = getIdParameterNotNull(request, "record");

View file

@ -104,7 +104,9 @@ public class LanguagesControll extends BaseControll {
String storeLocale = Application.getSessionStore().getLocale();
if (!locale.equalsIgnoreCase(storeLocale)) {
if (AppUtils.devMode()) Languages.instance.reset();
if (AppUtils.devMode()) {
Languages.instance.reset();
}
ServletUtils.setSessionAttribute(request, SK_LOCALE, locale);
return true;
}

View file

@ -39,7 +39,7 @@ import javax.servlet.http.HttpServletResponse;
* @since 09/20/2018
*/
@Controller
public class MiscPagesForward extends BasePageControll {
public class PagesForward extends BasePageControll {
@RequestMapping(value={ "/p/**/*", "/admin/p/**/*" }, method = RequestMethod.GET)
public ModelAndView page(HttpServletRequest request) {

View file

@ -96,7 +96,7 @@ public class ChartDesignControll extends BaseEntityControll {
throw new IllegalParameterException("无效图表参数");
}
if (!Application.getSecurityManager().allowedR(getRequestUser(request), entityMeta.getEntityCode())) {
if (!Application.getSecurityManager().allowRead(getRequestUser(request), entityMeta.getEntityCode())) {
response.sendError(403, "你没有读取 [" + EasyMeta.getLabel(entityMeta) + "] 的权限,因此无法设计此图表");
return null;
}
@ -107,7 +107,8 @@ public class ChartDesignControll extends BaseEntityControll {
for (Field field : MetadataSorter.sortFields(entityMeta)) {
EasyMeta easy = EasyMeta.valueOf(field);
DisplayType dt = easy.getDisplayType();
if (dt == DisplayType.IMAGE || dt == DisplayType.FILE || dt == DisplayType.ANYREFERENCE) {
if (dt == DisplayType.IMAGE || dt == DisplayType.FILE || dt == DisplayType.ANYREFERENCE
|| dt == DisplayType.AVATAR || dt == DisplayType.LOCATION || dt == DisplayType.MULTISELECT) {
continue;
}
String type = "text";

View file

@ -130,7 +130,7 @@ public class FileManagerControll extends BaseControll {
if (record.getEntityCode() == EntityHelper.Feeds || record.getEntityCode() == EntityHelper.FeedsComment) {
OK = FeedsHelper.checkReadable(record, user);
} else {
OK = Application.getSecurityManager().allowedR(user, record);
OK = Application.getSecurityManager().allowRead(user, record);
}
writeSuccess(response, OK);
}

View file

@ -21,7 +21,7 @@ final JSONArray navArray = NavManager.instance.getNavForPortal(request);
</div>
</div>
</div>
<% if (AppUtils.allowed(request, ZeroEntry.AllowCustomNav)) { %>
<% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
<div class="bottom-widget">
<a class="nav-settings" href="javascript:;" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
</div>

View file

@ -9,7 +9,7 @@
<li class="divider">系统</li>
<li class="${param['activeNav'] == 'systems' ? 'active' : ''}"><a href="${baseUrl}/admin/systems"><i class="icon zmdi zmdi-settings"></i><span>通用配置</span></a></li>
<li class="parent">
<a><i class="icon zmdi zmdi-cloud-outline-alt"></i><span>三方服务集成</span></a>
<a><i class="icon zmdi zmdi-puzzle-piece"></i><span>三方服务集成</span></a>
<ul class="sub-menu">
<li class="title">三方服务集成</li>
<li class="nav-items">
@ -28,7 +28,7 @@
<li class="${param['activeNav'] == 'apis-manager' ? 'active' : ''} bosskey-show"><a href="${baseUrl}/admin/apis-manager"><i class="icon zmdi zmdi-key"></i><span>API 秘钥管理</span></a></li>
<li class="divider">业务/实体</li>
<li class="${param['activeNav'] == 'entities' ? 'active' : ''}"><a href="${baseUrl}/admin/entities"><i class="icon zmdi zmdi-widgets"></i><span>实体管理</span></a></li>
<li class="${param['activeNav'] == 'classifications' ? 'active' : ''}"><a href="${baseUrl}/admin/entityhub/classifications"><i class="icon x21 zmdi zmdi-layers"></i><span>分类数据</span></a></li>
<li class="${param['activeNav'] == 'classifications' ? 'active' : ''}"><a href="${baseUrl}/admin/entityhub/classifications"><i class="icon zmdi zmdi-layers"></i><span>分类数据</span></a></li>
<li class="${param['activeNav'] == 'robot-approval' ? 'active' : ''}"><a href="${baseUrl}/admin/robot/approvals"><i class="icon zmdi zmdi-assignment-check"></i><span>审批流程</span></a></li>
<li class="${param['activeNav'] == 'robot-trigger' ? 'active' : ''}"><a href="${baseUrl}/admin/robot/triggers"><i class="icon zmdi zmdi-rotate-cw"></i><span>触发器</span></a></li>
<li class="${param['activeNav'] == 'data-reports' ? 'active' : ''}"><a href="${baseUrl}/admin/datas/data-reports"><i class="icon zmdi zmdi-map"></i><span>报表模板</span></a></li>

View file

@ -13,7 +13,7 @@ final User currentUser = Application.getUserStore().getUser(AppUtils.getRequestU
<a class="rb-toggle-left-sidebar" title="展开/收缩导航菜单"><span class="icon zmdi zmdi-menu"></span></a>
</div>
<div class="search-container">
<input class="form-control form-control-sm search-input-gs" type="text" name="search" maxlength="100" placeholder="搜索" />
<input class="form-control form-control-sm search-input-gs" type="text" name="search" maxlength="100" placeholder="搜索" autocomplete="off" />
<div class="search-models animated fadeIn faster"></div>
</div>
<div class="rb-right-navbar">

View file

@ -47,7 +47,7 @@
</div>
</aside>
<div class="main-content container-fluid">
<div class="alert alert-warning alert-icon alert-dismissible alert-sm hide J_tips">
<div class="alert alert-warning alert-icon alert-icon-border alert-dismissible alert-sm hide J_tips">
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
<div class="message"><a class="close" data-dismiss="alert"><span class="zmdi zmdi-close"></span></a><p>1</p></div>
</div>
@ -128,16 +128,16 @@
<td class="text-center"><i data-action="Z" class="priv R4"></i></td>
<td colspan="5" class="text-muted">需具备相应实体的读取权限</td>
</tr>
<!--
<tr>
<td class="name"><a data-name="AllowBatchUpdate">允许批量修改</a></td>
<td class="text-center"><i data-action="Z" class="priv R0"></i></td>
<td colspan="5">需具备相应实体的修改权限</td>
<td colspan="5" class="text-muted">需具备相应实体的修改权限</td>
</tr>
<!--
<tr>
<td class="name"><a data-name="AllowDataImport">允许导入数据</a></td>
<td class="text-center"><i data-action="Z" class="priv R0"></i></td>
<td colspan="5">需具备相应实体的创建权限</td>
<td colspan="5" class="text-muted">需具备相应实体的创建权限</td>
</tr>
-->
<tr>

View file

@ -16,7 +16,7 @@
</span>
</div>
<div class="main-content container-fluid">
<div class="alert alert-warning alert-icon alert-dismissible alert-sm hide J_tips">
<div class="alert alert-warning alert-icon alert-icon-border alert-dismissible alert-sm hide J_tips">
<div class="icon"><span class="zmdi zmdi-info-outline"></span></div>
<div class="message"><a class="close" data-dismiss="alert"><span class="zmdi zmdi-close"></span></a><p></p></div>
</div>

View file

@ -20,7 +20,7 @@
<div class="page-head-title">分类数据<span class="sub-title">${name}</span></div>
</div>
<div class="float-right pt-1">
<button class="btn btn-secondary J_imports" type="button"><i class="zmdi zmdi-cloud-download"></i> 导入公共数据</button>
<button class="btn btn-secondary J_imports" type="button"><i class="icon zmdi zmdi-cloud-outline-alt"></i> 从 RB 仓库导入</button>
</div>
<div class="clearfix"></div>
</div>

View file

@ -5,13 +5,43 @@
<%@ include file="/_include/Head.jsp"%>
<title>数据导入</title>
<style type="text/css">
.fuelux .wizard .step-content{padding:30px}
.fuelux .wizard>.steps-container>.steps li.complete:hover{cursor:default;}
#fieldsMapping th, #fieldsMapping td{padding:6px 0;vertical-align:middle;border-bottom:1px dotted #dee2e6;border-top:0 none;}
#fieldsMapping thead th{border-bottom:1px solid #dee2e6;padding-top:9px;}
#fieldsMapping td>em{font-style:normal;background-color:#eee;display:inline-block;min-width:30px;font-size:12px;text-align:center;margin-right:4px;padding-top:1px;color:#777}
#fieldsMapping td>i.zmdi{float:right;color:#aaa;font-size:1.4rem;margin-right:10px}
#ouser-warn .alert{margin-top:10px;margin-bottom:0}
.fuelux .wizard .step-content {
padding: 30px
}
.fuelux .wizard > .steps-container > .steps li.complete:hover {
cursor: default;
}
#fieldsMapping th, #fieldsMapping td {
padding: 6px 0;
vertical-align: middle;
border-bottom: 1px dotted #dee2e6;
border-top: 0 none;
}
#fieldsMapping thead th {
border-bottom: 1px solid #dee2e6;
padding-top: 9px;
}
#fieldsMapping td > em {
font-style: normal;
background-color: #eee;
display: inline-block;
min-width: 30px;
font-size: 12px;
text-align: center;
margin-right: 4px;
padding-top: 1px;
color: #777
}
#fieldsMapping td > i.zmdi {
float: right;
color: #aaa;
font-size: 1.4rem;
margin-right: 10px
}
#user-warn .alert {
margin-top: 10px;
margin-bottom: 0
}
</style>
</head>
<body>
@ -55,16 +85,15 @@
<label for="upload-input" class="btn-secondary"><i class="zmdi zmdi-upload"></i><span>选择文件</span></label>
</div>
</div>
<div class="float-left ml-2" style="padding-top:7px">
<div class="float-left ml-2 pt-1">
<div class="text-bold text-italic J_upload-input"></div>
</div>
<div class="clearfix"></div>
<div class="form-text mb-0">
<ul class="mb-0 pl-4">
<li>支持上传 Excel/CSV 文件,文件大小不超过 20M</li>
<li>有合并单元格的数据请处理过后再上传,否则可能出现表头识别有误</li>
<li>系统默认仅识别第一个 SHEET且会将首行识别为表头</li>
<li>更多导入帮助请 <a href="https://getrebuild.com/docs/admin/data-import" target="_blank">参考文档</a></li>
<li>更多导入帮助请 <a href="https://getrebuild.com/docs/admin/data-import" target="_blank" class="link">参考文档</a></li>
</ul>
</div>
</div>
@ -72,7 +101,7 @@
<div class="form-group row">
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">遇到重复记录时</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<div style="margin-top:6px;">
<div class="mt-1">
<label class="custom-control custom-control-sm custom-radio custom-control-inline">
<input class="custom-control-input" type="radio" name="repeatOpt" value="1" checked="checked"><span class="custom-control-label">覆盖 (更新)</span>
</label>
@ -95,15 +124,13 @@
<div class="col-md-12 col-xl-6 col-lg-8">
<select class="form-control form-control-sm" id="toUser">
</select>
<div class="form-text mb-0">
不选择则默认为当前用户,如字段映射中指定了用户则以映射为准
</div>
<div id="ouser-warn"></div>
<div class="form-text mb-0">不选择则默认为当前用户,如字段映射中指定了用户则以映射为准</div>
<div id="user-warn"></div>
</div>
</div>
<div class="form-group row footer">
<div class="col-md-12 col-xl-6 col-lg-8 offset-xl-3 offset-lg-4">
<button class="btn btn-primary J_step1-btn" type="button" data-loading-text="正在预处理">下一步</button>
<button class="btn btn-primary btn-space J_step1-btn" type="button" data-loading-text="正在预处理">下一步</button>
</div>
</div>
</form>
@ -128,9 +155,8 @@
</div>
<div class="form-group row footer">
<div class="col-md-12 col-xl-6 col-lg-8 offset-xl-3 offset-lg-4">
<button class="btn btn-primary J_step2-btn" type="button">开始导入</button>
&nbsp;&nbsp;
<button class="btn btn-link J_step2-return" type="button">返回上一步</button>
<button class="btn btn-primary btn-space J_step2-btn" type="button">开始导入</button>
<button class="btn btn-link btn-space J_step2-return" type="button">返回上一步</button>
</div>
</div>
</form>
@ -142,11 +168,9 @@
<div class="col-6 text-right text-muted">耗时 <span class="J_import_time">00:00:00</span></div>
</div>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated J_import-bar" style="width:0"></div>
</div>
<div class="mt-3">
<button class="btn btn-danger J_step3-cancel" type="button">终止导入</button>
<a class="btn btn-link J_step3-logs hide" href="data-importer">继续导入</a>
<button class="btn btn-danger btn-space J_step3-cancel" type="button">终止导入</button>
<a class="btn btn-link btn-space J_step3-logs hide" href="data-importer">继续导入</a>
</div>
</form>
</div>

View file

@ -13,7 +13,7 @@
<div class="tab-container">
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" href="#MANUAL" data-toggle="tab">手动</a></li>
<li class="nav-item"><a class="nav-link J_imports" href="#IMPORTS" data-toggle="tab">导入公共数据</a></li>
<li class="nav-item"><a class="nav-link J_imports" href="#IMPORTS" data-toggle="tab"><i class="icon zmdi zmdi-cloud-outline-alt"></i> 从 RB 仓库导入</a></li>
</ul>
<div class="tab-content m-0 pb-0">
<div class="tab-pane active" id="MANUAL">

View file

@ -40,7 +40,7 @@
<div class="rb-content">
<div class="field-head">
<h4 class="float-left">字段列表</h4>
<div class="float-right"><span class="not-nullable">必填字段</span><span class="readonly">只读字段</span></div>
<div class="float-right"><span class="not-nullable">必填</span><span class="readonly">只读</span></div>
</div>
<div class="rb-scroller">
<div class="field-list dd-list">

View file

@ -4,11 +4,6 @@
<head>
<%@ include file="/_include/Head.jsp"%>
<title>API 秘钥管理</title>
<style type="text/css">
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
.syscfg .table td{padding:10px;}
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
</style>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">

View file

@ -5,11 +5,6 @@
<head>
<%@ include file="/_include/Head.jsp"%>
<title>缓存系统配置</title>
<style type="text/css">
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
.syscfg .table td{padding:10px;}
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
</style>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
@ -43,10 +38,10 @@
</tbody>
</table>
<c:if test="${!cacheStatus}">
<div class="alert alert-warning alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
<div class="message">REDIS 缓存服务未配置或配置有误,当前已启用 EHCACHE 内建缓存</div>
</div>
<div class="alert alert-warning alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
<div class="message">REDIS 未配置或配置有误,当前已启用 EHCACHE 内建缓存</div>
</div>
</c:if>
</div>
</div>

View file

@ -5,11 +5,6 @@
<head>
<%@ include file="/_include/Head.jsp"%>
<title>云存储配置</title>
<style type="text/css">
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
.syscfg .table td{padding:10px;}
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
</style>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">
@ -48,10 +43,10 @@
</tbody>
</table>
<c:if test="${!storageStatus}">
<div class="alert alert-warning alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
<div class="message">七牛云存储账户配置有误,当前已启用本地文件存储</div>
</div>
<div class="alert alert-warning alert-icon mt-6">
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
<div class="message">七牛云存储账户未配置或配置有误,当前已启用本地文件存储</div>
</div>
</c:if>
</div>
</div>

View file

@ -5,11 +5,6 @@
<head>
<%@ include file="/_include/Head.jsp"%>
<title>短信/邮件配置</title>
<style type="text/css">
.syscfg h5{background-color:#eee;margin:0;padding:10px;}
.syscfg .table td{padding:10px;}
.syscfg .table td p{margin:0;color:#999;font-weight:normal;font-size:12px;}
</style>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header">

View file

@ -360,12 +360,4 @@ a.ui-draggable.ui-draggable-dragging {
.chart-type>a.active>i.C370 {
background-position: -864px -1520px;
}
.chart-option>div {
display: none;
}
.chart-option>div.active {
display: block;
}

View file

@ -47,8 +47,7 @@
.tools-bar {
height: 44px;
padding: 0 25px;
padding-top: 7px
padding: 7px 25px 0;
}
.tools-bar h4 {
@ -61,18 +60,37 @@
}
.chart-grid {
overflow: scroll;
overflow-x: hidden;
padding: 2px 5px 25px 15px;
padding: 2px 15px 25px 15px;
margin-top: 8px;
min-height: 300px;
height: auto;
}
@media (max-width:768px) {
.chart-grid {
overflow: hidden;
padding-bottom: 5px;
padding-right: 15px;
padding-bottom: 0;
}
.grid-stack>.grid-stack-item>.grid-stack-item-content {
left: 0;
right: 0;
}
.tools-bar .col-sm-6 {
padding-left: 5px;
padding-right: 5px;
}
.grid-stack.grid-stack-one-column-mode>.grid-stack-item {
margin-bottom: 15px;
}
}
@media (max-width:576px) {
.tools-bar .col-sm-6 {
padding-left: 0;
padding-right: 0;
}
}
@ -125,4 +143,8 @@
.J_dash-select:hover {
color: #4285f4;
}
.shareTo--wrap .custom-control {
margin-top: 0;
}

View file

@ -1,9 +1,10 @@
.rb-content .main-content {
margin-right: 300px;
margin-right: 280px;
}
.rb-right-sidebar.field-aside {
width: 300px;
width: 280px;
z-index: 1;
}
.readonly {

View file

@ -5795,8 +5795,8 @@ input[type=submit].btn-block {
font-weight: 700
}
.alert-dismissible {
padding-right: 4.27rem
.alert-dismissible .message>p {
padding-right: 2.4rem
}
.alert-dismissible .close {
@ -20258,6 +20258,7 @@ fieldset[disabled] .btn-color.btn-evernote:hover {
margin-right: 8px;
color: #696969;
width: 14px;
text-align: center;
}
.dropdown-menu>.dropdown-item:active,

View file

@ -141,11 +141,16 @@ body {
white-space: nowrap;
}
.text-wrap {
.text-wrap,
.text-break {
word-break: break-all;
word-wrap: break-word;
}
.text-overflow {
overflow: hidden;
}
.badge.text-id {
font-weight: normal;
color: #777;
@ -161,6 +166,10 @@ a.badge.text-id:hover {
border-color: #4285f4;
}
.bg-eee {
background-color: #eee;
}
.up-1 {
transform: translateY(-1px);
}
@ -245,6 +254,16 @@ a.btn {
color: #0d5bdd !important;
}
.btn-link.disabled,
.btn-link.disabled:hover,
.btn-link.disabled:active,
.btn-link[disabled],
.btn-link[disabled]:hover,
.btn-link[disabled]:active {
color: #aaa !important;
cursor: default;
}
.btn .icon.x14 {
font-size: 1.431rem
}
@ -258,6 +277,15 @@ a.btn {
opacity: 0.8;
}
.rb-left-sidebar .sidebar-elements a>.icon.zmdi-layers {
font-size: 22px;
margin-top: 0;
}
.rb-left-sidebar .sidebar-elements a>.icon.zmdi-puzzle-piece {
font-size: 18px;
}
.rb-color-header .rb-top-header .rb-navbar-header .rb-toggle-left-sidebar .icon {
color: rgba(255, 255, 255, .6)
}
@ -905,11 +933,12 @@ a {
.rbhighbar {
position: fixed;
min-width: 421px;
top: 0;
left: 50%;
margin-left: -210px;
left: 0;
right: 0;
width: 100%;
z-index: 1099;
text-align: center;
}
.rbhighbar.faster {
@ -917,16 +946,26 @@ a {
animation-duration: 200ms
}
.rbhighbar>.alert {
display: inline-block;
width: auto;
max-width: 80%;
min-width: 421px;
text-align: left;
}
@media (max-width:440px) {
.rbhighbar {
min-width: auto;
margin: 0;
left: 10px;
right: 10px;
padding: 0 10px;
}
.rbhighbar>.alert {
max-width: 100%;
min-width: 100%;
}
}
.rbhighbar a {
.rbhighbar>.alert>.message a {
color: #fff
}
@ -1211,6 +1250,46 @@ i.split.ui-draggable-dragging {
margin-left: 1px;
}
.form.batch-form {
padding: 5px 10px;
}
.form.batch-form .form-group:last-child {
margin-bottom: 0;
}
.batch-contents>div {
position: relative;
margin-bottom: 5px;
}
.batch-contents>div .badge {
font-size: 1rem;
font-weight: normal;
padding: 1px 10px;
cursor: default;
}
.batch-contents>div .badge.badge-warning {
border: 1px solid #fbbc05
}
.batch-contents>div a.del {
float: right;
font-size: 1.2rem;
padding: 1px 5px;
margin-right: -5px;
color: #999;
font-weight: bold;
display: none;
}
.batch-contents>div:hover a.del {
display: block;
}
/* DataList ends */
.unselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -1779,12 +1858,7 @@ form.dropzone .note {
}
form.simple {
max-width: 888px;
}
.rb-left-sidebar .sidebar-elements>li>a .icon.x21,
.rb-left-sidebar .sidebar-elements .sub-menu li>a .icon.x21 {
font-size: 21px;
max-width: 900px;
}
.fs-0 {
@ -2977,4 +3051,21 @@ a.icon-link>.zmdi {
font-size: 1.231rem;
margin-top: 2px;
left: 0;
}
.syscfg h5 {
background-color: #eee;
margin: 0;
padding: 10px;
}
.syscfg .table td {
padding: 10px;
}
.syscfg .table td p {
margin: 0;
color: #999;
font-weight: normal;
font-size: 12px;
}

View file

@ -220,10 +220,10 @@ let render_option = (() => {
const ct = select.data('type')
// Option
$('.chart-option>div').removeClass('active')
$('.chart-option>div').addClass('hide')
let ctOpt = $('.J_opt-' + ct)
if (ctOpt.length === 0) $('.chart-option>.J_opt-UNDEF').addClass('active')
else ctOpt.addClass('active')
if (ctOpt.length === 0) $('.J_opt-UNDEF').removeClass('hide')
else ctOpt.removeClass('hide')
// Sort
let sorts = $('.axis-editor .J_sort').removeClass('disabled')

View file

@ -82,9 +82,9 @@ let rendered_charts = []
let win_resize = function (t) {
if (on_resizestart === true) return
$setTimeout(() => {
let cg = $('.chart-grid')
if ($(window).width() >= 768) cg.height($(window).height() - 142)
else cg.height('auto')
// let cg = $('.chart-grid')
// if ($(window).width() >= 768) cg.height($(window).height() - 142)
// else cg.height('auto')
$(rendered_charts).each((idx, item) => { item.resize() })
}, t || 400, 'resize-charts')
}

View file

@ -322,7 +322,7 @@ class DlgImports extends RbModalHandler {
return
}
let cp = res.data.completed
let cp = res.data.progress
if (cp >= 1) {
RbHighbar.success('导入完成')
this.__mpro.end()

View file

@ -32,16 +32,19 @@ $(document).ready(() => {
ientry.entity = entity
})
}
$.get(`${rb.baseUrl}/commons/metadata/entities`, (res) => {
$.get(`${rb.baseUrl}/commons/metadata/entities?slave=true`, (res) => {
$(res.data).each(function () {
$('<option value="' + this.name + '">' + this.label + '</option>').appendTo('#toEntity')
})
$('#toEntity').select2({
let entityS2 = $('#toEntity').select2({
allowClear: false
}).on('change', function () {
fileds_render($(this).val())
check_ouser()
}).trigger('change')
check_user()
})
if ($urlp('entity')) entityS2.val($urlp('entity'))
entityS2.trigger('change')
})
$('input[name=repeatOpt]').click(function () {
@ -71,7 +74,7 @@ $(document).ready(() => {
}
}).on('change', function () {
ientry.owning_user = $(this).val() || null
check_ouser()
check_user()
})
$('.J_step1-btn').click(step_mapping)
@ -107,25 +110,18 @@ const init_upload = () => {
})
}
const check_ouser = () => {
$setTimeout(check_ouser0, 200, 'check_ouser')
}
const check_ouser0 = () => {
if (!(ientry.entity && ientry.owning_user)) return
$.get(`${rb.baseUrl}/admin/datas/data-importer/check-user-privileges?ouser=${ientry.owning_user}&entity=${ientry.entity}`, (res) => {
//
const check_user = () => $setTimeout(check_user0, 200, 'check_user')
const check_user0 = () => {
if (!ientry.entity || !ientry.owning_user) return
$.get(`${rb.baseUrl}/admin/datas/data-importer/check-user?user=${ientry.owning_user}&entity=${ientry.entity}`, (res) => {
let hasError = []
if (res.data.canCreate !== true) hasError.push('新建')
if (res.data.canUpdate !== true) hasError.push('更新')
if (hasError.length >= 2) {
$('.J_step1-btn').attr('disabled', true)
renderRbcomp(<RbAlertBox type="danger" message={'选择的用户无' + hasError.join('及') + '权限,请选择其他用户'} />, 'ouser-warn')
if (hasError.length > 0) {
renderRbcomp(<RbAlertBox message={`选择的用户无 ${hasError.join('/')} 权限。但作为管理员,你可以强制导入`} />, 'user-warn')
} else {
$('.J_step1-btn').attr('disabled', false)
if (hasError.length > 0) {
renderRbcomp(<RbAlertBox message={'选择的用户无' + hasError.join('/') + '权限,可能导致部分数据导入失败'} />, 'ouser-warn')
} else {
$('#ouser-warn').empty()
}
$('#user-warn').empty()
}
})
}
@ -161,7 +157,7 @@ const step_import = () => {
if (field) fm[field] = col
})
$(fields_cached).each((idx, item) => {
if (item.isNullable === true || !!item.defaultValue) {
if (item.nullable === true || !!item.defaultValue) {
// Not be must
} else if (fm[item.name] === undefined) {
RbHighbar.create(item.label + ' 为必填字段,请选择')
@ -172,14 +168,20 @@ const step_import = () => {
if (!fm) return
ientry.fields_mapping = fm
step_import_show()
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-submit`, JSON.stringify(ientry), function (res) {
if (res.error_code === 0) {
import_inprogress = true
import_taskid = res.data.taskid
location.hash = '#task=' + import_taskid
import_state(import_taskid)
} else RbHighbar.error(res.error_msg)
RbAlert.create('请再次确认导入选项和字段映射。开始导入吗?', {
confirm: function () {
this.disabled(true)
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-submit`, JSON.stringify(ientry), (res) => {
if (res.error_code === 0) {
this.hide()
step_import_show()
import_inprogress = true
import_taskid = res.data.taskid
location.hash = '#task=' + import_taskid
import_state(import_taskid)
} else RbHighbar.error(res.error_msg)
})
}
})
}
const step_import_show = () => {
@ -188,7 +190,7 @@ const step_import_show = () => {
$('.steps li[data-step=3], .step-content .step-pane[data-step=3]').addClass('active')
}
const import_state = (taskid, inLoad) => {
$.get(`${rb.baseUrl}/admin/datas/data-importer/import-state?taskid=${taskid}`, (res) => {
$.get(`${rb.baseUrl}/commons/task/state?taskid=${taskid}`, (res) => {
if (res.error_code !== 0) {
if (inLoad === true) step_upload()
else RbHighbar.error(res.error_msg)
@ -196,7 +198,7 @@ const import_state = (taskid, inLoad) => {
return
}
if (!res.data) {
setTimeout(() => { import_state(taskid) }, 1000)
setTimeout(() => import_state(taskid), 1000)
return
}
@ -205,9 +207,9 @@ const import_state = (taskid, inLoad) => {
if (_data.isCompleted === true) {
$('.J_import-bar').css('width', '100%')
$('.J_import_state').text('导入完成。共成功导入 ' + _data.success + ' 条数据')
$('.J_import_state').text('导入完成。共成功导入 ' + _data.succeeded + ' 条数据')
} else if (_data.isInterrupted === true) {
$('.J_import_state').text('导入被终止。已成功导入 ' + _data.success + ' 条数据')
$('.J_import_state').text('导入被终止。已成功导入 ' + _data.succeeded + ' 条数据')
}
if (_data.isCompleted === true || _data.isInterrupted === true) {
$('.J_step3-cancel').attr('disabled', true).text('导入完成')
@ -217,18 +219,18 @@ const import_state = (taskid, inLoad) => {
}
if (_data.total > -1) {
$('.J_import_state').text('正在导入 ... ' + _data.complete + ' / ' + _data.total)
$('.J_import-bar').css('width', (_data.complete * 100 / _data.total) + '%')
$('.J_import_state').text('正在导入 ... ' + _data.completed + ' / ' + _data.total)
$('.J_import-bar').css('width', (_data.progress * 100) + '%')
}
setTimeout(() => { import_state(taskid) }, 500)
})
}
const import_cancel = () => {
RbAlert.create('确认终止导入?请注意已导入数据无法自动删除', {
RbAlert.create('确认终止导入?请注意已导入数据无法自动删除', {
type: 'danger',
confirmText: '确认终止',
confirm: function () {
$.post(`${rb.baseUrl}/admin/datas/data-importer/import-cancel?taskid=${import_taskid}`, (res) => {
$.post(`${rb.baseUrl}/commons/task/cancel?taskid=${import_taskid}`, (res) => {
if (res.error_code > 0) RbHighbar.error(res.error_msg)
})
this.hide()
@ -241,7 +243,7 @@ const render_fieldsMapping = (columns, fields) => {
let fields_map = {}
let fields_select = $('<select><option value="">无</option></select>')
$(fields).each((idx, item) => {
let canNull = item.isNullable === false ? ' [必填]' : ''
let canNull = item.nullable === false ? ' [必填]' : ''
if (item.defaultValue) canNull = ''
$('<option value="' + item.name + '">' + item.label + canNull + '</option>').appendTo(fields_select)
fields_map[item.name] = item

View file

@ -11,7 +11,7 @@ class DlgAssign extends RbModalHandler {
return (<RbModal title={this.types[1]} ref={(c) => this._dlg = c}>
<div className="form">
{this.onView === true ? null : (
<div className="form-group row">
<div className="form-group row pb-0">
<label className="col-sm-3 col-form-label text-sm-right">{this.types[1]}哪些记录</label>
<div className="col-sm-7">
<div className="form-control-plaintext">{'选中的记录 (' + this.state.ids.length + '条)'}</div>
@ -141,21 +141,18 @@ class DlgUnshare extends RbModalHandler {
<label className="col-sm-3 col-form-label text-sm-right">取消哪些用户</label>
<div className="col-sm-7">
<div className="mt-1">
<label className="custom-control custom-control-sm custom-radio custom-control-inline">
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
<input className="custom-control-input" name="whichUsers" type="radio" checked={this.state.whichUsers === 'ALL'} onChange={() => this.whichMode(true)} />
<span className="custom-control-label">全部用户</span>
</label>
<label className="custom-control custom-control-sm custom-radio custom-control-inline">
<label className="custom-control custom-control-sm custom-radio custom-control-inline mb-2">
<input className="custom-control-input" name="whichUsers" type="radio" checked={this.state.whichUsers === 'SPEC'} onChange={() => this.whichMode()} />
<span className="custom-control-label">指定用户</span>
</label>
</div>
</div>
</div>
<div className={'form-group row pt-0 ' + (this.state.whichUsers === 'ALL' ? 'hide' : '')}>
<label className="col-sm-3 col-form-label text-sm-right"></label>
<div className="col-sm-7">
<select className="form-control form-control-sm" ref={(c) => this._toUser = c} />
<div className={'mb-2 ' + (this.state.whichUsers === 'ALL' ? 'hide' : '')}>
<select className="form-control form-control-sm" ref={(c) => this._toUser = c} />
</div>
</div>
</div>
<div className="form-group row footer">

View file

@ -8,7 +8,7 @@
'button': function (state) {
return this.each(function () {
var el = $(this)
if (el.prop('nodeName') !== 'BUTTON') return
if (!(el.prop('nodeName') === 'BUTTON' || el.prop('nodeName') === 'A')) return
if (state === 'loading') {
el.attr('disabled', true)
var loadingText = el.data('loading-text')

View file

@ -112,7 +112,8 @@ class RbModalHandler extends React.Component {
if (state && $.type(state) === 'object') this.setState(state, callback)
else callback()
}
hide = () => {
hide = (e) => {
if (e && e.target && $(e.target).attr('disabled')) return
// eslint-disable-next-line react/no-string-refs
let dlg = this._dlg || this.refs['dlg']
if (dlg) dlg.hide()
@ -223,17 +224,16 @@ class RbAlert extends React.Component {
// -- Usage
/**
* @param {*} message
* @param {*} titleExt
* @param {*} titleOrExt
* @param {*} ext
*/
static create(message, titleExt, ext) {
let title = titleExt
if ($.type(titleExt) === 'object') {
title = null
ext = titleExt
static create(message, titleOrExt, ext) {
if (typeof titleOrExt === 'object') {
ext = titleOrExt
titleOrExt = null
}
ext = ext || {}
let props = { ...ext, title: title }
let props = { ...ext, title: titleOrExt }
if (ext.html === true) props.htmlMessage = message
else props.message = message
renderRbcomp(<RbAlert {...props} />, null, ext.call)
@ -250,25 +250,23 @@ class RbHighbar extends React.Component {
render() {
let icon = this.props.type === 'success' ? 'check' : 'info-outline'
icon = this.props.type === 'danger' ? 'close-circle-o' : icon
let content = this.props.htmlMessage ? <div className="message" dangerouslySetInnerHTML={{ __html: this.props.htmlMessage }} /> : <div className="message">{this.props.message}</div>
return (<div ref={(c) => this._rbhighbar = c} className={'rbhighbar animated faster ' + this.state.animatedClass}>
<div className={'alert alert-dismissible alert-' + (this.props.type || 'warning')}>
<button className="close" type="button" onClick={() => this.close()}><span className="zmdi zmdi-close" /></button>
<div className="icon"><span className={'zmdi zmdi-' + icon} /></div>
let content = this.props.htmlMessage
? <div className="message pl-0" dangerouslySetInnerHTML={{ __html: this.props.htmlMessage }} />
: <div className="message pl-0">{this.props.message}</div>
return (<div ref={(c) => this._rbhighbar = c} className={`rbhighbar animated faster ${this.state.animatedClass}`}>
<div className={`alert alert-dismissible alert-${(this.props.type || 'warning')}`}>
<button className="close" type="button" onClick={this.close}><i className="zmdi zmdi-close" /></button>
<div className="icon"><i className={`zmdi zmdi-${icon}`} /></div>
{content}
</div>
</div>)
}
componentDidMount() {
setTimeout(() => { this.close() }, this.props.timeout || 3000)
}
close() {
this.setState({ animatedClass: 'fadeOut' }, () => {
$unmount($(this._rbhighbar).parent())
})
setTimeout(() => this.close(), this.props.timeout || 3000)
}
close = () => this.setState({ animatedClass: 'fadeOut' }, () => $unmount($(this._rbhighbar).parent()))
// -- Usage
/**
@ -301,7 +299,20 @@ class RbHighbar extends React.Component {
}
}
// ~~
// ~~
function RbAlertBox(props) {
let type = (props || {}).type || 'warning'
let icon = type === 'success' ? 'check' : (type === 'danger' ? 'close-circle-o' : 'info-outline')
return <div className={`alert alert-icon alert-icon-border alert-dismissible alert-sm alert-${type}`}>
<div className="icon"><i className={`zmdi zmdi-${icon}`} /></div>
<div className="message">
<a className="close" data-dismiss="alert"><i className="zmdi zmdi-close" /></a>
<p>{props.message || 'INMESSAGE'}</p>
</div>
</div>
}
// ~~
function RbSpinner(props) {
let spinner = <div className="rb-spinner">
<svg width="40px" height="40px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
@ -312,19 +323,6 @@ function RbSpinner(props) {
return spinner
}
// ~~
function RbAlertBox(props) {
let icon = props.type === 'success' ? 'check' : 'info-outline'
if (props.type === 'danger') icon = 'close-circle-o'
return (<div className={'alert alert-icon alert-dismissible alert-sm alert-' + (props.type || 'warning')}>
<div className="icon"><span className={'zmdi zmdi-' + icon} /></div>
<div className="message">
<a className="close" data-dismiss="alert"><span className="zmdi zmdi-close" /></a>
<p>{props.message}</p>
</div>
</div>)
}
// ~~
class UserSelector extends React.Component {
constructor(props) {

View file

@ -1,10 +1,12 @@
const wpc = window.__PageConfig || {}
/* eslint-disable react/no-string-refs */
/* eslint-disable react/prop-types */
// ~~
const COLUMN_MIN_WIDTH = 30
const COLUMN_MAX_WIDTH = 800
const FIXED_FOOTER = true
class RbList extends React.Component {
constructor(props) {
@ -145,9 +147,9 @@ class RbList extends React.Component {
})
let oper = $('.dataTables_oper')
oper.find('.J_delete, .J_view, .J_edit').attr('disabled', true)
oper.find('.J_delete, .J_view, .J_edit, .J_assign, .J_share, .J_unshare').attr('disabled', true)
let len = this.__selectedRows.length
if (len > 0) oper.find('.J_delete').attr('disabled', false)
if (len > 0) oper.find('.J_delete, .J_assign, .J_share, .J_unshare').attr('disabled', false)
if (len === 1) oper.find('.J_view, .J_edit').attr('disabled', false)
}
@ -276,6 +278,9 @@ class RbList extends React.Component {
//
/**
* 分页设置
*/
setPage(pageNo, pageSize) {
this.pageNo = pageNo || this.pageNo
if (pageSize) {
@ -285,6 +290,9 @@ class RbList extends React.Component {
this.fetchList()
}
/**
* 设置高级过滤器ID
*/
setAdvFilter(id) {
this.advFilter = id
this.fetchList(this.__buildQuick())
@ -293,23 +301,39 @@ class RbList extends React.Component {
else $storage.remove(this.__defaultFilterKey)
}
/**
* 获取选中行
*/
getSelectedRows() {
return this.__selectedRows
return this.__selectedRows || []
}
/**
* 获取选中 ID[]
*/
getSelectedIds() {
if (!this.__selectedRows || this.__selectedRows.length < 1) { RbHighbar.create('未选中任何记录'); return [] }
return this.__selectedRows.map((item) => { return item[0] })
let ids = this.getSelectedRows().map((item) => { return item[0] })
if (ids.length < 1) RbHighbar.create('未选中任何记录')
return ids
}
getQueryedTotal() {
return this._pagination.state.rowsTotal
/**
* 获取最后查询记录熟虑
*/
getLastQueryTotal() {
return this._pagination ? this._pagination.state.rowsTotal : 0
}
getLastQueryEntry() {
return this.__lastQueryEntry
/**
* 获取最后查询过滤数据
*/
getLastQueryData() {
return JSON.parse(JSON.stringify(this.__lastQueryEntry))
}
/**
* 搜索
*/
search(filter, fromAdv) {
let afHold = this.advFilter
if (fromAdv === true) this.advFilter = null
@ -321,9 +345,11 @@ class RbList extends React.Component {
this.lastFilter = null
}
}
reload = () => this.fetchList()
// @el - search element
searchQuick = (el) => this.search(this.__buildQuick(el))
__buildQuick(el) {
el = $(el || '.input-search>input')
let q = el.val()
@ -331,10 +357,6 @@ class RbList extends React.Component {
return { entity: this.props.config.entity, type: 'QUICK', values: { 1: q }, qfields: el.data('fields') }
}
reload() {
this.fetchList()
}
//
static renderAfter() {
}
@ -343,18 +365,22 @@ class RbList extends React.Component {
//
var CellRenders = {
__renders: {},
addRender(type, func) {
this.__renders[type] = func
},
clickView(v) {
RbViewModal.create({ id: v[0], entity: v[2][0] })
return false
},
render(value, type, width, key) {
let style = { width: (width || COLUMN_MIN_WIDTH) + 'px' }
if (!value) return this.renderSimple(value, style, key)
else return (this.__renders[type] || this.renderSimple)(value, style, key)
},
/**
* @param {*} v
* @param {*} s 样式
@ -419,7 +445,7 @@ CellRenders.addRender('MULTISELECT', function (v, s, k) {
</div></td>
})
//
//
class RbListPagination extends React.Component {
constructor(props) {
super(props)
@ -486,57 +512,9 @@ class RbListPagination extends React.Component {
}
}
//
class DataExport extends RbFormHandler {
constructor(props) {
super(props)
this.state.dataRange = '2'
}
render() {
let _list = this.props.listRef
return <RbModal ref={(c) => this._dlg = c} title="数据导出" disposeOnHide={true} width="480">
<div className="pl-2 mb-4">
<label className="custom-control custom-control-sm custom-radio">
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '1'} value="1" onChange={this.handleChange} />
<span className="custom-control-label">当前页的数据 ({_list.state.rowsData.length})</span>
</label>
<label className="custom-control custom-control-sm custom-radio">
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '2'} value="2" onChange={this.handleChange} />
<span className="custom-control-label">查询后的数据 ({_list.getQueryedTotal()})</span>
</label>
<label className="custom-control custom-control-sm custom-radio">
<input className="custom-control-input" name="dataRange" type="radio" checked={this.state.dataRange === '3'} value="3" onChange={this.handleChange} />
<span className="custom-control-label">全部数据</span>
</label>
</div>
<div className="dialog-footer" ref={(c) => this._btns = c}>
<a className="btn btn-link btn-space" onClick={this.hide}>取消</a>
<button className="btn btn-primary btn-space" type="button" data-loading-text="请稍后" onClick={this._export}>确定</button>
</div>
</RbModal>
}
_export = () => {
let dr = ~~this.state.dataRange
let filter = dr < 3 ? this.props.listRef.getLastQueryEntry() : {}
this.disabled(true)
$.post(`${rb.baseUrl}/app/entity/data-export-submit?dr=${dr}`, JSON.stringify(filter), (res) => {
if (res.error_code === 0) {
this.hide()
let url = `${rb.baseUrl}/filex/download/${res.data}?temp=yes`
window.open(url)
} else RbHighbar.error(res.error_msg)
})
}
}
//
const RbListPage = {
_RbList: null,
/**
* @param {*} config DataList config
* @param {*} entity [Name, Label, Icon]
@ -572,25 +550,29 @@ const RbListPage = {
}
})
$('.J_assign').click(() => {
if ($('.J_assign').attr('disabled')) return
let ids = this._RbList.getSelectedIds()
ids.length > 0 && DlgAssign.create({ entity: entity[0], ids: ids })
})
$('.J_share').click(() => {
if ($('.J_share').attr('disabled')) return
let ids = this._RbList.getSelectedIds()
ids.length > 0 && DlgShare.create({ entity: entity[0], ids: ids })
})
$('.J_unshare').click(() => {
if ($('.J_unshare').attr('disabled')) return
let ids = this._RbList.getSelectedIds()
ids.length > 0 && DlgUnshare.create({ entity: entity[0], ids: ids })
})
$('.J_columns').click(() => RbModal.create(`${rb.baseUrl}/p/general-entity/show-fields?entity=${entity[0]}`, '设置列显示'))
$('.J_export').click(() => renderRbcomp(<DataExport listRef={RbListPage._RbList} />))
$('.J_export').click(() => renderRbcomp(<DataExport listRef={RbListPage._RbList} entity={entity[0]} />))
$('.J_batch').click(() => renderRbcomp(<BatchUpdate listRef={RbListPage._RbList} entity={entity[0]} />))
// Privileges
if (ep) {
if (ep.C === false) $('.J_new').remove()
if (ep.D === false) $('.J_delete').remove()
if (ep.U === false) $('.J_edit').remove()
if (ep.U === false) $('.J_edit, .J_batch').remove()
if (ep.A === false) $('.J_assign').remove()
if (ep.S === false) $('.J_share, .J_unshare').remove()
$cleanMenu('.J_action')
@ -608,7 +590,6 @@ const RbListPage = {
//
const AdvFilters = {
/**
* @param {*} el 控件
* @param {*} entity 实体
@ -985,3 +966,385 @@ $(document).ready(() => {
ChartsWidget.init()
}
})
// ~~
class BatchOperator extends RbFormHandler {
constructor(props) {
super(props)
this.state.dataRange = 2
}
render() {
const _listRef = this.props.listRef
const selectedRows = _listRef.getSelectedRows().length
const pageRows = _listRef.state.rowsData.length
const queryRows = _listRef.getLastQueryTotal()
return <RbModal title={this.state.title} disposeOnHide={true} ref={(c) => this._dlg = c}>
<div className="form batch-form">
<div className="form-group">
<label className="text-bold">选择数据范围</label>
<div>
{selectedRows > 0 && <label className="custom-control custom-control-sm custom-radio mb-2">
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 1} value="1" onChange={this.handleChange} />
<span className="custom-control-label">选中的数据 ({selectedRows})</span>
</label>}
<label className="custom-control custom-control-sm custom-radio mb-2">
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 2} value="2" onChange={this.handleChange} />
<span className="custom-control-label">当前页的数据 ({pageRows})</span>
</label>
<label className="custom-control custom-control-sm custom-radio mb-2">
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 3} value="3" onChange={this.handleChange} />
<span className="custom-control-label">查询后的数据 ({queryRows})</span>
</label>
<label className="custom-control custom-control-sm custom-radio mb-1">
<input className="custom-control-input" name="dataRange" type="radio" checked={~~this.state.dataRange === 10} value="10" onChange={this.handleChange} />
<span className="custom-control-label">全部数据</span>
</label>
</div>
</div>
{this.renderOperator()}
</div>
<div className="dialog-footer" ref={(c) => this._btns = c}>
<a className="btn btn-link btn-space" onClick={this.hide}>取消</a>
<button className="btn btn-primary btn-space" type="button" data-loading-text="请稍后" onClick={this.confirm}>确定</button>
</div>
</RbModal>
}
getQueryData() {
let qd = this.props.listRef.getLastQueryData()
if (~~this.state.dataRange === 1) qd._selected = this.props.listRef.getSelectedIds().join('|')
return qd
}
//
renderOperator() { }
confirm = () => { }
}
// ~
class DataExport extends BatchOperator {
constructor(props) {
super(props)
this.state.title = '数据导出'
}
confirm = () => {
this.disabled(true)
$.post(`${rb.baseUrl}/app/${this.props.entity}/data-export/submit?dr=${this.state.dataRange}`, JSON.stringify(this.getQueryData()), (res) => {
if (res.error_code === 0) {
this.hide()
let url = `${rb.baseUrl}/filex/download/${res.data}?temp=yes`
window.open(url)
} else {
this.disabled(false)
RbHighbar.error(res.error_msg)
}
})
}
}
// ~
class BatchUpdate extends BatchOperator {
constructor(props) {
super(props)
this.state.title = '批量修改'
}
componentDidMount() {
$.get(`${rb.baseUrl}/app/${this.props.entity}/batch-update/fields`, (res) => this.setState({ fields: res.data }))
}
renderOperator() {
return <div className="form-group">
<label className="text-bold">修改内容</label>
<div>
<div className="batch-contents">
{(this.state.updateContents || []).map((item) => {
return <div key={`update-${item.field}`}>
<div className="row">
<div className="col-4">
<span className="badge badge-light">{this._fieldLabel(item.field)}</span>
</div>
<div className="col-2 pl-0 pr-0">
<span className="badge badge-warning">{BUE_OPTYPES[item.op]}</span>
</div>
<div className="col-6">
{item.op !== 'NULL' && <span className="badge badge-light">{item.text || item.value}</span>}
<a className="del" onClick={() => this.removeItem(item.field)}><i className="zmdi zmdi-close"></i></a>
</div>
</div>
</div>
})}
</div>
<div className="mt-2">
{this.state.fields && <BatchUpdateEditor ref={(c) => this._editor = c} fields={this.state.fields} entity={this.props.entity} />}
<div className="mt-1">
<button className="btn btn-primary btn-sm bordered" onClick={this.addItem}>添加</button>
</div>
</div>
</div>
</div>
}
_fieldLabel(fieldName) {
let field = this.state.fields.find((item) => { return fieldName === item.name })
return field ? field.label : `[${fieldName}.toUpperCase()]`
}
addItem = () => {
let item = this._editor.buildItem()
if (!item) return
let contents = this.state.updateContents || []
$(contents).each(function () {
if (item.field === this.field) {
RbHighbar.create('修改字段已经存在')
item = null
return false
}
})
if (!item) return
contents.push(item)
this.setState({ updateContents: contents })
}
removeItem(fieldName) {
let contents = []
this.state.updateContents.forEach((item) => {
if (fieldName !== item.field) contents.push(item)
})
this.setState({ updateContents: contents })
}
confirm = () => {
if (!this.state.updateContents || this.state.updateContents.length === 0) { RbHighbar.create('请添加修改内容'); return }
let _data = { queryData: this.getQueryData(), updateContents: this.state.updateContents }
// eslint-disable-next-line no-console
if (rb.env === 'dev') console.log(JSON.stringify(_data))
let that = this
RbAlert.create('请再次确认修改数据范围和修改内容。开始修改吗?', {
confirm: function () {
this.hide()
that.disabled(true)
$.post(`${rb.baseUrl}/app/${that.props.entity}/batch-update/submit?dr=${that.state.dataRange}`, JSON.stringify(_data), (res) => {
if (res.error_code === 0) {
let mp = new Mprogress({ template: 2, start: true, parent: '.rbmodal .modal-body' })
that.__checkState(res.data, mp)
} else {
that.disabled(false)
RbHighbar.error(res.error_msg)
}
})
}
})
}
__checkState(taskid, mp) {
$.get(`${rb.baseUrl}/commons/task/state?taskid=${taskid}`, (res) => {
if (res.error_code === 0) {
if (res.data.hasError) {
mp && mp.end()
RbHighbar.error(res.data.hasError)
return
}
let cp = res.data.progress
if (cp >= 1) {
mp && mp.end()
$(this._btns).find('.btn-primary').text('修改成功')
RbHighbar.success(`成功修改 ${res.data.succeeded} 条记录`)
setTimeout(() => {
this.hide()
window.RbListPage && window.RbListPage.reload()
}, 500)
} else {
mp && mp.set(cp)
setTimeout(() => { this.__checkState(taskid, mp) }, 1000)
}
}
})
}
}
const BUE_OPTYPES = { 'SET': '修改为', 'NULL': '置空', 'PREFIX': '前添加', 'SUFFIX': '后添加', 'PLUS': '加上', 'MINUS': '减去' }
// ~
class BatchUpdateEditor extends React.Component {
state = { ...this.props, selectOp: 'SET' }
componentDidMount() {
let fieldS2 = $(this._field).select2({
allowClear: false
}).on('change', () => {
this.setState({ selectField: fieldS2.val() })
})
let opS2 = $(this._op).select2({
allowClear: false
}).on('change', () => {
this.setState({ selectOp: opS2.val() })
})
fieldS2.trigger('change')
this.__select2 = [fieldS2, opS2]
}
componentWillUnmount() {
this.__select2.forEach((item) => { item.select2('destroy') })
this.__select2 = null
this.__destroyLastValueComp()
}
render() {
return <div className="row">
<div className="col-4">
<select className="form-control form-control-sm" ref={(c) => this._field = c}>
{this.props.fields.map((item) => {
return <option value={item.name} key={`field-${item.name}`}>{item.label}</option>
})}
</select>
</div>
<div className="col-2 pl-0 pr-0">{this.renderOp()}</div>
<div className="col-6">
<div className={`${this.state.selectOp === 'NULL' ? 'hide' : ''}`}>
{(this.state.selectField && this.state.selectOp) && this.renderValue()}
</div>
</div>
</div>
}
renderOp() {
return <select className="form-control form-control-sm" ref={(c) => this._op = c}>
<option value="SET">修改为</option>
<option value="NULL">置空</option>
</select>
}
renderValue() {
if (this.state.selectOp === 'NULL' || !this.state.selectField) return null // set Null
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
const fieldKey = `fv-${field.name}`
if (field.type === 'PICKLIST' || field.type === 'STATE' || field.type === 'MULTISELECT' || field.type === 'BOOL'
|| field.type === 'REFERENCE' || field.type === 'CLASSIFICATION') {
return <select className="form-control form-control-sm" multiple={field.type === 'MULTISELECT'} ref={(c) => this._value = c} key={fieldKey}>
{(field.options || []).map((item) => {
let itemId = item.id || item.mask
if (item.id === false) itemId = 'false' // for BOOL
return <option key={`value-${itemId}`} value={itemId}>{item.text}</option>
})}
</select>
} else {
return <input className="form-control form-control-sm" placeholder={`输入${field.label}`} ref={(c) => this._value = c} key={fieldKey} />
}
}
__destroyLastValueComp() {
if (this.__lastSelect2) {
this.__lastSelect2.select2('destroy')
this.__lastSelect2 = null
}
if (this.__lastDatetimepicker) {
this.__lastDatetimepicker.datetimepicker('remove')
this.__lastDatetimepicker = null
}
}
componentDidUpdate(prevProps, prevState) {
// Unchanged
if (prevState.selectField === this.state.selectField && prevState.selectOp === this.state.selectOp) return
if (this.state.selectOp === 'NULL') return
this.__destroyLastValueComp()
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
if (this._value.tagName === 'SELECT') {
if (field.type === 'REFERENCE' || field.type === 'CLASSIFICATION') {
this.__lastSelect2 = $initReferenceSelect2(this._value, {
name: field.name,
label: field.label,
entity: this.props.entity,
searchType: field.type === 'CLASSIFICATION' ? 'classification' : null
})
} else {
this.__lastSelect2 = $(this._value).select2({
placeholder: `选择${field.label}`
})
}
this.__lastSelect2.val(null).trigger('change')
} else if (field.type === 'DATE' || field.type === 'DATETIME') {
this.__lastDatetimepicker = $(this._value).datetimepicker({
componentIcon: 'zmdi zmdi-calendar',
navIcons: { rightIcon: 'zmdi zmdi-chevron-right', leftIcon: 'zmdi zmdi-chevron-left' },
format: field.type === 'DATE' ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:ii:ss',
minView: field.type === 'DATE' ? 'month' : 0,
weekStart: 1,
autoclose: true,
language: 'zh',
todayHighlight: true,
showMeridian: false,
keyboardNavigation: false,
minuteStep: 5
})
}
}
buildItem() {
let item = { field: this.state.selectField, op: this.state.selectOp }
const field = this.props.fields.find((item) => { return this.state.selectField === item.name })
if (item.op === 'NULL') {
if (!field.nullable) {
RbHighbar.create(`${field.label}不允许为空`)
return null
} else {
return item
}
}
item.value = $(this._value).val()
if (!item.value || item.value.length === 0) {
RbHighbar.create('修改值不能为空')
return null
}
if (field.type === 'MULTISELECT') {
let maskTotal = 0
item.value.forEach((mask) => maskTotal += ~~mask)
item.value = maskTotal
} else if (field.type === 'NUMBER' || field.type === 'DECIMAL') {
if (isNaN(item.value)) {
RbHighbar.create(`${field.label}格式不正确`)
return null
} else if (field.notNegative === 'true' && ~~item.value < 0) {
RbHighbar.create(`${field.label}不允许为负数`)
return null
}
} else if (field.type === 'EMAIL') {
if (!$regex.isMail(item.value)) {
RbHighbar.create(`${field.label}格式不正确`)
return null
}
} else if (field.type === 'URL') {
if (!$regex.isUrl(item.value)) {
RbHighbar.create(`${field.label}格式不正确`)
return null
}
} else if (field.type === 'PHONE') {
if (!$regex.isTel(item.value)) {
RbHighbar.create(`${field.label}格式不正确`)
return null
}
}
if (this._value.tagName === 'SELECT') {
let texts = $(this._value).select2('data').map((o) => { return o.text })
item.text = texts.join(', ')
$(this._value).val(null).trigger('change')
} else {
$(this._value).val('')
}
return item
}
}

View file

@ -783,40 +783,22 @@ class RbFormReference extends RbFormElement {
const entity = this.props.$$$parent.props.entity
const field = this.props.field
let search_input = null
this.__select2 = $(this._fvalue).select2({
placeholder: '选择' + this.props.label,
minimumInputLength: 0,
maximumSelectionLength: 1,
ajax: {
url: rb.baseUrl + '/commons/search/reference',
delay: 300,
data: function (params) {
search_input = params.term
return { entity: entity, field: field, q: params.term }
},
processResults: function (data) {
return { results: data.data }
}
},
language: {
noResults: () => { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
inputTooShort: () => { return '输入关键词搜索' },
searching: () => { return '搜索中...' },
maximumSelected: () => { return '只能选择 1 项' }
}
this.__select2 = $initReferenceSelect2(this._fvalue, {
name: field,
label: this.props.label,
entity: entity
})
let that = this
let s2 = this.__select2
$setTimeout(function () {
let val = that.props.value
const that = this
$setTimeout(() => {
let val = this.props.value
if (val) {
let o = new Option(val[1], val[0], true, true)
s2.append(o).trigger('change')
this.__select2.append(o).trigger('change')
}
s2.on('change', function (e) {
this.__select2.on('change', function (e) {
let v = e.target.value
if (v) {
$.post(`${rb.baseUrl}/commons/search/recently-add?id=${v}`)
@ -870,30 +852,11 @@ class RbFormClassification extends RbFormElement {
super.componentDidMount()
if (this.state.viewMode === true) return
const entity = this.props.$$$parent.props.entity
const field = this.props.field
let search_input = null
this.__select2 = $(this._fvalue).select2({
placeholder: '选择' + this.props.label,
minimumInputLength: 0,
maximumSelectionLength: 1,
ajax: {
url: rb.baseUrl + '/commons/search/classification',
delay: 300,
data: function (params) {
search_input = params.term
return { entity: entity, field: field, q: params.term }
},
processResults: function (data) {
return { results: data.data }
}
},
language: {
noResults: () => { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
inputTooShort: () => { return '输入关键词搜索' },
searching: () => { return '搜索中...' },
maximumSelected: () => { return '只能选择 1 项' }
}
this.__select2 = $initReferenceSelect2(this._fvalue, {
name: this.props.field,
label: this.props.label,
entity: this.props.$$$parent.props.entity,
searchType: 'classification'
})
// In edits

View file

@ -406,6 +406,33 @@ var $initUserSelect2 = function (el, multiple) {
return s
}
// 初始化引用字段搜索
var $initReferenceSelect2 = function (el, field) {
var search_input = null
return $(el).select2({
placeholder: '选择' + field.label,
minimumInputLength: 0,
maximumSelectionLength: 1,
ajax: {
url: rb.baseUrl + '/commons/search/' + (field.searchType || 'reference'),
delay: 300,
data: function (params) {
search_input = params.term
return { entity: field.entity, field: field.name, q: params.term }
},
processResults: function (data) {
return { results: data.data }
}
},
language: {
noResults: function () { return (search_input || '').length > 0 ? '未找到结果' : '输入关键词搜索' },
inputTooShort: function () { return '输入关键词搜索' },
searching: function () { return '搜索中...' },
maximumSelected: function () { return '只能选择 1 ' }
}
})
}
// 保持模态窗口如果需要
var $keepModalOpen = function () {
if ($('.rbmodal.show, .rbview.show').length > 0) {

View file

@ -174,7 +174,7 @@ class SelectReport extends React.Component {
<div>
<ul className="list-unstyled">
{(this.state.reports || []).map((item) => {
let reportUrl = `${rb.baseUrl}/app/entity/report-export?report=${item.id}&record=${this.props.id}`
let reportUrl = `${rb.baseUrl}/app/${this.props.entity}/reports/export?report=${item.id}&record=${this.props.id}`
return <li key={'r-' + item.id}><a target="_blank" href={reportUrl} className="text-truncate">{item.name}<i className="zmdi zmdi-download"></i></a></li>
})}
</ul>
@ -187,9 +187,7 @@ class SelectReport extends React.Component {
}
componentDidMount() {
$.get(`${rb.baseUrl}/app/entity/available-reports?entity=${this.props.entity}`, (res) => {
this.setState({ reports: res.data })
})
$.get(`${rb.baseUrl}/app/${this.props.entity}/reports/available`, (res) => this.setState({ reports: res.data }))
$(this._dlg).modal({ show: true, keyboard: true })
}
hide = () => $(this._dlg).modal('hide')

View file

@ -58,16 +58,16 @@
<div class="data-info mt-3">
<h5>图表选项</h5>
<div class="pl-1 mt-3 chart-option">
<div class="J_opt-UNDEF active">
图表无选项
<div class="J_opt-UNDEF">
当前图表无选项
</div>
<div class="admin-show J_opt-TABLE J_opt-INDEX J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-TREEMAP">
<div class="hide admin-show J_opt-TABLE J_opt-INDEX J_opt-LINE J_opt-BAR J_opt-PIE J_opt-FUNNEL J_opt-TREEMAP">
<label class="custom-control custom-control-sm custom-checkbox mb-2">
<input class="custom-control-input" type="checkbox" data-name="noPrivileges">
<span class="custom-control-label"> 使用全部数据 <i class="zmdi zmdi-help zicon" title="不启用则仅能使用权限范围内的数据"></i></span>
</label>
</div>
<div class="J_opt-TABLE">
<div class="hide J_opt-TABLE">
<label class="custom-control custom-control-sm custom-checkbox mb-2">
<input class="custom-control-input" type="checkbox" data-name="showLineNumber">
<span class="custom-control-label"> 显示行号</span>

View file

@ -7,11 +7,6 @@
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/lib/charts/gridstack.css">
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/charts.css">
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/dashboard.css">
<style type="text/css">
.shareTo--wrap .custom-control {
margin-top: 0;
}
</style>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo">

View file

@ -20,7 +20,7 @@
<div class="tab-container">
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" href="attachment">附件</a></li>
<li class="nav-item"><a class="nav-link" href="docs">文</a></li>
<li class="nav-item"><a class="nav-link" href="docs">文</a></li>
</ul>
<div class="tab-content rb-scroller">
<div class="tab-pane active" id="navTree">

View file

@ -3,13 +3,13 @@
<html>
<head>
<%@ include file="/_include/Head.jsp"%>
<title>文管理</title>
<title>文管理</title>
<link rel="stylesheet" type="text/css" href="${baseUrl}/assets/css/files.css">
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-aside">
<jsp:include page="/_include/NavTop.jsp">
<jsp:param value="文管理" name="pageTitle"/>
<jsp:param value="文管理" name="pageTitle"/>
</jsp:include>
<jsp:include page="/_include/NavLeft.jsp">
<jsp:param value="nav_entity-Attachment" name="activeNav"/>
@ -20,7 +20,7 @@
<div class="tab-container">
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link" href="attachment">附件</a></li>
<li class="nav-item"><a class="nav-link active" href="docs">文</a></li>
<li class="nav-item"><a class="nav-link active" href="docs">文</a></li>
</ul>
<div class="tab-content rb-scroller">
<div class="tab-pane active" id="navTree">
@ -40,7 +40,7 @@
<div class="file-header">
<div class="file-path">
<ol class="breadcrumb">
<li class="breadcrumb-item active">全部文</li>
<li class="breadcrumb-item active">全部文</li>
</ol>
</div>
<div class="file-operator row">

View file

@ -71,7 +71,7 @@
<div class="dataTables_oper">
<button class="btn btn-space btn-secondary J_view" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> 打开</button>
<button class="btn btn-space btn-secondary J_edit" disabled="disabled"><i class="icon zmdi zmdi-border-color"></i> 编辑</button>
<button class="btn btn-space btn-primary J_new" data-url="${baseUrl}/entity/${entity}/new"><i class="icon zmdi zmdi-plus"></i> 新建</button>
<button class="btn btn-space btn-primary J_new"><i class="icon zmdi zmdi-plus"></i> 新建</button>
<div class="btn-group btn-space J_action">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
<div class="dropdown-menu dropdown-menu-right">
@ -80,11 +80,10 @@
<a class="dropdown-item J_share"><i class="icon zmdi zmdi-portable-wifi"></i> 共享</a>
<a class="dropdown-item J_unshare"><i class="icon zmdi zmdi-portable-wifi-off"></i> 取消共享</a>
<div class="dropdown-divider"></div>
<c:if test="${AllowCustomDataList}">
<a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a>
</c:if><c:if test="${AllowDataExport}">
<a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 导出</a>
</c:if>
<c:if test="${AllowDataExport}"><a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 数据导出</a></c:if>
<a class="dropdown-item admin-show" href="${baseUrl}/admin/datas/data-importer?entity=${entityName}" target="_blank"><i class="icon zmdi zmdi-cloud-upload"></i> 数据导入</a>
<c:if test="${AllowBatchUpdate}"><a class="dropdown-item J_batch"><i class="icon zmdi zmdi-flash"></i> 批量修改</a></c:if>
<c:if test="${AllowCustomDataList}"><a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a></c:if>
</div>
</div>
</div>
@ -109,14 +108,14 @@ window.__PageConfig = {
advFilter: true
}
</script>
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-datalist.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-advfilter.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-assignshare.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-approval.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/settings-share2.jsx" type="text/babel"></script>
</body>
</html>

View file

@ -42,7 +42,7 @@
<a class="dropdown-item J_assign"><i class="icon zmdi zmdi-mail-reply-all"></i> 分派</a>
<a class="dropdown-item J_share"><i class="icon zmdi zmdi-portable-wifi"></i> 共享</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/entity/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/${entityName}/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
<a class="dropdown-item J_report"><i class="icon zmdi zmdi-map"></i> 报表</a>
</div>
</div>

View file

@ -74,11 +74,10 @@
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item J_delete"><i class="icon zmdi zmdi-delete"></i> 删除</a>
<div class="dropdown-divider"></div>
<c:if test="${AllowCustomDataList}">
<a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a>
</c:if><c:if test="${AllowDataExport}">
<a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 导出</a>
</c:if>
<c:if test="${AllowDataExport}"><a class="dropdown-item J_export"><i class="icon zmdi zmdi-cloud-download"></i> 数据导出</a></c:if>
<a class="dropdown-item admin-show" href="${baseUrl}/admin/datas/data-importer?entity=${entityName}" target="_blank"><i class="icon zmdi zmdi-cloud-upload"></i> 数据导入</a>
<c:if test="${AllowBatchUpdate}"><a class="dropdown-item J_batch"><i class="icon zmdi zmdi-flash"></i> 批量修改</a></c:if>
<c:if test="${AllowCustomDataList}"><a class="dropdown-item J_columns"><i class="icon zmdi zmdi-code-setting"></i> 列显示</a></c:if>
</div>
</div>
</div>
@ -103,13 +102,13 @@ window.__PageConfig = {
advFilter: true
}
</script>
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-datalist.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-advfilter.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/rb-approval.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/lib/charts/echarts.min.js"></script>
<script src="${baseUrl}/assets/js/charts/charts.jsx" type="text/babel"></script>
<script src="${baseUrl}/assets/js/settings-share2.jsx" type="text/babel"></script>
</body>
</html>

View file

@ -38,7 +38,7 @@
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item J_delete"><i class="icon zmdi zmdi-delete"></i> 删除</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/entity/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
<a class="dropdown-item J_print" target="_blank" href="${baseUrl}/app/${entityName}/print?id=${id}"><i class="icon zmdi zmdi-print"></i> 打印</a>
</div>
</div>
</div>

View file

@ -67,7 +67,7 @@ public abstract class TestSupportWithUser extends TestSupport {
protected ID addRecordOfTestAllFields() {
Entity testEntity = MetadataHelper.getEntity(TEST_ENTITY);
// 自动添加权限
if (!Application.getSecurityManager().allowedR(getSessionUser(), testEntity.getEntityCode())) {
if (!Application.getSecurityManager().allowCreate(getSessionUser(), testEntity.getEntityCode())) {
Record p = EntityHelper.forNew(EntityHelper.RolePrivileges, UserService.SYSTEM_USER);
p.setID("roleId", SIMPLE_ROLE);
p.setInt("entity", testEntity.getEntityCode());

View file

@ -58,7 +58,8 @@ public class DataImporterTest extends TestSupport {
JSONObject rule = JSON.parseObject("{ file:'dataimports-test.xlsx', entity:'TestAllFields', repeat_opt:2, repeat_fields:['TestAllFieldsName'], owning_user:'001-0000000000000001', fields_mapping:{TestAllFieldsName:5} }");
ImportRule importsEnter = ImportRule.parse(rule);
DataImporter dataImports = new DataImporter(importsEnter, UserService.ADMIN_USER);
DataImporter dataImports = new DataImporter(importsEnter);
dataImports.setUser(UserService.ADMIN_USER);
dataImports.run();
}
}

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