mirror of
https://gitee.com/dromara/Jpom.git
synced 2024-11-30 19:08:18 +08:00
feat: node stat
This commit is contained in:
parent
e78c655633
commit
04da92db0a
@ -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】取消节点监控周期字段(采用全局统计)
|
||||
|
||||
------
|
||||
|
||||
|
@ -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<String> strings = StrUtil.splitTrim(info, StrUtil.COLON);
|
||||
String last = CollUtil.getLast(strings);
|
||||
List<String> 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
|
||||
|
@ -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<InetAddress> localAddressList = NetUtil.localAddressList(address -> {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ public enum ClassFeature {
|
||||
*/
|
||||
NULL(""),
|
||||
NODE("节点管理"),
|
||||
NODE_STAT("节点统计"),
|
||||
UPGRADE_NODE_LIST("节点升级"),
|
||||
SEARCH_PROJECT("搜索项目"),
|
||||
SSH("SSH管理"),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<NodeStatModel> 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<Entity> list = nodeStatService.query(sql, workspaceId);
|
||||
Map<String, Integer> map = CollStreamUtil.toMap(list, entity -> entity.getStr("status"), entity -> entity.getInt("cunt"));
|
||||
return JsonMessage.getString(200, "", map);
|
||||
}
|
||||
}
|
@ -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"));
|
||||
//
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<NodeModel> 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<NodeModel> nodeModels = nodeService.listDeDuplicationByUrl();
|
||||
//
|
||||
this.checkList(nodeModels);
|
||||
}
|
||||
|
||||
private void checkList(List<NodeModel> 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<NodeModel> getListByUrl(String url) {
|
||||
NodeModel nodeModel = new NodeModel();
|
||||
nodeModel.setUrl(url);
|
||||
return nodeService.listByBean(nodeModel);
|
||||
}
|
||||
|
||||
private void getNodeInfo(NodeModel nodeModel) {
|
||||
JsonMessage<JSONObject> message = NodeForward.request(nodeModel, null, NodeUrl.GetDirectTop);
|
||||
JSONObject jsonObject = message.getData();
|
||||
if (jsonObject == null) {
|
||||
private void checkList(List<NodeModel> nodeModels) {
|
||||
if (CollUtil.isEmpty(nodeModels)) {
|
||||
return;
|
||||
}
|
||||
double disk = jsonObject.getDoubleValue("disk");
|
||||
if (disk <= 0) {
|
||||
return;
|
||||
nodeModels.forEach(nodeModel -> {
|
||||
//
|
||||
nodeModel.setName(nodeModel.getUrl());
|
||||
List<NodeModel> 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<Object> 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<NodeModel> modelList, JSONObject systemMonitor) {
|
||||
if (systemMonitor != null) {
|
||||
List<SystemMonitorLog> 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<NodeModel> 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<NodeModel> 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<JSONObject> message = NodeForward.request(reqNode, null, NodeUrl.GetDirectTop);
|
||||
return message.getData();
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ public abstract class BaseDbCommonService<T> {
|
||||
* @param <R> 乏型
|
||||
* @return data
|
||||
*/
|
||||
private <R> R entityToBean(Entity entity, Class<R> rClass) {
|
||||
protected <R> R entityToBean(Entity entity, Class<R> rClass) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -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<NodeModel> implements ICron {
|
||||
public class NodeService extends BaseGroupService<NodeModel> {
|
||||
|
||||
private final SshService sshService;
|
||||
private final WorkspaceService workspaceService;
|
||||
@ -203,10 +202,7 @@ public class NodeService extends BaseGroupService<NodeModel> 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<NodeModel> 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<NodeModel> 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<NodeModel> listByCycle(Cycle cycle) {
|
||||
NodeModel nodeModel = new NodeModel();
|
||||
nodeModel.setCycle(cycle.getCode());
|
||||
nodeModel.setOpenStatus(1);
|
||||
List<NodeModel> list = this.listByBean(nodeModel);
|
||||
if (list == null) {
|
||||
return new ArrayList<>();
|
||||
public List<NodeModel> listDeDuplicationByUrl() {
|
||||
String sql = "select url,max(loginName) as loginName,max(loginPwd) as loginPwd,max(protocol) as protocol from " + super.getTableName() + " group by url";
|
||||
List<Entity> 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<NodeModel> getNodeBySshId(String sshId) {
|
||||
|
@ -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<NodeStatModel> {
|
||||
}
|
@ -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();
|
||||
});
|
||||
|
@ -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": [
|
||||
{
|
||||
|
@ -61,7 +61,6 @@
|
||||
"id": "systemConfig",
|
||||
"title": "系统管理",
|
||||
"icon_v3": "setting",
|
||||
"role": "sys",
|
||||
"childs": [
|
||||
{
|
||||
"id": "whitelistDirectory",
|
||||
|
@ -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 '节点统计';
|
||||
|
||||
|
||||
|
||||
|
39
web-vue/src/api/node-stat.js
Normal file
39
web-vue/src/api/node-stat.js
Normal file
@ -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 分钟",
|
||||
// };
|
@ -281,12 +281,3 @@ export function downloadRemote() {
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
|
||||
export const nodeMonitorCycle = {
|
||||
0: "不开启",
|
||||
"-30": "30 秒",
|
||||
1: "1 分钟",
|
||||
5: "5 分钟",
|
||||
10: "10 分钟",
|
||||
30: "30 分钟",
|
||||
};
|
||||
|
@ -2,16 +2,36 @@
|
||||
<div class="code-mirror-div">
|
||||
<div class="tool-bar" ref="toolBar" v-if="showTool">
|
||||
<slot name="tool_before" />
|
||||
皮肤:
|
||||
<a-select v-model="cmOptions.theme" @select="handleSelectTheme" show-search option-filter-prop="children" :filter-option="filterOption" placeholder="请选择" style="width: 150px">
|
||||
<a-select-option v-for="item in cmThemeOptions" :key="item">{{ item }}</a-select-option>
|
||||
</a-select>
|
||||
<div style="margin-left: 30px">
|
||||
语言:
|
||||
<a-select v-model="cmOptions.mode" @select="handleSelectMode" show-search option-filter-prop="children" :filter-option="filterOption" placeholder="请选择" style="width: 150px">
|
||||
<a-select-option v-for="item in cmEditorModeOptions" :key="item">{{ item }}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-space>
|
||||
<div>
|
||||
皮肤:
|
||||
<a-select v-model="cmOptions.theme" @select="handleSelectTheme" show-search option-filter-prop="children" :filter-option="filterOption" placeholder="请选择" style="width: 150px">
|
||||
<a-select-option v-for="item in cmThemeOptions" :key="item">{{ item }}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div>
|
||||
语言:
|
||||
<a-select v-model="cmOptions.mode" @select="handleSelectMode" show-search option-filter-prop="children" :filter-option="filterOption" placeholder="请选择" style="width: 150px">
|
||||
<a-select-option v-for="item in cmEditorModeOptions" :key="item">{{ item }}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<ul>
|
||||
<li>Ctrl-F / Cmd-F Start searching</li>
|
||||
<li>Ctrl-G / Cmd-G Find next</li>
|
||||
<li>Shift-Ctrl-G / Shift-Cmd-G Find previous</li>
|
||||
<li>Shift-Ctrl-F / Cmd-Option-F Replace</li>
|
||||
<li>Shift-Ctrl-R / Shift-Cmd-Option-F Replace all</li>
|
||||
<li>Alt-F Persistent search (dialog doesn't autoclose, enter to find next, Shift-Enter to find previous)</li>
|
||||
<li>Alt-G Jump to line</li>
|
||||
</ul>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
|
||||
<slot name="tool_after" />
|
||||
</div>
|
||||
<div :style="{ height: codeMirrorHeight }">
|
||||
|
@ -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,
|
||||
|
@ -124,11 +124,11 @@
|
||||
<a-select-option v-for="ssh in sshList" :key="ssh.id" :disabled="ssh.disabled">{{ ssh.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="监控周期" prop="cycle">
|
||||
<!-- <a-form-model-item label="监控周期" prop="cycle">
|
||||
<a-select v-model="temp.cycle" defaultValue="0" placeholder="监控周期">
|
||||
<a-select-option v-for="(name, key) in nodeMonitorCycle" :key="parseInt(key)">{{ name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
</a-form-model-item> -->
|
||||
|
||||
<a-form-model-item label="节点状态" prop="openStatus">
|
||||
<a-switch
|
||||
@ -326,7 +326,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getNodeList, getNodeStatus, editNode, deleteNode, syncProject, unLockWorkspace, nodeMonitorCycle, getNodeGroupAll, fastInstall, pullFastInstallResult, confirmFastInstall } from "@/api/node";
|
||||
import { getNodeList, getNodeStatus, editNode, deleteNode, syncProject, unLockWorkspace, getNodeGroupAll, fastInstall, pullFastInstallResult, confirmFastInstall } from "@/api/node";
|
||||
import { getSshListAll } from "@/api/ssh";
|
||||
import { syncScript } from "@/api/node-other";
|
||||
import NodeLayout from "./node-layout";
|
||||
@ -348,7 +348,7 @@ export default {
|
||||
loading: false,
|
||||
childLoading: false,
|
||||
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
nodeMonitorCycle: nodeMonitorCycle,
|
||||
// nodeMonitorCycle: nodeMonitorCycle,
|
||||
sshList: [],
|
||||
list: [],
|
||||
groupList: [],
|
||||
@ -374,7 +374,7 @@ export default {
|
||||
{ title: "节点协议", dataIndex: "protocol", sorter: true, key: "protocol", width: 100, ellipsis: true, scopedSlots: { customRender: "protocol" } },
|
||||
{ title: "节点地址", dataIndex: "url", sorter: true, key: "url", ellipsis: true, scopedSlots: { customRender: "url" } },
|
||||
{ title: "账号", dataIndex: "loginName", sorter: true, key: "loginName", ellipsis: true, scopedSlots: { customRender: "loginName" } },
|
||||
{ title: "监控周期", dataIndex: "cycle", sorter: true, key: "cycle", ellipsis: true, scopedSlots: { customRender: "cycle" } },
|
||||
// { title: "监控周期", dataIndex: "cycle", sorter: true, key: "cycle", ellipsis: true, scopedSlots: { customRender: "cycle" } },
|
||||
{ title: "超时时间", dataIndex: "timeOut", sorter: true, key: "timeOut", width: 100, ellipsis: true },
|
||||
{
|
||||
title: "修改时间",
|
||||
|
168
web-vue/src/pages/node/stat.vue
Normal file
168
web-vue/src/pages/node/stat.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="full-content">
|
||||
<a-space direction="vertical">
|
||||
<a-card>
|
||||
<!-- <template slot="title">
|
||||
<a-row>
|
||||
<a-col :span="2">状态概况</a-col>
|
||||
</a-row>
|
||||
</template> -->
|
||||
<a-row>
|
||||
<a-col :span="4">
|
||||
<a-statistic title="节点总数" :value="nodeCount"> </a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="4" v-for="(desc, key) in statusMap" :key="key">
|
||||
<a-statistic :title="desc" :value="statusStatMap[key]"> </a-statistic>
|
||||
</a-col>
|
||||
<a-col :span="2"> <a-statistic-countdown format="s 秒" title="刷新倒计时" :value="deadline" @finish="onFinish" /> </a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<div ref="filter" class="filter">
|
||||
<a-space>
|
||||
<a-input v-model="listQuery['%name%']" placeholder="节点名称" />
|
||||
<a-input v-model="listQuery['%url%']" placeholder="节点地址" />
|
||||
<a-select v-model="listQuery.status" allowClear placeholder="请选择状态" class="search-input-item">
|
||||
<a-select-option v-for="(desc, key) in statusMap" :key="key">{{ desc }}</a-select-option>
|
||||
</a-select>
|
||||
<a-tooltip title="按住 Ctr 或者 Alt 键点击按钮快速回到第一页">
|
||||
<a-button :loading="loading" type="primary" @click="loadData">搜索</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 表格 :scroll="{ x: 1070, y: tableHeight -60 }" scroll 跟 expandedRowRender 不兼容,没法同时使用不然会多出一行数据-->
|
||||
<a-table :columns="columns" :data-source="list" bordered rowKey="id" :pagination="(this, pagination)" @change="changePage">
|
||||
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
|
||||
<span>{{ text }}</span>
|
||||
</a-tooltip>
|
||||
<template slot="status" slot-scope="text, record">
|
||||
<a-tooltip v-if="text !== 0" placement="topLeft" :title="record.failureMsg">
|
||||
<span>{{ statusMap[text] }}</span>
|
||||
</a-tooltip>
|
||||
<span v-else>{{ statusMap[text] }}</span>
|
||||
</template>
|
||||
|
||||
<template slot="progress" slot-scope="text">
|
||||
<a-tooltip placement="topLeft" :title="`${text}%`">
|
||||
<a-progress
|
||||
:percent="text"
|
||||
:stroke-color="{
|
||||
from: '#87d068',
|
||||
to: '#108ee9',
|
||||
}"
|
||||
size="small"
|
||||
status="active"
|
||||
:showInfo="false"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getStatist, status, statusStat } from "@/api/node-stat";
|
||||
import { parseTime } from "@/utils/time";
|
||||
import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL, PAGE_DEFAULT_LIST_QUERY } from "@/utils/const";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
statusMap: status,
|
||||
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
|
||||
list: [],
|
||||
statusStatMap: {},
|
||||
nodeCount: 0,
|
||||
// nodeMonitorCycle: nodeMonitorCycle,
|
||||
deadline: 0,
|
||||
temp: {},
|
||||
columns: [
|
||||
{ title: "节点名称", dataIndex: "name", sorter: true, key: "name", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "节点地址", dataIndex: "url", sorter: true, key: "url", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "cpu", dataIndex: "occupyCpu", sorter: true, key: "occupyCpu", ellipsis: true, scopedSlots: { customRender: "progress" } },
|
||||
{ title: "disk", dataIndex: "occupyDisk", sorter: true, key: "occupyDisk", ellipsis: true, scopedSlots: { customRender: "progress" } },
|
||||
{ title: "memory", dataIndex: "occupyMemory", sorter: true, key: "occupyMemory", ellipsis: true, scopedSlots: { customRender: "progress" } },
|
||||
{ title: "memoryUsed", dataIndex: "occupyMemoryUsed", sorter: true, key: "occupyMemoryUsed", ellipsis: true, scopedSlots: { customRender: "progress" } },
|
||||
{ title: "运行时间", dataIndex: "upTimeStr", sorter: true, key: "upTimeStr", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
|
||||
{ title: "状态", dataIndex: "status", sorter: true, key: "status", ellipsis: true, scopedSlots: { customRender: "status" } },
|
||||
{
|
||||
title: "更新时间",
|
||||
dataIndex: "modifyTimeMillis",
|
||||
ellipsis: true,
|
||||
sorter: true,
|
||||
customRender: (text) => {
|
||||
return parseTime(text);
|
||||
},
|
||||
width: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pagination() {
|
||||
return {
|
||||
total: this.listQuery.total || 0,
|
||||
current: this.listQuery.page || 1,
|
||||
pageSize: this.listQuery.limit || PAGE_DEFAULT_LIMIT,
|
||||
pageSizeOptions: PAGE_DEFAULT_SIZW_OPTIONS,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => {
|
||||
return PAGE_DEFAULT_SHOW_TOTAL(total, this.listQuery);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
this.loadData();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.pullFastInstallResultTime) {
|
||||
clearInterval(this.pullFastInstallResultTime);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData(pointerEvent) {
|
||||
this.list = [];
|
||||
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page;
|
||||
this.loading = true;
|
||||
getStatist(this.listQuery).then((res) => {
|
||||
if (res.code === 200) {
|
||||
this.list = res.data.result;
|
||||
this.listQuery.total = res.data.total;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
statusStat().then((res) => {
|
||||
if (res.data) {
|
||||
this.statusStatMap = res.data;
|
||||
let nodeCount2 = 0;
|
||||
// console.log(this.statusStatMap);
|
||||
Object.values(this.statusStatMap).forEach((element) => {
|
||||
nodeCount2 += element;
|
||||
});
|
||||
this.nodeCount = nodeCount2;
|
||||
this.deadline = Date.now() + 30 * 1000;
|
||||
}
|
||||
});
|
||||
},
|
||||
// 分页、排序、筛选变化时触发
|
||||
changePage(pagination, filters, sorter) {
|
||||
this.listQuery.page = pagination.current;
|
||||
this.listQuery.limit = pagination.pageSize;
|
||||
if (sorter) {
|
||||
this.listQuery.order = sorter.order;
|
||||
this.listQuery.order_field = sorter.field;
|
||||
}
|
||||
this.loadData();
|
||||
},
|
||||
onFinish() {
|
||||
this.loadData();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped></style>
|
@ -183,7 +183,7 @@
|
||||
<a-icon type="menu" />
|
||||
菜单配置
|
||||
</span>
|
||||
<a-alert :message="`菜单配置只对非超级管理员生效`" style="margin-top: 10px; margin-bottom: 20px" banner />
|
||||
<a-alert :message="`菜单配置只对非超级管理员生效,当前配置对当前工作空间生效,其他工作空间请切换后配置`" style="margin-top: 10px; margin-bottom: 20px" banner />
|
||||
<a-form-model ref="editWhiteForm" :model="menusConfigData">
|
||||
<a-row type="flex" justify="center">
|
||||
<a-col :span="12">
|
||||
|
@ -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",
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
const routeMenuMap = {
|
||||
nodeList: "/node/list",
|
||||
nodeStat: "/node/stat",
|
||||
sshList: "/ssh",
|
||||
commandList: "/ssh/command",
|
||||
commandLogList: "/ssh/command-log",
|
||||
|
Loading…
Reference in New Issue
Block a user