From 04da92db0a5a69614fa05aa59e7fb43a9673d6a6 Mon Sep 17 00:00:00 2001 From: bwcx_jzy Date: Sat, 22 Jan 2022 16:11:58 +0800 Subject: [PATCH] feat: node stat --- CHANGELOG.md | 9 +- .../commander/impl/LinuxSystemCommander.java | 25 +-- .../io/jpom/system/init/AutoRegSeverNode.java | 2 + modules/agent/src/test/java/TestStr.java | 7 + .../java/io/jpom/plugin/ClassFeature.java | 1 + .../interceptor/PermissionInterceptor.java | 2 +- .../controller/node/NodeStatController.java | 53 +++++ .../node/ssh/SshInstallAgentController.java | 3 +- .../java/io/jpom/model/data/NodeModel.java | 3 + .../io/jpom/model/stat/NodeStatModel.java | 196 ++++++++++++++++ .../java/io/jpom/monitor/NodeMonitor.java | 209 ++++++++++++++---- .../service/h2db/BaseDbCommonService.java | 2 +- .../io/jpom/service/node/NodeService.java | 75 +++---- .../io/jpom/service/stat/NodeStatService.java | 35 +++ .../io/jpom/system/init/CheckMonitor.java | 3 + .../src/main/resources/menus/index.json | 5 +- .../src/main/resources/menus/node-index.json | 1 - .../src/main/resources/sql/h2-db-v3.0.sql | 27 +++ web-vue/src/api/node-stat.js | 39 ++++ web-vue/src/api/node.js | 9 - web-vue/src/components/codeEditor/index.vue | 40 +++- web-vue/src/components/lazy_antd.js | 4 +- web-vue/src/pages/node/list.vue | 10 +- web-vue/src/pages/node/stat.vue | 168 ++++++++++++++ web-vue/src/pages/system/config.vue | 2 +- web-vue/src/router/index.js | 5 + web-vue/src/router/route-menu.js | 1 + 27 files changed, 797 insertions(+), 139 deletions(-) create mode 100644 modules/server/src/main/java/io/jpom/controller/node/NodeStatController.java create mode 100644 modules/server/src/main/java/io/jpom/model/stat/NodeStatModel.java create mode 100644 modules/server/src/main/java/io/jpom/service/stat/NodeStatService.java create mode 100644 web-vue/src/api/node-stat.js create mode 100644 web-vue/src/pages/node/stat.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index c9cbb94d8..691269e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ ### 新增功能 -1. 【server】系统配置新增节点白名单、节点系统配置分发功能,方便集群节点统一配置 -2. 【server】构建新增快捷复制功能,方便快速创建类型一致的项目 -3. 【server】系统配置新增配置菜单是否显示,用于非超级管理员页面菜单控制 +1. 【server】新增系统配置-节点白名单、节点系统配置分发功能,方便集群节点统一配置 +2. 【server】新增构建快捷复制功能,方便快速创建类型一致的项目 +3. 【server】新增系统配置-配置菜单是否显示,用于非超级管理员页面菜单控制 +4. 【server】新增节点统计功能,快速了解当前所有节点状态 ### 解决BUG、优化功能 @@ -14,6 +15,8 @@ 2. 【server】修复快速安装服务端 ping 检查超时时间 5ms to 5s 3. 项目文本文件支持在线实时阅读(感谢@) 4. 【server】控制台日志支持搜索高亮 +5. 【server】跨工作空间更新节点授权将自动同步更新 +6. 【server】取消节点监控周期字段(采用全局统计) ------ diff --git a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java b/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java index df1e7a077..b97617707 100644 --- a/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java +++ b/modules/agent/src/main/java/io/jpom/common/commander/impl/LinuxSystemCommander.java @@ -22,6 +22,7 @@ */ package io.jpom.common.commander.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.text.CharPool; @@ -207,12 +208,13 @@ public class LinuxSystemCommander extends AbstractSystemCommander { * @param info cpu信息 * @return cpu信息 */ - private static String getLinuxCpu(String info) { - if (StrUtil.isEmpty(info)) { + public static String getLinuxCpu(String info) { + List strings = StrUtil.splitTrim(info, StrUtil.COLON); + String last = CollUtil.getLast(strings); + List list = StrUtil.splitTrim(last, StrUtil.COMMA); + if (CollUtil.isEmpty(list)) { return null; } - int i = info.indexOf(CharPool.COLON); - String[] split = info.substring(i + 1).split(StrUtil.COMMA); // 1.3% us — 用户空间占用CPU的百分比。 // 1.0% sy — 内核空间占用CPU的百分比。 // 0.0% ni — 改变过优先级的进程占用CPU的百分比 @@ -220,17 +222,12 @@ public class LinuxSystemCommander extends AbstractSystemCommander { // 0.0% wa — IO等待占用CPU的百分比 // 0.3% hi — 硬中断(Hardware IRQ)占用CPU的百分比 // 0.0% si — 软中断(Software Interrupts)占用CPU的百分比 - for (String str : split) { - str = str.trim(); - String value = str.substring(0, str.length() - 2).trim(); - String tag = str.substring(str.length() - 2); - if ("id".equalsIgnoreCase(tag)) { - value = value.replace("%", ""); - double val = Convert.toDouble(value, 0.0); - return String.format("%.2f", 100.00 - val); - } + String value = list.stream().filter(s -> StrUtil.endWithIgnoreCase(s, "id")).map(s -> StrUtil.removeSuffixIgnoreCase(s, "id")).findAny().orElse(null); + Double val = Convert.toDouble(value); + if (val == null) { + return null; } - return "0"; + return String.format("%.2f", 100.00 - val); } @Override diff --git a/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java b/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java index faf22c607..b28887003 100644 --- a/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java +++ b/modules/agent/src/main/java/io/jpom/system/init/AutoRegSeverNode.java @@ -136,6 +136,8 @@ public class AutoRegSeverNode { * @param url 服务端url */ public static void autoPushToServer(String url) { + url = StrUtil.removeSuffix(url, CharPool.SINGLE_QUOTE + ""); + url = StrUtil.removePrefix(url, CharPool.SINGLE_QUOTE + ""); UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); // LinkedHashSet localAddressList = NetUtil.localAddressList(address -> { diff --git a/modules/agent/src/test/java/TestStr.java b/modules/agent/src/test/java/TestStr.java index 46aabfa92..8262aa3e7 100644 --- a/modules/agent/src/test/java/TestStr.java +++ b/modules/agent/src/test/java/TestStr.java @@ -23,6 +23,7 @@ import cn.hutool.core.lang.RegexPool; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ReUtil; +import io.jpom.common.commander.impl.LinuxSystemCommander; import org.junit.Test; /** @@ -47,4 +48,10 @@ public class TestStr { System.out.println(String.format("%.2f", (float)1 / (float)2 * 100)); System.out.println(NumberUtil.div(1,2)); } + + @Test + public void testParse(){ + String linuxCpu = LinuxSystemCommander.getLinuxCpu("%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\n"); + System.out.println(linuxCpu); + } } diff --git a/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java b/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java index eabd85578..c330e4389 100644 --- a/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java +++ b/modules/common/src/main/java/io/jpom/plugin/ClassFeature.java @@ -34,6 +34,7 @@ public enum ClassFeature { */ NULL(""), NODE("节点管理"), + NODE_STAT("节点统计"), UPGRADE_NODE_LIST("节点升级"), SEARCH_PROJECT("搜索项目"), SSH("SSH管理"), diff --git a/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java b/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java index 70cc2ad86..0536cc116 100644 --- a/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java +++ b/modules/server/src/main/java/io/jpom/common/interceptor/PermissionInterceptor.java @@ -127,7 +127,7 @@ public class PermissionInterceptor extends BaseJpomInterceptor { String workspaceId = ServletUtil.getHeader(request, Const.WORKSPACEID_REQ_HEADER, CharsetUtil.CHARSET_UTF_8); boolean exists = userBindWorkspaceService.exists(userModel.getId(), workspaceId + StrUtil.DASHED + method.name()); if (!exists) { - this.errorMsg(response, "您没有对应功能管理权限:" + method.getName()); + this.errorMsg(response, "您没有对应功能【" + feature.cls().getName() + "】管理权限:" + method.getName()); return false; } } diff --git a/modules/server/src/main/java/io/jpom/controller/node/NodeStatController.java b/modules/server/src/main/java/io/jpom/controller/node/NodeStatController.java new file mode 100644 index 000000000..f9f7bed71 --- /dev/null +++ b/modules/server/src/main/java/io/jpom/controller/node/NodeStatController.java @@ -0,0 +1,53 @@ +package io.jpom.controller.node; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.db.Entity; +import cn.jiangzeyin.common.JsonMessage; +import io.jpom.common.BaseServerController; +import io.jpom.model.PageResultDto; +import io.jpom.model.stat.NodeStatModel; +import io.jpom.plugin.ClassFeature; +import io.jpom.plugin.Feature; +import io.jpom.plugin.MethodFeature; +import io.jpom.service.stat.NodeStatService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 2022/1/22 + */ +@RestController +@RequestMapping(value = "/node/stat") +@Feature(cls = ClassFeature.NODE_STAT) +public class NodeStatController extends BaseServerController { + + private final NodeStatService nodeStatService; + + public NodeStatController(NodeStatService nodeStatService) { + this.nodeStatService = nodeStatService; + } + + @PostMapping(value = "list_data.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public String listJson() { + PageResultDto nodeModelPageResultDto = nodeStatService.listPage(getRequest()); + return JsonMessage.getString(200, "", nodeModelPageResultDto); + } + + @GetMapping(value = "status_stat.json", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public String statusStat() { + String workspaceId = nodeStatService.getCheckUserWorkspace(getRequest()); + String sql = "select `status`,count(1) as cunt from " + nodeStatService.getTableName() + " where workspaceId=? group by `status`"; + List list = nodeStatService.query(sql, workspaceId); + Map map = CollStreamUtil.toMap(list, entity -> entity.getStr("status"), entity -> entity.getInt("cunt")); + return JsonMessage.getString(200, "", map); + } +} diff --git a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java b/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java index 02af3a74b..4c11c5786 100644 --- a/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java +++ b/modules/server/src/main/java/io/jpom/controller/node/ssh/SshInstallAgentController.java @@ -16,7 +16,6 @@ import cn.jiangzeyin.controller.multipart.MultipartFileBuilder; import com.alibaba.fastjson.JSONObject; import io.jpom.common.BaseServerController; import io.jpom.common.Type; -import io.jpom.model.Cycle; import io.jpom.model.data.NodeModel; import io.jpom.model.data.SshModel; import io.jpom.model.system.AgentAutoUser; @@ -199,7 +198,7 @@ public class SshInstallAgentController extends BaseServerController { Assert.hasText(nodeModel.getName(), "输入节点名称"); Assert.hasText(nodeModel.getUrl(), "请输入节点地址"); - nodeModel.setCycle(Cycle.one.getCode()); + //nodeModel.setCycle(Cycle.one.getCode()); // //nodeModel.setProtocol(StrUtil.emptyToDefault(nodeModel.getProtocol(), "http")); // diff --git a/modules/server/src/main/java/io/jpom/model/data/NodeModel.java b/modules/server/src/main/java/io/jpom/model/data/NodeModel.java index 71f5faba4..fd6e7be51 100644 --- a/modules/server/src/main/java/io/jpom/model/data/NodeModel.java +++ b/modules/server/src/main/java/io/jpom/model/data/NodeModel.java @@ -69,6 +69,7 @@ public class NodeModel extends BaseGroupModel { * * @see io.jpom.model.Cycle */ + @Deprecated private Integer cycle; public String getName() { @@ -79,6 +80,7 @@ public class NodeModel extends BaseGroupModel { this.name = name; } + @Deprecated public Integer getCycle() { return cycle; } @@ -87,6 +89,7 @@ public class NodeModel extends BaseGroupModel { * @param cycle 监控频率 * @see io.jpom.model.Cycle */ + @Deprecated public void setCycle(Integer cycle) { this.cycle = cycle; } diff --git a/modules/server/src/main/java/io/jpom/model/stat/NodeStatModel.java b/modules/server/src/main/java/io/jpom/model/stat/NodeStatModel.java new file mode 100644 index 000000000..4e65f45ce --- /dev/null +++ b/modules/server/src/main/java/io/jpom/model/stat/NodeStatModel.java @@ -0,0 +1,196 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Code Technology Studio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package io.jpom.model.stat; + +import io.jpom.model.BaseWorkspaceModel; +import io.jpom.model.log.SystemMonitorLog; +import io.jpom.service.h2db.TableName; + +/** + * @author bwcx_jzy + * @see SystemMonitorLog + * @since 2022/1/22 + */ +@TableName(value = "NODE_STAT", name = "节点统计") +public class NodeStatModel extends BaseWorkspaceModel { + /** + * 占用cpu + */ + private Double occupyCpu; + /** + * 占用内存 (总共) + */ + private Double occupyMemory; + /** + * 占用内存 (使用) @author jzy + */ + private Double occupyMemoryUsed; + /** + * 占用磁盘 + */ + private Double occupyDisk; + /** + * 网络耗时 + */ + private Integer networkTime; + /** + * 运行时间 + */ + private String upTimeStr; + /** + * 系统名称 + */ + private String osName; + /** + * jpom 版本 + */ + private String jpomVersion; + + /** + * 状态{1,无法连接,0 正常, 2 授权信息错误, 3 状态码错误} + */ + private Integer status; + /** + * 开启状态,如果关闭状态就暂停使用节点 + */ + private Integer openStatus; + + /** + * 错误消息 + */ + private String failureMsg; + + /** + * 节点地址 + */ + private String url; + + /** + * 节点名称 + */ + private String name; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFailureMsg() { + return failureMsg; + } + + public void setFailureMsg(String failureMsg) { + this.failureMsg = failureMsg; + } + + public Double getOccupyCpu() { + return occupyCpu; + } + + public void setOccupyCpu(Double occupyCpu) { + this.occupyCpu = occupyCpu; + } + + public Double getOccupyMemory() { + return occupyMemory; + } + + public void setOccupyMemory(Double occupyMemory) { + this.occupyMemory = occupyMemory; + } + + public Double getOccupyMemoryUsed() { + return occupyMemoryUsed; + } + + public void setOccupyMemoryUsed(Double occupyMemoryUsed) { + this.occupyMemoryUsed = occupyMemoryUsed; + } + + public Double getOccupyDisk() { + return occupyDisk; + } + + public void setOccupyDisk(Double occupyDisk) { + this.occupyDisk = occupyDisk; + } + + public Integer getNetworkTime() { + return networkTime; + } + + public void setNetworkTime(Integer networkTime) { + this.networkTime = networkTime; + } + + public String getUpTimeStr() { + return upTimeStr; + } + + public void setUpTimeStr(String upTimeStr) { + this.upTimeStr = upTimeStr; + } + + public String getOsName() { + return osName; + } + + public void setOsName(String osName) { + this.osName = osName; + } + + public String getJpomVersion() { + return jpomVersion; + } + + public void setJpomVersion(String jpomVersion) { + this.jpomVersion = jpomVersion; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Integer getOpenStatus() { + return openStatus; + } + + public void setOpenStatus(Integer openStatus) { + this.openStatus = openStatus; + } +} diff --git a/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java b/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java index 755d01888..178bb551a 100644 --- a/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java +++ b/modules/server/src/main/java/io/jpom/monitor/NodeMonitor.java @@ -22,7 +22,11 @@ */ package io.jpom.monitor; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.SystemClock; import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.cron.CronUtil; import cn.hutool.cron.pattern.CronPattern; import cn.hutool.cron.task.Task; @@ -30,16 +34,22 @@ import cn.jiangzeyin.common.DefaultSystemLog; import cn.jiangzeyin.common.JsonMessage; import cn.jiangzeyin.common.spring.SpringUtil; import com.alibaba.fastjson.JSONObject; +import io.jpom.common.BaseServerController; import io.jpom.common.forward.NodeForward; import io.jpom.common.forward.NodeUrl; +import io.jpom.cron.CronUtils; import io.jpom.model.Cycle; import io.jpom.model.data.NodeModel; +import io.jpom.model.data.UserModel; import io.jpom.model.log.SystemMonitorLog; +import io.jpom.model.stat.NodeStatModel; import io.jpom.service.dblog.DbSystemMonitorLogService; import io.jpom.service.node.NodeService; -import io.jpom.cron.CronUtils; +import io.jpom.service.stat.NodeStatService; +import io.jpom.system.AuthorizeException; import java.util.List; +import java.util.stream.Collectors; /** * 节点监控 @@ -52,6 +62,8 @@ public class NodeMonitor implements Task { private static final String CRON_ID = "node_monitor"; private static DbSystemMonitorLogService dbSystemMonitorLogService; + private static NodeStatService nodeStatService; + private static NodeService nodeService; /** * 开启调度 @@ -62,7 +74,18 @@ public class NodeMonitor implements Task { CronPattern cronPattern = Cycle.seconds30.getCronPattern(); CronUtils.upsert(CRON_ID, cronPattern.toString(), new NodeMonitor()); } - dbSystemMonitorLogService = SpringUtil.getBean(DbSystemMonitorLogService.class); + } + + private void init() { + if (dbSystemMonitorLogService == null) { + dbSystemMonitorLogService = SpringUtil.getBean(DbSystemMonitorLogService.class); + } + if (nodeStatService == null) { + nodeStatService = SpringUtil.getBean(NodeStatService.class); + } + if (nodeService == null) { + nodeService = SpringUtil.getBean(NodeService.class); + } } public static void stop() { @@ -71,61 +94,153 @@ public class NodeMonitor implements Task { @Override public void execute() { - long time = System.currentTimeMillis(); + this.init(); NodeService nodeService = SpringUtil.getBean(NodeService.class); - // - List nodeModels = nodeService.listByCycle(Cycle.seconds30); - // - if (Cycle.one.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.one)); - } - // - if (Cycle.five.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.five)); - } - // - if (Cycle.ten.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.ten)); - } - // - if (Cycle.thirty.getCronPattern().match(time, CronUtil.getScheduler().isMatchSecond())) { - nodeModels.addAll(nodeService.listByCycle(Cycle.thirty)); - } + List nodeModels = nodeService.listDeDuplicationByUrl(); // this.checkList(nodeModels); } - private void checkList(List nodeModels) { - if (nodeModels == null || nodeModels.isEmpty()) { - return; - } - nodeModels.forEach(nodeModel -> ThreadUtil.execute(() -> { - try { - getNodeInfo(nodeModel); - } catch (Exception e) { - DefaultSystemLog.getLog().error("获取节点监控信息失败:{}", e.getMessage()); - } - })); + private List getListByUrl(String url) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setUrl(url); + return nodeService.listByBean(nodeModel); } - private void getNodeInfo(NodeModel nodeModel) { - JsonMessage message = NodeForward.request(nodeModel, null, NodeUrl.GetDirectTop); - JSONObject jsonObject = message.getData(); - if (jsonObject == null) { + private void checkList(List nodeModels) { + if (CollUtil.isEmpty(nodeModels)) { return; } - double disk = jsonObject.getDoubleValue("disk"); - if (disk <= 0) { - return; + nodeModels.forEach(nodeModel -> { + // + nodeModel.setName(nodeModel.getUrl()); + List modelList = this.getListByUrl(nodeModel.getUrl()); + boolean match = modelList.stream().allMatch(NodeModel::isOpenStatus); + if (!match) { + // 节点都关闭 + return; + } + nodeModel.setOpenStatus(1); + nodeModel.setTimeOut(5); + // + ThreadUtil.execute(() -> { + try { + BaseServerController.resetInfo(UserModel.EMPTY); + JSONObject nodeTopInfo = this.getNodeTopInfo(nodeModel); + // + long timeMillis = SystemClock.now(); + JsonMessage jsonMessage = NodeForward.requestBySys(nodeModel, NodeUrl.Status, "nodeId", nodeModel.getId()); + int networkTime = (int) (System.currentTimeMillis() - timeMillis); + JSONObject jsonObject; + if (jsonMessage.getCode() == 200) { + jsonObject = jsonMessage.getData(JSONObject.class); + jsonObject.put("networkTime", networkTime); + this.save(modelList, nodeTopInfo, jsonObject); + } else { + // 状态码错 + jsonObject = new JSONObject(); + jsonObject.put("status", 3); + jsonObject.put("failureMsg", jsonMessage.toString()); + jsonObject.put("networkTime", networkTime); + this.save(modelList, nodeTopInfo, jsonObject); + } + } catch (AuthorizeException agentException) { + this.save(modelList, 2, agentException.getMessage()); + } catch (Exception e) { + this.save(modelList, 1, e.getMessage()); + DefaultSystemLog.getLog().error("获取节点监控信息失败", e); + } finally { + BaseServerController.removeEmpty(); + } + }); + }); + } + + private void saveSystemMonitor(List modelList, JSONObject systemMonitor) { + if (systemMonitor != null) { + List monitorLogs = modelList.stream().map(nodeModel -> { + SystemMonitorLog log = new SystemMonitorLog(); + log.setOccupyMemory(systemMonitor.getDouble("memory")); + log.setOccupyMemoryUsed(systemMonitor.getDouble("memoryUsed")); + log.setOccupyDisk(systemMonitor.getDouble("disk")); + log.setOccupyCpu(systemMonitor.getDouble("cpu")); + log.setMonitorTime(systemMonitor.getLongValue("time")); + log.setNodeId(nodeModel.getId()); + return log; + }).collect(Collectors.toList()); + // + dbSystemMonitorLogService.insert(monitorLogs); } + } + + /** + * 更新状态 和错误信息 + * + * @param modelList 节点 + * @param satus 状态 + * @param msg 错误消息 + */ + private void save(List modelList, int satus, String msg) { + for (NodeModel nodeModel : modelList) { + NodeStatModel nodeStatModel = this.create(nodeModel); + nodeStatModel.setFailureMsg(StrUtil.maxLength(msg, 240)); + nodeStatModel.setStatus(satus); + nodeStatService.upsert(nodeStatModel); + } + } + + /** + * 报错结果 + * + * @param modelList 节点 + * @param systemMonitor 系统监控 + * @param statusData 状态数据 + */ + private void save(List modelList, JSONObject systemMonitor, JSONObject statusData) { + this.saveSystemMonitor(modelList, systemMonitor); // - SystemMonitorLog log = new SystemMonitorLog(); - log.setOccupyMemory(jsonObject.getDoubleValue("memory")); - log.setOccupyMemoryUsed(jsonObject.getDoubleValue("memoryUsed")); - log.setOccupyDisk(disk); - log.setOccupyCpu(jsonObject.getDoubleValue("cpu")); - log.setMonitorTime(jsonObject.getLongValue("time")); - log.setNodeId(nodeModel.getId()); - dbSystemMonitorLogService.insert(log); + for (NodeModel nodeModel : modelList) { + NodeStatModel nodeStatModel = this.create(nodeModel); + if (systemMonitor != null) { + nodeStatModel.setOccupyMemory(ObjectUtil.defaultIfNull(systemMonitor.getDouble("memory"), -1D)); + nodeStatModel.setOccupyMemoryUsed(ObjectUtil.defaultIfNull(systemMonitor.getDouble("memoryUsed"), -1D)); + nodeStatModel.setOccupyDisk(ObjectUtil.defaultIfNull(systemMonitor.getDouble("disk"), -1D)); + nodeStatModel.setOccupyCpu(ObjectUtil.defaultIfNull(systemMonitor.getDouble("cpu"), -1D)); + } + // + nodeStatModel.setNetworkTime(statusData.getIntValue("networkTime")); + nodeStatModel.setJpomVersion(statusData.getString("jpomVersion")); + nodeStatModel.setOsName(statusData.getString("osName")); + nodeStatModel.setUpTimeStr(statusData.getString("runTime")); + nodeStatModel.setFailureMsg(StrUtil.emptyToDefault(statusData.getString("failureMsg"), StrUtil.EMPTY)); + // + Integer statusInteger = statusData.getInteger("status"); + if (statusInteger != null) { + nodeStatModel.setStatus(statusInteger); + } else { + nodeStatModel.setStatus(0); + } + nodeStatModel.setOpenStatus(nodeStatModel.getOpenStatus()); + nodeStatService.upsert(nodeStatModel); + } + } + + private NodeStatModel create(NodeModel model) { + NodeStatModel nodeStatModel = new NodeStatModel(); + nodeStatModel.setId(model.getId()); + nodeStatModel.setWorkspaceId(model.getWorkspaceId()); + nodeStatModel.setName(model.getName()); + nodeStatModel.setUrl(model.getUrl()); + return nodeStatModel; + } + + /** + * 获取节点监控信息 + * + * @param reqNode 真实节点 + */ + private JSONObject getNodeTopInfo(NodeModel reqNode) { + JsonMessage message = NodeForward.request(reqNode, null, NodeUrl.GetDirectTop); + return message.getData(); } } diff --git a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java b/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java index 354d1a038..fce428e25 100644 --- a/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java +++ b/modules/server/src/main/java/io/jpom/service/h2db/BaseDbCommonService.java @@ -285,7 +285,7 @@ public abstract class BaseDbCommonService { * @param 乏型 * @return data */ - private R entityToBean(Entity entity, Class rClass) { + protected R entityToBean(Entity entity, Class rClass) { if (entity == null) { return null; } diff --git a/modules/server/src/main/java/io/jpom/service/node/NodeService.java b/modules/server/src/main/java/io/jpom/service/node/NodeService.java index aa78d84cb..36666450c 100644 --- a/modules/server/src/main/java/io/jpom/service/node/NodeService.java +++ b/modules/server/src/main/java/io/jpom/service/node/NodeService.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; import cn.hutool.extra.servlet.ServletUtil; +import cn.jiangzeyin.common.DefaultSystemLog; import cn.jiangzeyin.common.JsonMessage; import cn.jiangzeyin.common.spring.SpringUtil; import io.jpom.common.BaseServerController; @@ -12,13 +13,11 @@ import io.jpom.common.Const; import io.jpom.common.JpomManifest; import io.jpom.common.forward.NodeForward; import io.jpom.common.forward.NodeUrl; -import io.jpom.cron.ICron; import io.jpom.model.Cycle; import io.jpom.model.data.NodeModel; import io.jpom.model.data.SshModel; import io.jpom.model.data.UserModel; import io.jpom.model.data.WorkspaceModel; -import io.jpom.monitor.NodeMonitor; import io.jpom.service.h2db.BaseGroupService; import io.jpom.service.node.ssh.SshService; import io.jpom.service.system.WorkspaceService; @@ -26,17 +25,17 @@ import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; /** * @author bwcx_jzy * @since 2021/12/4 */ @Service -public class NodeService extends BaseGroupService implements ICron { +public class NodeService extends BaseGroupService { private final SshService sshService; private final WorkspaceService workspaceService; @@ -203,10 +202,7 @@ public class NodeService extends BaseGroupService implements ICron { public void insert(NodeModel nodeModel) { this.fillNodeInfo(nodeModel); super.insert(nodeModel); - Integer cycle = nodeModel.getCycle(); - if (nodeModel.isOpenStatus() && cycle != null && cycle != Cycle.none.getCode()) { - NodeMonitor.start(); - } + this.updateDuplicateNode(nodeModel); } @Override @@ -214,10 +210,7 @@ public class NodeService extends BaseGroupService implements ICron { nodeModel.setWorkspaceId(Const.WORKSPACE_DEFAULT_ID); this.fillNodeInfo(nodeModel); super.insertNotFill(nodeModel); - Integer cycle = nodeModel.getCycle(); - if (nodeModel.isOpenStatus() && cycle != null && cycle != Cycle.none.getCode()) { - NodeMonitor.start(); - } + this.updateDuplicateNode(nodeModel); } /** @@ -241,50 +234,48 @@ public class NodeService extends BaseGroupService implements ICron { return super.del(where); } - @Override - public int update(NodeModel nodeModel) { - int update = super.update(nodeModel); - this.startCron(); - return update; - } - @Override public int updateById(NodeModel info) { int updateById = super.updateById(info); - this.startCron(); + if (updateById > 0) { + this.updateDuplicateNode(info); + } return updateById; } - @Override - public int startCron() { - // 关闭监听 - Entity entity = Entity.create(); - entity.set("openStatus", 1); - entity.set("cycle", StrUtil.format(" <> {}", Cycle.none.getCode())); - long count = super.count(entity); - if (count <= 0) { - NodeMonitor.stop(); - } else { - NodeMonitor.start(); + /** + * 更新相同节点对 授权信息 + * + * @param info 节点信息 + */ + private void updateDuplicateNode(NodeModel info) { + if (StrUtil.hasEmpty(info.getUrl(), info.getLoginName(), info.getLoginPwd())) { + return; + } + NodeModel update = new NodeModel(); + update.setLoginName(info.getLoginName()); + update.setLoginPwd(info.getLoginPwd()); + // + NodeModel where = new NodeModel(); + where.setUrl(info.getUrl()); + int updateCount = super.update(super.dataBeanToEntity(update), super.dataBeanToEntity(where)); + if (updateCount > 1) { + DefaultSystemLog.getLog().debug("update duplicate node {} {}", info.getUrl(), updateCount); } - return (int) count; } /** - * 根据周期获取list + * 根据 url 去重 * - * @param cycle 周期 * @return list */ - public List listByCycle(Cycle cycle) { - NodeModel nodeModel = new NodeModel(); - nodeModel.setCycle(cycle.getCode()); - nodeModel.setOpenStatus(1); - List list = this.listByBean(nodeModel); - if (list == null) { - return new ArrayList<>(); + public List listDeDuplicationByUrl() { + String sql = "select url,max(loginName) as loginName,max(loginPwd) as loginPwd,max(protocol) as protocol from " + super.getTableName() + " group by url"; + List query = this.query(sql); + if (query != null) { + return query.stream().map((entity -> this.entityToBean(entity, this.tClass))).collect(Collectors.toList()); } - return list; + return null; } public List getNodeBySshId(String sshId) { diff --git a/modules/server/src/main/java/io/jpom/service/stat/NodeStatService.java b/modules/server/src/main/java/io/jpom/service/stat/NodeStatService.java new file mode 100644 index 000000000..446c7518e --- /dev/null +++ b/modules/server/src/main/java/io/jpom/service/stat/NodeStatService.java @@ -0,0 +1,35 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Code Technology Studio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package io.jpom.service.stat; + +import io.jpom.model.stat.NodeStatModel; +import io.jpom.service.h2db.BaseWorkspaceService; +import org.springframework.stereotype.Service; + +/** + * @author bwcx_jzy + * @since 2022/1/22 + */ +@Service +public class NodeStatService extends BaseWorkspaceService { +} diff --git a/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java b/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java index ff9fe4092..571d28fe2 100644 --- a/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java +++ b/modules/server/src/main/java/io/jpom/system/init/CheckMonitor.java @@ -37,6 +37,7 @@ import io.jpom.common.forward.NodeUrl; import io.jpom.cron.CronUtils; import io.jpom.cron.ICron; import io.jpom.model.data.NodeModel; +import io.jpom.monitor.NodeMonitor; import io.jpom.service.IStatusRecover; import io.jpom.service.dblog.BackupInfoService; import io.jpom.service.node.NodeService; @@ -141,6 +142,8 @@ public class CheckMonitor { DefaultSystemLog.getLog().debug("{} Recover bad data {}", name, count); } }); + // 节点监控 + NodeMonitor.start(); // RemoteVersion.loadRemoteInfo(); }); diff --git a/modules/server/src/main/resources/menus/index.json b/modules/server/src/main/resources/menus/index.json index 06fa21404..4925179fb 100644 --- a/modules/server/src/main/resources/menus/index.json +++ b/modules/server/src/main/resources/menus/index.json @@ -8,6 +8,10 @@ "title": "节点列表", "id": "nodeList" }, + { + "title": "节点统计", + "id": "nodeStat" + }, { "title": "项目列表", "id": "projectSearch" @@ -129,7 +133,6 @@ { "title": "系统管理", "icon_v3": "setting", - "role": "sys", "id": "setting", "childs": [ { diff --git a/modules/server/src/main/resources/menus/node-index.json b/modules/server/src/main/resources/menus/node-index.json index 77d5cd32f..e5075563e 100644 --- a/modules/server/src/main/resources/menus/node-index.json +++ b/modules/server/src/main/resources/menus/node-index.json @@ -61,7 +61,6 @@ "id": "systemConfig", "title": "系统管理", "icon_v3": "setting", - "role": "sys", "childs": [ { "id": "whitelistDirectory", diff --git a/modules/sub-plugin/db-h2/src/main/resources/sql/h2-db-v3.0.sql b/modules/sub-plugin/db-h2/src/main/resources/sql/h2-db-v3.0.sql index 31c151a71..a223f9ae3 100644 --- a/modules/sub-plugin/db-h2/src/main/resources/sql/h2-db-v3.0.sql +++ b/modules/sub-plugin/db-h2/src/main/resources/sql/h2-db-v3.0.sql @@ -330,4 +330,31 @@ CREATE TABLE IF NOT EXISTS PUBLIC.SERVER_SCRIPT_EXECUTE_LOG comment on table SERVER_SCRIPT_EXECUTE_LOG is '脚本模版执行记录'; +-- 节点统计 +CREATE TABLE IF NOT EXISTS PUBLIC.NODE_STAT +( + id VARCHAR(50) not null comment 'id', + createTimeMillis BIGINT COMMENT '数据创建时间', + modifyTimeMillis BIGINT COMMENT '数据修改时间', + modifyUser VARCHAR(50) comment '修改人', + strike int DEFAULT 0 comment '逻辑删除{1,删除,0 未删除(默认)}', + workspaceId varchar(50) not null comment '所属工作空间', + occupyMemoryUsed DOUBLE comment '占用cpu', + occupyCpu DOUBLE comment '占用cpu', + occupyMemory DOUBLE comment '占用内存', + occupyDisk DOUBLE comment '占用磁盘', + networkTime int DEFAULT 0 comment '网络耗时', + upTimeStr varchar(50) comment '运行时长', + osName varchar(100) comment '所属工作空间', + jpomVersion varchar(50) comment 'jpom 版本', + status int DEFAULT 0 comment '状态{1,无法连接,0 正常, 2 授权信息错误}', + openStatus int DEFAULT 0 comment '启用状态{1,启用,0 未启用)}', + failureMsg VARCHAR(255) comment '错误消息', + url VARCHAR(255) comment '节点地址', + name VARCHAR(255) comment '节点名称', + CONSTRAINT NODE_STAT_PK PRIMARY KEY (id) +); +comment on table NODE_STAT is '节点统计'; + + diff --git a/web-vue/src/api/node-stat.js b/web-vue/src/api/node-stat.js new file mode 100644 index 000000000..0076279c7 --- /dev/null +++ b/web-vue/src/api/node-stat.js @@ -0,0 +1,39 @@ +import axios from "./config"; + +// node 列表 +export function getStatist(params) { + return axios({ + url: "/node/stat/list_data.json", + method: "post", + params: params, + headers: { + loading: "no", + }, + }); +} + +// node 列表 +export function statusStat() { + return axios({ + url: "/node/stat/status_stat.json", + method: "get", + headers: { + loading: "no", + }, + }); +} + +export const status = { + 1: "无法连接", + 0: "正常", + 2: "授权信息错误", + 3: "状态码错误", +}; + +// export const nodeMonitorCycle = { +// "-30": "30 秒", +// 1: "1 分钟", +// 5: "5 分钟", +// 10: "10 分钟", +// 30: "30 分钟", +// }; diff --git a/web-vue/src/api/node.js b/web-vue/src/api/node.js index 5366ce40f..e1390bc2f 100644 --- a/web-vue/src/api/node.js +++ b/web-vue/src/api/node.js @@ -281,12 +281,3 @@ export function downloadRemote() { data: {}, }); } - -export const nodeMonitorCycle = { - 0: "不开启", - "-30": "30 秒", - 1: "1 分钟", - 5: "5 分钟", - 10: "10 分钟", - 30: "30 分钟", -}; diff --git a/web-vue/src/components/codeEditor/index.vue b/web-vue/src/components/codeEditor/index.vue index e5318f39a..a9190e4ac 100644 --- a/web-vue/src/components/codeEditor/index.vue +++ b/web-vue/src/components/codeEditor/index.vue @@ -2,16 +2,36 @@
- 皮肤: - - {{ item }} - -
- 语言: - - {{ item }} - -
+ +
+ 皮肤: + + {{ item }} + +
+
+ 语言: + + {{ item }} + +
+ + + + + +
+
diff --git a/web-vue/src/components/lazy_antd.js b/web-vue/src/components/lazy_antd.js index 0dec3eb7b..35322da61 100644 --- a/web-vue/src/components/lazy_antd.js +++ b/web-vue/src/components/lazy_antd.js @@ -43,7 +43,7 @@ import { Select, // Slider, Spin, - // Statistic, + Statistic, Steps, Switch, Table, @@ -112,7 +112,7 @@ const components = [ Select, // Slider, Spin, - // Statistic, + Statistic, Steps, Switch, Table, diff --git a/web-vue/src/pages/node/list.vue b/web-vue/src/pages/node/list.vue index 34f0a2025..0ed99f6f4 100644 --- a/web-vue/src/pages/node/list.vue +++ b/web-vue/src/pages/node/list.vue @@ -124,11 +124,11 @@ {{ ssh.name }} - + + diff --git a/web-vue/src/pages/system/config.vue b/web-vue/src/pages/system/config.vue index e3065e01d..c33adc3b4 100644 --- a/web-vue/src/pages/system/config.vue +++ b/web-vue/src/pages/system/config.vue @@ -183,7 +183,7 @@ 菜单配置 - + diff --git a/web-vue/src/router/index.js b/web-vue/src/router/index.js index 7d9750684..35723f656 100644 --- a/web-vue/src/router/index.js +++ b/web-vue/src/router/index.js @@ -21,6 +21,11 @@ const children = [ name: "node-list", component: () => import("../pages/node/list"), }, + { + path: "/node/stat", + name: "node-stat", + component: () => import("../pages/node/stat"), + }, { path: "/node/search", name: "node-search", diff --git a/web-vue/src/router/route-menu.js b/web-vue/src/router/route-menu.js index fb66c840b..0e793e885 100644 --- a/web-vue/src/router/route-menu.js +++ b/web-vue/src/router/route-menu.js @@ -5,6 +5,7 @@ */ const routeMenuMap = { nodeList: "/node/list", + nodeStat: "/node/stat", sshList: "/ssh", commandList: "/ssh/command", commandLogList: "/ssh/command-log",