mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 15:35:55 +08:00
Merge pull request #180 from getrebuild/online-users-480
RB-480 Online users
This commit is contained in:
commit
fd2294ce5e
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
||||||
Subproject commit 743aa9c6fb21bbf3f88a9eec93b34c0ee8a4c4a6
|
Subproject commit e75127ce05c1ad0b93ffff42d545bd21bc7971b7
|
8
pom.xml
8
pom.xml
|
@ -31,7 +31,7 @@
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<argLine>-Dfile.encoding=UTF-8</argLine>
|
<argLine>-Dfile.encoding=UTF-8</argLine>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
<spring.version>4.3.26.RELEASE</spring.version>
|
<spring.version>4.3.27.RELEASE</spring.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.devezhao</groupId>
|
<groupId>com.github.devezhao</groupId>
|
||||||
<artifactId>momentjava</artifactId>
|
<artifactId>momentjava</artifactId>
|
||||||
<version>91f4df2081</version>
|
<version>0.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.devezhao</groupId>
|
<groupId>com.github.devezhao</groupId>
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.devezhao</groupId>
|
<groupId>com.github.devezhao</groupId>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<version>1.1.7</version>
|
<version>1.2.0</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.devezhao</groupId>
|
<groupId>com.github.devezhao</groupId>
|
||||||
<artifactId>persist4j</artifactId>
|
<artifactId>persist4j</artifactId>
|
||||||
<version>1beaf8f565</version>
|
<version>1.4.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
|
||||||
|
|
||||||
|
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||||
|
See LICENSE and COMMERCIAL in the project root for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.rebuild.server.configuration.portals;
|
||||||
|
|
||||||
|
import cn.devezhao.commons.CodecUtils;
|
||||||
|
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.ServerListener;
|
||||||
|
import com.rebuild.server.configuration.ConfigEntry;
|
||||||
|
import com.rebuild.server.metadata.MetadataHelper;
|
||||||
|
import com.rebuild.utils.AppUtils;
|
||||||
|
import com.rebuild.utils.JSONUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航渲染
|
||||||
|
*
|
||||||
|
* @author devezhao
|
||||||
|
* @since 2020/6/16
|
||||||
|
*/
|
||||||
|
public class NavBuilder extends NavManager {
|
||||||
|
|
||||||
|
public static final NavBuilder instance = new NavBuilder();
|
||||||
|
private NavBuilder() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认导航
|
||||||
|
*/
|
||||||
|
private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray(
|
||||||
|
new String[] { "icon", "text", "type", "value" },
|
||||||
|
new Object[][] {
|
||||||
|
new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS },
|
||||||
|
new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG }
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public JSONArray getNavPortal(HttpServletRequest request) {
|
||||||
|
return getNavPortal(AppUtils.getRequestUser(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param user
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public JSONArray getNavPortal(ID user) {
|
||||||
|
ConfigEntry config = getLayoutOfNav(user);
|
||||||
|
if (config == null) {
|
||||||
|
return NAVS_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤
|
||||||
|
JSONArray navs = (JSONArray) config.getJSON("config");
|
||||||
|
for (Iterator<Object> iter = navs.iterator(); iter.hasNext(); ) {
|
||||||
|
JSONObject nav = (JSONObject) iter.next();
|
||||||
|
JSONArray subNavs = nav.getJSONArray("sub");
|
||||||
|
|
||||||
|
if (subNavs != null && !subNavs.isEmpty()) {
|
||||||
|
for (Iterator<Object> subIter = subNavs.iterator(); subIter.hasNext(); ) {
|
||||||
|
JSONObject subNav = (JSONObject) subIter.next();
|
||||||
|
if (isFilterNav(subNav, user)) {
|
||||||
|
subIter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无子级,移除主菜单
|
||||||
|
if (subNavs.isEmpty()) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
} else if (isFilterNav(nav, user)) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return navs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要过滤掉
|
||||||
|
*
|
||||||
|
* @param nav
|
||||||
|
* @param user
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isFilterNav(JSONObject nav, ID user) {
|
||||||
|
String type = nav.getString("type");
|
||||||
|
if ("ENTITY".equalsIgnoreCase(type)) {
|
||||||
|
String entity = nav.getString("value");
|
||||||
|
if (NAV_PARENT.equals(entity)) {
|
||||||
|
return true;
|
||||||
|
} else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) {
|
||||||
|
return false;
|
||||||
|
} else if (!MetadataHelper.containsEntity(entity)) {
|
||||||
|
LOG.warn("Unknow entity in nav : " + entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity entityMeta = MetadataHelper.getEntity(entity);
|
||||||
|
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染导航菜單
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* @param activeNav
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String renderNavItem(JSONObject item, String activeNav) {
|
||||||
|
final boolean isUrlType = "URL".equals(item.getString("type"));
|
||||||
|
String navName = item.getString("value");
|
||||||
|
String navUrl = item.getString("value");
|
||||||
|
|
||||||
|
boolean isOutUrl = isUrlType && navUrl.startsWith("http");
|
||||||
|
if (isUrlType) {
|
||||||
|
navName = "nav_url-" + navName.hashCode();
|
||||||
|
if (isOutUrl) {
|
||||||
|
navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl);
|
||||||
|
} else {
|
||||||
|
navUrl = ServerListener.getContextPath() + navUrl;
|
||||||
|
}
|
||||||
|
} else if (NAV_FEEDS.equals(navName)) {
|
||||||
|
navName = "nav_entity-Feeds";
|
||||||
|
navUrl = ServerListener.getContextPath() + "/feeds/home";
|
||||||
|
} else if (NAV_FILEMRG.equals(navName)) {
|
||||||
|
navName = "nav_entity-Attachment";
|
||||||
|
navUrl = ServerListener.getContextPath() + "/files/home";
|
||||||
|
} else {
|
||||||
|
navName = "nav_entity-" + navName;
|
||||||
|
navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list";
|
||||||
|
}
|
||||||
|
|
||||||
|
String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
|
||||||
|
String navText = item.getString("text");
|
||||||
|
|
||||||
|
JSONArray subNavs = null;
|
||||||
|
if (activeNav != null) {
|
||||||
|
subNavs = item.getJSONArray("sub");
|
||||||
|
if (subNavs == null || subNavs.isEmpty()) {
|
||||||
|
subNavs = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder navHtml = new StringBuilder()
|
||||||
|
.append(String.format("<li class=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon zmdi zmdi-%s\"></i><span>%s</span></a>",
|
||||||
|
navName + (subNavs == null ? StringUtils.EMPTY : " parent"),
|
||||||
|
subNavs == null ? navUrl : "###",
|
||||||
|
isOutUrl ? "_blank" : "_self",
|
||||||
|
navIcon,
|
||||||
|
navText));
|
||||||
|
if (subNavs != null) {
|
||||||
|
StringBuilder subHtml = new StringBuilder()
|
||||||
|
.append("<ul class=\"sub-menu\"><li class=\"title\">")
|
||||||
|
.append(navText)
|
||||||
|
.append("</li><li class=\"nav-items\"><div class=\"content\"><ul class=\"sub-menu-ul\">");
|
||||||
|
|
||||||
|
for (Object o : subNavs) {
|
||||||
|
JSONObject subNav = (JSONObject) o;
|
||||||
|
subHtml.append(renderNavItem(subNav, null));
|
||||||
|
}
|
||||||
|
subHtml.append("</ul></div></li></ul>");
|
||||||
|
navHtml.append(subHtml);
|
||||||
|
}
|
||||||
|
navHtml.append("</li>");
|
||||||
|
|
||||||
|
if (activeNav != null) {
|
||||||
|
Document navBody = Jsoup.parseBodyFragment(navHtml.toString());
|
||||||
|
for (Element nav : navBody.select("." + activeNav)) {
|
||||||
|
nav.addClass("active");
|
||||||
|
if (activeNav.startsWith("nav_entity-")) {
|
||||||
|
Element navParent = nav.parent();
|
||||||
|
if (navParent != null && navParent.hasClass("sub-menu-ul")) {
|
||||||
|
navParent.parent().parent().parent().parent().addClass("open active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return navBody.selectFirst("li").outerHtml();
|
||||||
|
}
|
||||||
|
return navHtml.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,26 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
||||||
|
|
||||||
package com.rebuild.server.configuration.portals;
|
package com.rebuild.server.configuration.portals;
|
||||||
|
|
||||||
import cn.devezhao.commons.CodecUtils;
|
|
||||||
import cn.devezhao.persist4j.Entity;
|
|
||||||
import cn.devezhao.persist4j.engine.ID;
|
import cn.devezhao.persist4j.engine.ID;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONArray;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.rebuild.server.Application;
|
|
||||||
import com.rebuild.server.ServerListener;
|
|
||||||
import com.rebuild.server.configuration.ConfigEntry;
|
import com.rebuild.server.configuration.ConfigEntry;
|
||||||
import com.rebuild.server.metadata.MetadataHelper;
|
|
||||||
import com.rebuild.utils.AppUtils;
|
|
||||||
import com.rebuild.utils.JSONUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.jsoup.Jsoup;
|
|
||||||
import org.jsoup.nodes.Document;
|
|
||||||
import org.jsoup.nodes.Element;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,9 +28,11 @@ public class NavManager extends BaseLayoutManager {
|
||||||
public static final String NAV_FEEDS = "$FEEDS$";
|
public static final String NAV_FEEDS = "$FEEDS$";
|
||||||
// 文件
|
// 文件
|
||||||
public static final String NAV_FILEMRG = "$FILEMRG$";
|
public static final String NAV_FILEMRG = "$FILEMRG$";
|
||||||
|
// 项目看板
|
||||||
|
public static final String NAV_KANBANMRG = "$KANBANMRG$";
|
||||||
|
|
||||||
public static final NavManager instance = new NavManager();
|
public static final NavManager instance = new NavManager();
|
||||||
private NavManager() { }
|
protected NavManager() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param user
|
* @param user
|
||||||
|
@ -79,160 +66,4 @@ public class NavManager extends BaseLayoutManager {
|
||||||
}
|
}
|
||||||
return array.toArray(new ID[0]);
|
return array.toArray(new ID[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认导航
|
|
||||||
*/
|
|
||||||
private static final JSONArray NAVS_DEFAULT = JSONUtils.toJSONObjectArray(
|
|
||||||
new String[] { "icon", "text", "type", "value" },
|
|
||||||
new Object[][] {
|
|
||||||
new Object[] { "chart-donut", "动态", "ENTITY", NAV_FEEDS },
|
|
||||||
new Object[] { "folder", "文件", "ENTITY", NAV_FILEMRG }
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param request
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public JSONArray getNavForPortal(HttpServletRequest request) {
|
|
||||||
return getNavForPortal(AppUtils.getRequestUser(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param user
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public JSONArray getNavForPortal(ID user) {
|
|
||||||
ConfigEntry config = getLayoutOfNav(user);
|
|
||||||
if (config == null) {
|
|
||||||
return NAVS_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤
|
|
||||||
JSONArray navs = (JSONArray) config.getJSON("config");
|
|
||||||
for (Iterator<Object> iter = navs.iterator(); iter.hasNext(); ) {
|
|
||||||
JSONObject nav = (JSONObject) iter.next();
|
|
||||||
JSONArray subNavs = nav.getJSONArray("sub");
|
|
||||||
|
|
||||||
if (subNavs != null && !subNavs.isEmpty()) {
|
|
||||||
for (Iterator<Object> subIter = subNavs.iterator(); subIter.hasNext(); ) {
|
|
||||||
JSONObject subNav = (JSONObject) subIter.next();
|
|
||||||
if (isFilterNav(subNav, user)) {
|
|
||||||
subIter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无子级,移除主菜单
|
|
||||||
if (subNavs.isEmpty()) {
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
} else if (isFilterNav(nav, user)) {
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return navs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否需要过滤掉
|
|
||||||
*
|
|
||||||
* @param nav
|
|
||||||
* @param user
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isFilterNav(JSONObject nav, ID user) {
|
|
||||||
String type = nav.getString("type");
|
|
||||||
if ("ENTITY".equalsIgnoreCase(type)) {
|
|
||||||
String entity = nav.getString("value");
|
|
||||||
if (NAV_PARENT.equals(entity)) {
|
|
||||||
return true;
|
|
||||||
} else if (NAV_FEEDS.equals(entity) || NAV_FILEMRG.equals(entity)) {
|
|
||||||
return false;
|
|
||||||
} else if (!MetadataHelper.containsEntity(entity)) {
|
|
||||||
LOG.warn("Unknow entity in nav : " + entity);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity entityMeta = MetadataHelper.getEntity(entity);
|
|
||||||
return !Application.getSecurityManager().allowRead(user, entityMeta.getEntityCode());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渲染导航菜單
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
* @param activeNav
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String renderNavItem(JSONObject item, String activeNav) {
|
|
||||||
final boolean isUrlType = "URL".equals(item.getString("type"));
|
|
||||||
String navName = item.getString("value");
|
|
||||||
String navUrl = item.getString("value");
|
|
||||||
|
|
||||||
if (isUrlType) {
|
|
||||||
navName = "nav_url-" + navName.hashCode();
|
|
||||||
navUrl = ServerListener.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(navUrl);
|
|
||||||
} else if (NAV_FEEDS.equals(navName)) {
|
|
||||||
navName = "nav_entity-Feeds";
|
|
||||||
navUrl = ServerListener.getContextPath() + "/feeds/home";
|
|
||||||
} else if (NAV_FILEMRG.equals(navName)) {
|
|
||||||
navName = "nav_entity-Attachment";
|
|
||||||
navUrl = ServerListener.getContextPath() + "/files/home";
|
|
||||||
} else {
|
|
||||||
navName = "nav_entity-" + navName;
|
|
||||||
navUrl = ServerListener.getContextPath() + "/app/" + navUrl + "/list";
|
|
||||||
}
|
|
||||||
|
|
||||||
String navIcon = StringUtils.defaultIfBlank(item.getString("icon"), "texture");
|
|
||||||
String navText = item.getString("text");
|
|
||||||
|
|
||||||
JSONArray subNavs = null;
|
|
||||||
if (activeNav != null) {
|
|
||||||
subNavs = item.getJSONArray("sub");
|
|
||||||
if (subNavs == null || subNavs.isEmpty()) {
|
|
||||||
subNavs = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder navHtml = new StringBuilder()
|
|
||||||
.append(String.format("<li class=\"%s\"><a href=\"%s\" target=\"%s\"><i class=\"icon zmdi zmdi-%s\"></i><span>%s</span></a>",
|
|
||||||
navName + (subNavs == null ? StringUtils.EMPTY : " parent"),
|
|
||||||
subNavs == null ? navUrl : "###",
|
|
||||||
isUrlType ? "_blank" : "_self",
|
|
||||||
navIcon,
|
|
||||||
navText));
|
|
||||||
if (subNavs != null) {
|
|
||||||
StringBuilder subHtml = new StringBuilder()
|
|
||||||
.append("<ul class=\"sub-menu\"><li class=\"title\">")
|
|
||||||
.append(navText)
|
|
||||||
.append("</li><li class=\"nav-items\"><div class=\"content\"><ul class=\"sub-menu-ul\">");
|
|
||||||
|
|
||||||
for (Object o : subNavs) {
|
|
||||||
JSONObject subNav = (JSONObject) o;
|
|
||||||
subHtml.append(renderNavItem(subNav, null));
|
|
||||||
}
|
|
||||||
subHtml.append("</ul></div></li></ul>");
|
|
||||||
navHtml.append(subHtml);
|
|
||||||
}
|
|
||||||
navHtml.append("</li>");
|
|
||||||
|
|
||||||
if (activeNav != null) {
|
|
||||||
Document navBody = Jsoup.parseBodyFragment(navHtml.toString());
|
|
||||||
for (Element nav : navBody.select("." + activeNav)) {
|
|
||||||
nav.addClass("active");
|
|
||||||
if (activeNav.startsWith("nav_entity-")) {
|
|
||||||
Element navParent = nav.parent();
|
|
||||||
if (navParent != null && navParent.hasClass("sub-menu-ul")) {
|
|
||||||
navParent.parent().parent().parent().parent().addClass("open active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return navBody.selectFirst("li").outerHtml();
|
|
||||||
}
|
|
||||||
return navHtml.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,9 @@ public enum ConfigurableItem {
|
||||||
// 管理员警告
|
// 管理员警告
|
||||||
AdminDangers(true),
|
AdminDangers(true),
|
||||||
|
|
||||||
|
// 允许同一用户多个会话
|
||||||
|
MultipleSessions(true),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private Object defaultVal;
|
private Object defaultVal;
|
||||||
|
|
|
@ -129,6 +129,17 @@ public class OnlineSessionStore extends CurrentCaller implements HttpSessionList
|
||||||
Object loginUser = s.getAttribute(WebUtils.CURRENT_USER);
|
Object loginUser = s.getAttribute(WebUtils.CURRENT_USER);
|
||||||
Assert.notNull(loginUser, "No login user found in session!");
|
Assert.notNull(loginUser, "No login user found in session!");
|
||||||
|
|
||||||
|
if (!SysConfiguration.getBool(ConfigurableItem.MultipleSessions)) {
|
||||||
|
HttpSession previous = getSession((ID) loginUser);
|
||||||
|
if (previous != null) {
|
||||||
|
LOG.warn("Kill previous session : " + loginUser + " < " + previous.getId());
|
||||||
|
try {
|
||||||
|
previous.invalidate();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ONLINE_SESSIONS.remove(s);
|
ONLINE_SESSIONS.remove(s);
|
||||||
ONLINE_USERS.put((ID) loginUser, s);
|
ONLINE_USERS.put((ID) loginUser, s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,9 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter implements In
|
||||||
// for Language
|
// for Language
|
||||||
Application.getSessionStore().setLocale(AppUtils.getLocale(request));
|
Application.getSessionStore().setLocale(AppUtils.getLocale(request));
|
||||||
// Last active
|
// Last active
|
||||||
Application.getSessionStore().storeLastActive(request);
|
if (!(isIgnoreActive(requestUrl) || ServletUtils.isAjaxRequest(request))) {
|
||||||
|
Application.getSessionStore().storeLastActive(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean chain = super.preHandle(request, response, handler);
|
boolean chain = super.preHandle(request, response, handler);
|
||||||
|
@ -217,6 +219,14 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter implements In
|
||||||
|| reqUrl.startsWith("/commons/barcode/render");
|
|| reqUrl.startsWith("/commons/barcode/render");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param reqUrl
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static boolean isIgnoreActive(String reqUrl) {
|
||||||
|
return reqUrl.contains("/language/") || reqUrl.contains("/user-avatar");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否特定缓存策略
|
* 是否特定缓存策略
|
||||||
*
|
*
|
||||||
|
@ -229,4 +239,6 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter implements In
|
||||||
|| reqUrl.startsWith("/language/")
|
|| reqUrl.startsWith("/language/")
|
||||||
|| reqUrl.startsWith("/commons/barcode/");
|
|| reqUrl.startsWith("/commons/barcode/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
/*
|
/*
|
||||||
rebuild - Building your business-systems freely.
|
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
|
||||||
Copyright (C) 2019 devezhao <zhaofang123@gmail.com>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||||
it under the terms of the GNU General Public License as published by
|
See LICENSE and COMMERCIAL in the project root for license information.
|
||||||
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.admin.bizz;
|
package com.rebuild.web.admin.bizz;
|
||||||
|
|
||||||
|
import cn.devezhao.commons.web.WebUtils;
|
||||||
|
import cn.devezhao.momentjava.Moment;
|
||||||
import cn.devezhao.persist4j.engine.ID;
|
import cn.devezhao.persist4j.engine.ID;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.rebuild.server.Application;
|
||||||
import com.rebuild.server.configuration.portals.DataListManager;
|
import com.rebuild.server.configuration.portals.DataListManager;
|
||||||
|
import com.rebuild.server.service.bizz.UserHelper;
|
||||||
|
import com.rebuild.utils.JSONUtils;
|
||||||
import com.rebuild.utils.LocationUtils;
|
import com.rebuild.utils.LocationUtils;
|
||||||
import com.rebuild.web.BaseEntityControll;
|
import com.rebuild.web.BaseEntityControll;
|
||||||
|
import com.rebuild.web.OnlineSessionStore;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author devezhao-mac zhaofang123@gmail.com
|
* @author devezhao-mac zhaofang123@gmail.com
|
||||||
|
@ -57,4 +56,41 @@ public class LoginLogControll extends BaseEntityControll {
|
||||||
writeFailure(response);
|
writeFailure(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/admin/bizuser/online-users")
|
||||||
|
public void getOnlineUsers(HttpServletResponse response) throws IOException {
|
||||||
|
JSONArray users = new JSONArray();
|
||||||
|
for (HttpSession s : Application.getSessionStore().getAllSession()) {
|
||||||
|
ID user = (ID) s.getAttribute(WebUtils.CURRENT_USER);
|
||||||
|
if (user == null) continue;
|
||||||
|
|
||||||
|
Object[] active = (Object[]) s.getAttribute(OnlineSessionStore.SK_LASTACTIVE);
|
||||||
|
if (active == null) {
|
||||||
|
active = new Object[] { "", "/dashboard/home" };
|
||||||
|
} else {
|
||||||
|
active = active.clone();
|
||||||
|
active[0] = Moment.moment(new Date((Long) active[0])).fromNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject item = JSONUtils.toJSONObject(
|
||||||
|
new String[] { "user", "fullName", "activeTime", "activeUrl" },
|
||||||
|
new Object[] { user, UserHelper.getName(user), active[0], active[1] } );
|
||||||
|
users.add(item);
|
||||||
|
}
|
||||||
|
writeSuccess(response, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/admin/bizuser/kill-session")
|
||||||
|
public void killSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
|
ID user = getIdParameterNotNull(request, "user");
|
||||||
|
HttpSession s = Application.getSessionStore().getSession(user);
|
||||||
|
if (s != null) {
|
||||||
|
LOG.warn("Kill session via admin : " + user + " < " + s.getId());
|
||||||
|
try {
|
||||||
|
s.invalidate();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeSuccess(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||||
<%@ page import="com.alibaba.fastjson.JSONObject"%>
|
<%@ page import="com.alibaba.fastjson.JSONObject"%>
|
||||||
<%@ page import="com.alibaba.fastjson.JSONArray"%>
|
<%@ page import="com.alibaba.fastjson.JSONArray"%>
|
||||||
<%@ page import="com.rebuild.server.configuration.portals.NavManager"%>
|
|
||||||
<%@ page import="com.rebuild.utils.AppUtils" %>
|
<%@ page import="com.rebuild.utils.AppUtils" %>
|
||||||
<%@ page import="com.rebuild.server.service.bizz.privileges.ZeroEntry" %>
|
<%@ page import="com.rebuild.server.service.bizz.privileges.ZeroEntry" %>
|
||||||
|
<%@ page import="com.rebuild.server.configuration.portals.NavBuilder" %>
|
||||||
<%
|
<%
|
||||||
final String activeNav = request.getParameter("activeNav");
|
final String activeNav = request.getParameter("activeNav");
|
||||||
final JSONArray navArray = NavManager.instance.getNavForPortal(request);
|
final JSONArray navArray = NavBuilder.instance.getNavPortal(request);
|
||||||
%>
|
%>
|
||||||
<div class="rb-left-sidebar">
|
<div class="rb-left-sidebar">
|
||||||
<div class="left-sidebar-wrapper">
|
<div class="left-sidebar-wrapper">
|
||||||
|
@ -16,14 +16,14 @@ final JSONArray navArray = NavManager.instance.getNavForPortal(request);
|
||||||
<div class="left-sidebar-content no-divider">
|
<div class="left-sidebar-content no-divider">
|
||||||
<ul class="sidebar-elements">
|
<ul class="sidebar-elements">
|
||||||
<li class="<%="dashboard-home".equals(activeNav) ? "active" : ""%>"><a href="${baseUrl}/dashboard/home"><i class="icon zmdi zmdi-home"></i><span>首页</span></a></li>
|
<li class="<%="dashboard-home".equals(activeNav) ? "active" : ""%>"><a href="${baseUrl}/dashboard/home"><i class="icon zmdi zmdi-home"></i><span>首页</span></a></li>
|
||||||
<% for (Object o : navArray) { out.print(NavManager.instance.renderNavItem((JSONObject) o, activeNav)); } %>
|
<% for (Object o : navArray) out.print(NavBuilder.instance.renderNavItem((JSONObject) o, activeNav)); %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
|
<% if (AppUtils.allow(request, ZeroEntry.AllowCustomNav)) { %>
|
||||||
<div class="bottom-widget">
|
<div class="bottom-widget">
|
||||||
<a class="nav-settings" href="javascript:;" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
|
<a class="nav-settings" title="设置导航菜单"><i class="icon zmdi zmdi-apps"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="dataTables_oper">
|
<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_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> 打开</button>
|
||||||
<button class="btn btn-primary btn-space J_new" type="button"><i class="icon zmdi zmdi-accounts-add"></i> 新建${entityLabel}</button>
|
<button class="btn btn-primary btn-space J_new" type="button"><i class="icon zmdi zmdi-accounts-add"></i> 新建${entityLabel}</button>
|
||||||
<div class="btn-group btn-space">
|
<div class="btn-group btn-space">
|
||||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="dataTables_oper">
|
<div class="dataTables_oper">
|
||||||
|
<button class="btn btn-space btn-secondary J_view-online" type="button"><i class="icon zmdi zmdi-accounts"></i> 查看在线用户</button>
|
||||||
<div class="btn-group btn-space">
|
<div class="btn-group btn-space">
|
||||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
<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">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
@ -59,6 +60,7 @@ window.__PageConfig = {
|
||||||
<script src="${baseUrl}/assets/js/rb-datalist.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.jsx" type="text/babel"></script>
|
||||||
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
|
<script src="${baseUrl}/assets/js/rb-forms.exts.jsx" type="text/babel"></script>
|
||||||
|
<script src="${baseUrl}/assets/js/admin/online-users.jsx" type="text/babel"></script>
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
RbList.renderAfter = function() {
|
RbList.renderAfter = function() {
|
||||||
let ipAddrIndex = -1
|
let ipAddrIndex = -1
|
||||||
|
@ -82,6 +84,9 @@ RbList.renderAfter = function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
$(document).ready(() => {
|
||||||
|
$('.J_view-online').click(() => renderRbcomp(<OnlineUserViewer />))
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="dataTables_oper">
|
<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_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> 打开</button>
|
||||||
<button class="btn btn-primary btn-space J_new" type="button"><i class="icon zmdi zmdi-plus"></i> 新建${entityLabel}</button>
|
<button class="btn btn-primary btn-space J_new" type="button"><i class="icon zmdi zmdi-plus"></i> 新建${entityLabel}</button>
|
||||||
<div class="btn-group btn-space">
|
<div class="btn-group btn-space">
|
||||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-7">
|
<div class="col-12 col-lg-7">
|
||||||
<div class="dataTables_oper">
|
<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_view" type="button" disabled="disabled"><i class="icon zmdi zmdi-folder"></i> 打开</button>
|
||||||
<div class="btn-group btn-space">
|
<div class="btn-group btn-space">
|
||||||
<button class="btn btn-primary J_new" type="button"><i class="icon zmdi zmdi-account-add"></i> 新建${entityLabel}</button>
|
<button class="btn btn-primary J_new" type="button"><i class="icon zmdi zmdi-account-add"></i> 新建${entityLabel}</button>
|
||||||
<button class="btn btn-primary dropdown-toggle auto" type="button" data-toggle="dropdown"><span class="icon zmdi zmdi-chevron-down"></span></button>
|
<button class="btn btn-primary dropdown-toggle auto" type="button" data-toggle="dropdown"><span class="icon zmdi zmdi-chevron-down"></span></button>
|
||||||
|
|
|
@ -57,7 +57,7 @@ a#entityIcon:hover{opacity:0.8}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<button type="button" class="btn btn-danger J_drop-confirm" disabled="disabled" data-loading-text="删除中"><i class="zmdi zmdi-delete icon"></i> 确认删除</button>
|
<button type="button" class="btn btn-danger J_drop-confirm" type="button" disabled="disabled" data-loading-text="删除中"><i class="zmdi zmdi-delete icon"></i> 确认删除</button>
|
||||||
<div class="alert alert-warning alert-icon hide col-sm-6 mb-0">
|
<div class="alert alert-warning alert-icon hide col-sm-6 mb-0">
|
||||||
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
<div class="icon"><span class="zmdi zmdi-alert-triangle"></span></div>
|
||||||
<div class="message">系统内建实体,不允许删除</div>
|
<div class="message">系统内建实体,不允许删除</div>
|
||||||
|
|
70
src/main/webapp/assets/js/admin/online-users.jsx
Normal file
70
src/main/webapp/assets/js/admin/online-users.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) REBUILD <https://getrebuild.com/> and its owners. All rights reserved.
|
||||||
|
|
||||||
|
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||||
|
See LICENSE and COMMERCIAL in the project root for license information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
class OnlineUserViewer extends RbModalHandler {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <RbModal ref={(c) => this._dlg = c} title="在线用户" disposeOnHide={true}>
|
||||||
|
<table className="table table-hover table-sm mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="30%">用户</th>
|
||||||
|
<th>最近活跃</th>
|
||||||
|
<th width="80"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{(this.state.users || []).map((item) => {
|
||||||
|
return (
|
||||||
|
<tr key={`user-${item.user}`}>
|
||||||
|
<td className="user-avatar">
|
||||||
|
<img src={`${rb.baseUrl}/account/user-avatar/${item.user}`} />
|
||||||
|
<span>{item.fullName}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="###" className="text-break">{item.activeUrl || '无'}</a>
|
||||||
|
<p className="text-muted fs-12 m-0">{item.activeTime}</p>
|
||||||
|
</td>
|
||||||
|
<td className="text-right">
|
||||||
|
<button className="btn btn-danger bordered btn-sm" type="button" onClick={() => this._killSession(item.user)}>强退</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</RbModal >
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._load()
|
||||||
|
}
|
||||||
|
|
||||||
|
_load() {
|
||||||
|
$.get('/admin/bizuser/online-users', (res) => {
|
||||||
|
if (res.error_code === 0) this.setState({ users: res.data })
|
||||||
|
else RbHighbar.error(res.error_msg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_killSession(user) {
|
||||||
|
const that = this
|
||||||
|
RbAlert.create('确认强制退出该用户?', {
|
||||||
|
confirm: function () {
|
||||||
|
$.post(`/admin/bizuser/kill-session?user=${user}`, () => {
|
||||||
|
this.hide()
|
||||||
|
that._load()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ $(document).ready(function () {
|
||||||
value = $val('.J_menuUrl')
|
value = $val('.J_menuUrl')
|
||||||
if (!value) {
|
if (!value) {
|
||||||
RbHighbar.create('请输入 URL'); return
|
RbHighbar.create('请输入 URL'); return
|
||||||
} else if (!!value && !$regex.isUrl(value)) {
|
} else if (!($regex.isUrl(value) || $regex.isUrl(`https://getrebuild.com${value}`))) {
|
||||||
RbHighbar.create('请输入有效的 URL')
|
RbHighbar.create('请输入有效的 URL')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<select class="form-control form-control-sm J_menuEntity">
|
<select class="form-control form-control-sm J_menuEntity">
|
||||||
<option value="">请选择关联项</option>
|
<option value="">请选择关联项</option>
|
||||||
<optgroup label="业务实体"></optgroup>
|
<optgroup label="业务实体"></optgroup>
|
||||||
<optgroup label="其他">
|
<optgroup label="系统内建">
|
||||||
<option value="$FEEDS$" data-icon="chart-donut">动态</option>
|
<option value="$FEEDS$" data-icon="chart-donut">动态</option>
|
||||||
<option value="$FILEMRG$" data-icon="folder">文件</option>
|
<option value="$FILEMRG$" data-icon="folder">文件</option>
|
||||||
<option value="$PARENT$" data-icon="menu">父级菜单</option>
|
<option value="$PARENT$" data-icon="menu">父级菜单</option>
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="URL">
|
<div class="tab-pane" id="URL">
|
||||||
<input type="text" class="form-control form-control-sm J_menuUrl" placeholder="输入 URL">
|
<input type="text" class="form-control form-control-sm J_menuUrl" placeholder="输入 URL">
|
||||||
|
<div class="form-text">支持外部地址或相对地址</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
<script src="${baseUrl}/assets/js/zmdi-icons.js"></script>
|
<script src="${baseUrl}/assets/js/zmdi-icons.js"></script>
|
||||||
<script type="text/babel">
|
<script type="text/babel">
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
let call = parent.clickIcon || function(icon){ console.log(icon) }
|
const call = parent && parent.clickIcon ? parent.clickIcon : function(icon){ console.log(icon) }
|
||||||
$(ZMDI_ICONS).each(function(){
|
$(ZMDI_ICONS).each(function(){
|
||||||
if (ZMDI_ICONS_IGNORE.contains(this + '') == false) {
|
if (ZMDI_ICONS_IGNORE.contains(this + '') == false) {
|
||||||
let a = $('<a data-icon="' + this + '" title="' + this.toUpperCase() + '"><i class="zmdi zmdi-' + this + '"></a>').appendTo('#icons')
|
const $a = $('<a data-icon="' + this + '" title="' + this.toUpperCase() + '"><i class="zmdi zmdi-' + this + '"></a>').appendTo('#icons')
|
||||||
a.click(function(){ call($(this).data('icon')) })
|
$a.click(function(){ call($(this).data('icon')) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
parent.RbModal.resize()
|
parent && parent.RbModal && parent.RbModal.resize()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -69,8 +69,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="dataTables_oper">
|
<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_view" type="button" 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-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-border-color"></i> 编辑</button>
|
||||||
<button class="btn btn-space btn-primary J_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">
|
<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>
|
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown">更多 <i class="icon zmdi zmdi-more-vert"></i></button>
|
||||||
|
|
|
@ -67,8 +67,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<div class="dataTables_oper">
|
<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_view" type="button" 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-secondary J_edit" type="button" disabled="disabled"><i class="icon zmdi zmdi-border-color"></i> 编辑</button>
|
||||||
<div class="btn-group btn-space J_action">
|
<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>
|
<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">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
|
|
@ -46,18 +46,18 @@ public class NavManagerTest extends TestSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPortalNav() throws Exception {
|
public void testNavBuilder() throws Exception {
|
||||||
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
|
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
|
||||||
.get("/rebuild")
|
.get("/rebuild")
|
||||||
.sessionAttr(WebUtils.CURRENT_USER, UserService.ADMIN_USER);
|
.sessionAttr(WebUtils.CURRENT_USER, UserService.ADMIN_USER);
|
||||||
HttpServletRequest request = builder.buildRequest(new MockServletContext());
|
HttpServletRequest request = builder.buildRequest(new MockServletContext());
|
||||||
|
|
||||||
JSON navForPortal = NavManager.instance.getNavForPortal(request);
|
JSONArray navForPortal = NavBuilder.instance.getNavPortal(request);
|
||||||
System.out.println("testPortalNav .......... \n" + navForPortal.toJSONString());
|
System.out.println("testPortalNav .......... \n" + navForPortal.toJSONString());
|
||||||
|
|
||||||
if (!((JSONArray) navForPortal).isEmpty()) {
|
if (!navForPortal.isEmpty()) {
|
||||||
JSONObject firstNav = (JSONObject) ((JSONArray) navForPortal).get(0);
|
JSONObject firstNav = (JSONObject) navForPortal.get(0);
|
||||||
String navHtml = NavManager.instance.renderNavItem(firstNav, "home");
|
String navHtml = NavBuilder.instance.renderNavItem(firstNav, "home");
|
||||||
System.out.println(navHtml);
|
System.out.println(navHtml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue