mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
新增监控日志
This commit is contained in:
@@ -9,39 +9,27 @@ import com.gitee.easyopen.util.CopyUtil;
|
||||
import com.gitee.fastmybatis.core.PageInfo;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.fastmybatis.core.query.Sort;
|
||||
import com.gitee.fastmybatis.core.query.expression.ValueExpression;
|
||||
import com.gitee.fastmybatis.core.util.MapperUtil;
|
||||
import com.gitee.fastmybatis.core.util.MyBeanUtil;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.LimitNewUpdateParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.LimitNewVO;
|
||||
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
|
||||
import com.gitee.sop.adminserver.common.LimitEnum;
|
||||
import com.gitee.sop.adminserver.entity.ConfigLimit;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigLimitMapper;
|
||||
import com.gitee.sop.adminserver.service.RouteConfigService;
|
||||
import com.gitee.sop.adminserver.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 限流
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-限流管理")
|
||||
@Slf4j
|
||||
public class LimitNewApi {
|
||||
|
||||
|
@@ -0,0 +1,169 @@
|
||||
package com.gitee.sop.adminserver.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.gitee.easyopen.annotation.Api;
|
||||
import com.gitee.easyopen.annotation.ApiService;
|
||||
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
||||
import com.gitee.easyopen.exception.ApiException;
|
||||
import com.gitee.easyopen.verify.DefaultMd5Verifier;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.adminserver.api.service.param.LogMonitorInstanceAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.LogMonitorInstanceVO;
|
||||
import com.gitee.sop.adminserver.common.QueryUtil;
|
||||
import com.gitee.sop.adminserver.entity.ConfigCommon;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigCommonMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理-日志监控")
|
||||
@Slf4j
|
||||
public class LogApi {
|
||||
|
||||
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance";
|
||||
public static final String CODE_SUCCESS = "10000";
|
||||
|
||||
@Autowired
|
||||
ConfigCommonMapper configCommonMapper;
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
private String secret;
|
||||
|
||||
@Api(name = "monitor.log.list")
|
||||
@ApiDocMethod(description = "获取监控日志")
|
||||
List<LogMonitorInstanceVO> listLog() {
|
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE);
|
||||
List<LogMonitorInstanceVO> ret = new ArrayList<>();
|
||||
int id = 1;
|
||||
for (ConfigCommon configCommon : configCommonList) {
|
||||
int pid = id++;
|
||||
String ipPort = configCommon.getConfigKey();
|
||||
// 父节点
|
||||
LogMonitorInstanceVO logMonitorInstanceVOParent = new LogMonitorInstanceVO();
|
||||
logMonitorInstanceVOParent.setRawId(configCommon.getId());
|
||||
logMonitorInstanceVOParent.setTreeId(pid);
|
||||
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent());
|
||||
ret.add(logMonitorInstanceVOParent);
|
||||
try {
|
||||
String logData = this.requestLogServer(ipPort, "listErrors");
|
||||
JSONObject jsonObject = JSON.parseObject(logData);
|
||||
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||
int errorTotal = 0;
|
||||
List<LogMonitorInstanceVO> data = JSON.parseArray(jsonObject.getString("data"), LogMonitorInstanceVO.class);
|
||||
for (LogMonitorInstanceVO instanceVO : data) {
|
||||
instanceVO.setTreeId(id++);
|
||||
instanceVO.setParentId(pid);
|
||||
errorTotal += instanceVO.getCount();
|
||||
}
|
||||
ret.addAll(data);
|
||||
logMonitorInstanceVOParent.setCount(errorTotal);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取日志信息出错", e);
|
||||
throw new ApiException("获取日志信息出错");
|
||||
}
|
||||
}
|
||||
Collections.sort(ret, Comparator.comparing(LogMonitorInstanceVO::getCount));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Api(name = "monitor.log.clear")
|
||||
@ApiDocMethod(description = "清空日志")
|
||||
void clearLog(@NotNull(message = "id不能为空") Long id) {
|
||||
ConfigCommon configCommon = configCommonMapper.getById(id);
|
||||
if (configCommon == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String ipPort = configCommon.getConfigKey();
|
||||
this.requestLogServer(ipPort, "clearErrors");
|
||||
} catch (Exception e) {
|
||||
throw new ApiException("清除失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.list")
|
||||
@ApiDocMethod(description = "获取已添加的监控实例")
|
||||
List<String> listServiceInstance() {
|
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE);
|
||||
return configCommonList.stream()
|
||||
.map(ConfigCommon::getConfigKey)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.add")
|
||||
@ApiDocMethod(description = "添加监控实例")
|
||||
void addServiceInstance(LogMonitorInstanceAddParam param) {
|
||||
String ipPort = param.getIp() + ":" + param.getPort();
|
||||
this.checkInstance(ipPort);
|
||||
|
||||
Query query = new Query();
|
||||
query.eq("config_group", LOG_MONITOR_INSTANCE)
|
||||
.eq("config_key", ipPort);
|
||||
ConfigCommon rec = configCommonMapper.getByQuery(query);
|
||||
if (rec != null) {
|
||||
throw new ApiException("该实例已添加");
|
||||
}
|
||||
ConfigCommon configCommon = new ConfigCommon();
|
||||
configCommon.setConfigGroup(LOG_MONITOR_INSTANCE);
|
||||
configCommon.setConfigKey(ipPort);
|
||||
configCommon.setContent(param.getServiceId() + "(" + ipPort + ")");
|
||||
configCommonMapper.saveIgnoreNull(configCommon);
|
||||
}
|
||||
|
||||
private void checkInstance(String ipPort) {
|
||||
try {
|
||||
String json = this.requestLogServer(ipPort, "listErrors");
|
||||
JSONObject jsonObject = JSON.parseObject(json);
|
||||
if (!CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||
log.error("请求结果:{}", json);
|
||||
throw new ApiException("添加失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("添加失败", e);
|
||||
throw new ApiException("添加失败");
|
||||
}
|
||||
}
|
||||
|
||||
private String requestLogServer(String ipPort, String path) throws Exception {
|
||||
DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("time", System.currentTimeMillis());
|
||||
String sign = md5Verifier.buildSign(params, secret);
|
||||
params.put("sign", sign);
|
||||
String query = QueryUtil.buildQueryString(params);
|
||||
String url = "http://" + ipPort + "/" + path + "?" + query;
|
||||
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
|
||||
if (entity.getStatusCode() != HttpStatus.OK) {
|
||||
throw new IllegalAccessException("无权访问");
|
||||
}
|
||||
return entity.getBody();
|
||||
}
|
||||
|
||||
@Api(name = "monitor.instance.del")
|
||||
@ApiDocMethod(description = "删除监控实例")
|
||||
void delServiceInstance(@NotNull(message = "id不能为空") Long id) {
|
||||
configCommonMapper.deleteById(id);
|
||||
}
|
||||
|
||||
}
|
@@ -43,7 +43,7 @@ import java.util.stream.Collectors;
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-路由管理")
|
||||
@Slf4j
|
||||
public class RouteApi {
|
||||
|
||||
|
@@ -36,7 +36,7 @@ import java.util.stream.Collectors;
|
||||
* @author tanghc
|
||||
*/
|
||||
@ApiService
|
||||
@ApiDoc("服务管理")
|
||||
@ApiDoc("服务管理-服务列表")
|
||||
@Slf4j
|
||||
public class ServiceApi {
|
||||
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LogMonitorInstanceAddParam {
|
||||
@NotBlank(message = "serviceId不能为空")
|
||||
private String serviceId;
|
||||
|
||||
@NotBlank(message = "ip不能为空")
|
||||
private String ip;
|
||||
|
||||
private int port;
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.gitee.sop.adminserver.api.service.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class LogMonitorInstanceVO {
|
||||
private String id;
|
||||
private int treeId;
|
||||
// 表主键
|
||||
private long rawId;
|
||||
private String name;
|
||||
private String version;
|
||||
private String serviceId;
|
||||
private String errorMsg;
|
||||
private long count;
|
||||
|
||||
private int parentId;
|
||||
private String monitorName;
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.gitee.sop.adminserver.common;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class QueryUtil {
|
||||
|
||||
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException {
|
||||
if (params == null || params.size() == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder query = new StringBuilder();
|
||||
int i = 0;
|
||||
for (Map.Entry<String, ?> entry : params.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
String value = String.valueOf(entry.getValue());
|
||||
if (i++ > 0) {
|
||||
query.append("&");
|
||||
}
|
||||
query.append(name).append("=").append(URLEncoder.encode(value, "UTF-8"));
|
||||
}
|
||||
return query.toString();
|
||||
}
|
||||
}
|
@@ -32,9 +32,10 @@ public class WebConfig {
|
||||
@Bean
|
||||
ApiConfig apiConfig() {
|
||||
ApiConfig apiConfig = new ApiConfig();
|
||||
apiConfig.setJsonResultSerializer(obj -> {
|
||||
return JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
|
||||
});
|
||||
apiConfig.setJsonResultSerializer(obj -> JSON.toJSONString(obj,
|
||||
SerializerFeature.WriteDateUseDateFormat
|
||||
, SerializerFeature.WriteNullStringAsEmpty
|
||||
, SerializerFeature.WriteMapNullValue));
|
||||
ApiSessionManager apiSessionManager = new ApiSessionManager();
|
||||
// session有效期
|
||||
int timeout = NumberUtils.toInt(accessTokenTimeout, 30);
|
||||
|
@@ -0,0 +1,45 @@
|
||||
package com.gitee.sop.adminserver.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_common
|
||||
* 备注:通用配置表
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_common")
|
||||
@Data
|
||||
public class ConfigCommon {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 配置分组, 数据库字段:config_group */
|
||||
private String configGroup;
|
||||
|
||||
/** 配置key, 数据库字段:config_key */
|
||||
private String configKey;
|
||||
|
||||
/** 内容, 数据库字段:content */
|
||||
private String content;
|
||||
|
||||
/** 备注, 数据库字段:remark */
|
||||
private String remark;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.adminserver.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
|
||||
import com.gitee.sop.adminserver.entity.ConfigCommon;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigCommonMapper extends CrudMapper<ConfigCommon, Long> {
|
||||
}
|
@@ -48,3 +48,7 @@ logging:
|
||||
mybatis:
|
||||
fill: {com.gitee.fastmybatis.core.support.DateFillInsert: gmt_create,
|
||||
com.gitee.fastmybatis.core.support.DateFillUpdate: gmt_modified}
|
||||
|
||||
# 不用改,如果要改,建议全局替换修改
|
||||
zuul:
|
||||
secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,c=t[0],i=t[1],f=t[2],l=0,s=[];l<c.length;l++)o=c[l],u[o]&&s.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);d&&d(t);while(s.length)s.shift()();return a.push.apply(a,f||[]),n()}function n(){for(var e,t=0;t<a.length;t++){for(var n=a[t],r=!0,o=1;o<n.length;o++){var c=n[o];0!==u[c]&&(r=!1)}r&&(a.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},a=[];function c(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"5ba26eff","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"0cbb7297","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,a=document.getElementsByTagName("link"),c=0;c<a.length;c++){var f=a[c],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var s=document.getElementsByTagName("style");for(c=0;c<s.length;c++){f=s[c],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=t,d.onerror=function(t){var r=t&&t.target&&t.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=r,delete o[e],d.parentNode.removeChild(d),n(a)},d.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(d)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var a=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=a);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=c(e),f=function(t){l.onerror=l.onload=null,clearTimeout(s);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,a=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");a.type=r,a.request=o,n[1](a)}u[e]=void 0}};var s=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var s=0;s<f.length;s++)t(f[s]);var d=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.bc31495f.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,a=t[0],i=t[1],f=t[2],l=0,d=[];l<a.length;l++)o=a[l],u[o]&&d.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);s&&s(t);while(d.length)d.shift()();return c.push.apply(c,f||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,o=1;o<n.length;o++){var a=n[o];0!==u[a]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},c=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"447435b3","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"46f37153","chunk-4a59cbe4":"3309de97","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-4a59cbe4":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,c=document.getElementsByTagName("link"),a=0;a<c.length;a++){var f=c[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){f=d[a],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||u,c=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");c.code="CSS_CHUNK_LOAD_FAILED",c.request=r,delete o[e],s.parentNode.removeChild(s),n(c)},s.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(s)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var c=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=c);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,c=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");c.type=r,c.request=o,n[1](c)}u[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var s=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.36a3cb86.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.child;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.children;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]);
|
File diff suppressed because one or more lines are too long
@@ -78,6 +78,12 @@ export const constantRoutes = [
|
||||
name: 'Limit',
|
||||
component: () => import('@/views/service/limit/index2'),
|
||||
meta: { title: '限流管理' }
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'Log',
|
||||
component: () => import('@/views/service/log/index'),
|
||||
meta: { title: '监控日志' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -35,7 +35,7 @@ Object.assign(Vue.prototype, {
|
||||
const resp = response.data
|
||||
const code = resp.code
|
||||
if (!code || code === '-9') {
|
||||
that.$message.error('系统错误')
|
||||
that.$message.error(resp.msg || '系统错误')
|
||||
return
|
||||
}
|
||||
if (code === '-100' || code === '18' || code === '21') { // 未登录
|
||||
@@ -96,6 +96,9 @@ Object.assign(Vue.prototype, {
|
||||
},
|
||||
logout: function() {
|
||||
removeToken()
|
||||
this.$router.push({ path: `/login?redirect=${this.$route.fullPath}` })
|
||||
const fullPath = this.$route.fullPath
|
||||
if (fullPath.indexOf('login?redirect') === -1) {
|
||||
this.$router.push({ path: `/login?redirect=${fullPath}` })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -92,7 +92,7 @@ export default {
|
||||
// 如果ele是子元素的话 ,把ele扔到他的父亲的child数组中.
|
||||
data.forEach(d => {
|
||||
if (d.id === parentId) {
|
||||
let childArray = d.child
|
||||
let childArray = d.children
|
||||
if (!childArray) {
|
||||
childArray = []
|
||||
}
|
||||
|
234
sop-admin/sop-admin-vue/src/views/service/log/index.vue
Normal file
234
sop-admin/sop-admin-vue/src/views/service/log/index.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="onAddServer">添加监控服务器</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%;margin-bottom: 20px;"
|
||||
border
|
||||
row-key="treeId"
|
||||
empty-text="请添加监控服务器"
|
||||
>
|
||||
<el-table-column
|
||||
prop="monitorName"
|
||||
label="网关实例"
|
||||
width="300"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentId === 0">{{ scope.row.monitorName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="serviceId"
|
||||
label="serviceId"
|
||||
width="200"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentId > 0">{{ scope.row.serviceId }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="接口名 (版本号)"
|
||||
width="200"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="count"
|
||||
label="出错次数"
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="errorMsg"
|
||||
label="报错信息"
|
||||
width="300"
|
||||
>
|
||||
<template v-if="scope.row.parentId > 0" slot-scope="scope">
|
||||
<div style="display: inline-block;" v-html="showErrorMsg(scope.row)"></div> <el-button type="text" size="mini" @click="onShowErrorDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="180"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row.parentId === 0 && scope.row.children" type="text" size="mini" @click="onClearLog(scope.row)">清空日志</el-button>
|
||||
<el-button v-if="scope.row.parentId === 0" type="text" size="mini" @click="onDelete(scope.row)">删除实例</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- dialog -->
|
||||
<el-dialog
|
||||
title="选择服务器实例"
|
||||
:visible.sync="logDialogInstanceVisible"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="logDialogForm"
|
||||
:model="logDialogFormData"
|
||||
:rules="rulesLog"
|
||||
label-width="150px"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item>
|
||||
<p style="color: #878787;">只能选择网关实例,其它实例不支持</p>
|
||||
</el-form-item>
|
||||
<el-form-item prop="instanceData" label="服务器实例">
|
||||
<el-select v-model="logDialogFormData.instanceData" value-key="id" style="width: 400px;">
|
||||
<el-option
|
||||
v-for="item in serviceData"
|
||||
:key="item.id"
|
||||
:label="item.name + '(' + item.ipAddr + ':' + item.serverPort + ')'"
|
||||
:value="item"
|
||||
:disabled="isOptionDisabled(item)"
|
||||
>
|
||||
<span style="float: left">{{ item.name }} <span v-if="isOptionDisabled(item)">(已添加)</span></span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.ipAddr + ':' + item.serverPort }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="logDialogInstanceVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onLogDialogSave">保 存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="错误详情"
|
||||
:visible.sync="logDetailVisible"
|
||||
width="60%"
|
||||
>
|
||||
<div style="overflow-x: auto" v-html="errorMsgDetail"></div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="logDetailVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchFormData: {},
|
||||
tableData: [],
|
||||
serviceData: [],
|
||||
// 已经添加的实例
|
||||
addedInstanceList: [],
|
||||
logDialogFormData: {
|
||||
instanceData: null
|
||||
},
|
||||
logDialogInstanceVisible: false,
|
||||
logDetailVisible: false,
|
||||
rulesLog: {
|
||||
instanceData: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
errorMsgDetail: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadServiceInstance()
|
||||
this.loadTable()
|
||||
},
|
||||
methods: {
|
||||
loadServiceInstance: function() {
|
||||
this.post('service.instance.list', {}, function(resp) {
|
||||
this.serviceData = resp.data.filter(el => {
|
||||
return el.instanceId && el.instanceId.length > 0
|
||||
})
|
||||
})
|
||||
this.post('monitor.instance.list', {}, function(resp) {
|
||||
this.addedInstanceList = resp.data
|
||||
})
|
||||
},
|
||||
loadTable: function() {
|
||||
this.post('monitor.log.list', {}, function(resp) {
|
||||
this.tableData = this.buildTreeData(resp.data)
|
||||
})
|
||||
},
|
||||
isOptionDisabled: function(item) {
|
||||
const ipPort = item.ipAddr + ':' + item.serverPort
|
||||
const index = this.addedInstanceList.findIndex((value, index, arr) => {
|
||||
return value === ipPort
|
||||
})
|
||||
return index > -1
|
||||
},
|
||||
buildTreeData: function(data) {
|
||||
data.forEach(ele => {
|
||||
const parentId = ele.parentId
|
||||
if (parentId === 0) {
|
||||
// 是根元素 ,不做任何操作,如果是正常的for-i循环,可以直接continue.
|
||||
} else {
|
||||
// 如果ele是子元素的话 ,把ele扔到他的父亲的child数组中.
|
||||
data.forEach(d => {
|
||||
if (d.treeId === parentId) {
|
||||
let childArray = d.children
|
||||
if (!childArray) {
|
||||
childArray = []
|
||||
}
|
||||
childArray.push(ele)
|
||||
d.children = childArray
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// 去除重复元素
|
||||
data = data.filter(ele => ele.parentId === 0)
|
||||
return data
|
||||
},
|
||||
showErrorMsg: function(row) {
|
||||
const msg = row.errorMsg.replace(/\<br\>/g, '')
|
||||
return msg.substring(0, 30) + '...'
|
||||
},
|
||||
onAddServer: function() {
|
||||
this.logDialogInstanceVisible = true
|
||||
},
|
||||
onDelete: function(row) {
|
||||
this.confirm('确定要删除实例【' + row.monitorName + '】吗?', function(done) {
|
||||
this.post('monitor.instance.del', { id: row.rawId }, function(resp) {
|
||||
done()
|
||||
this.tip('删除成功')
|
||||
this.loadTable()
|
||||
})
|
||||
})
|
||||
},
|
||||
onClearLog: function(row) {
|
||||
this.confirm('确定要清空日志吗?', function(done) {
|
||||
this.post('monitor.log.clear', { id: row.rawId }, function(resp) {
|
||||
done()
|
||||
this.tip('清空成功')
|
||||
this.loadTable()
|
||||
})
|
||||
})
|
||||
},
|
||||
onShowErrorDetail: function(row) {
|
||||
this.errorMsgDetail = row.errorMsg
|
||||
this.logDetailVisible = true
|
||||
},
|
||||
onLogDialogSave: function() {
|
||||
this.$refs['logDialogForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const instanceData = this.logDialogFormData.instanceData
|
||||
const data = {
|
||||
serviceId: instanceData.serviceId,
|
||||
ip: instanceData.ipAddr,
|
||||
port: instanceData.serverPort
|
||||
}
|
||||
this.post('monitor.instance.add', data, function(resp) {
|
||||
this.logDialogInstanceVisible = false
|
||||
this.loadTable()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -79,12 +79,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="mergeResult"
|
||||
label="合并结果"
|
||||
width="80"
|
||||
label="统一格式输出"
|
||||
width="120"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.mergeResult === 1" style="color:#67C23A">合并</span>
|
||||
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C">不合并</span>
|
||||
<span v-if="scope.row.mergeResult === 1" style="color:#67C23A">是</span>
|
||||
<span v-if="scope.row.mergeResult === 0" style="color:#E6A23C">否</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
Reference in New Issue
Block a user