This commit is contained in:
fit2-zhao 2020-12-04 18:06:27 +08:00
commit 8d7ec9b8ed
33 changed files with 989 additions and 44 deletions

View File

@ -0,0 +1,55 @@
package io.metersphere.api.controller;
import io.metersphere.api.dto.ApiMonitorSearch;
import io.metersphere.api.dto.ApiResponseCodeMonitor;
import io.metersphere.api.dto.ApiResponseTimeMonitor;
import io.metersphere.api.service.APIMonitorService;
import io.metersphere.commons.constants.RoleConstants;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping(value = "/api/monitor")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public class ApiMonitorController {
@Resource
private APIMonitorService apiMonitorService;
/**
* 查询所有接口
*/
@GetMapping("/list")
public List<ApiMonitorSearch> apiList() {
return apiMonitorService.list();
}
/**
* 查询响应时间
*/
@GetMapping("/getResponseTime")
public List<ApiResponseTimeMonitor> responseTimeData(@RequestHeader("apiUrl") String url, String startTime, String endTime) {
return apiMonitorService.getApiResponseTimeData(url, startTime, endTime);
}
/**
* 查询状态码
*/
@GetMapping("/getResponseCode")
public List<ApiResponseCodeMonitor> responseCodeData(@RequestHeader("apiUrl") String url, String startTime, String endTime) {
return apiMonitorService.getApiResponseCodeData(url, startTime, endTime);
}
/**
* 查询reportId
*/
@GetMapping("/getReportId")
public String searchReportId(@RequestHeader("apiUrl") String url, @RequestParam("startTime") String startTime) {
return apiMonitorService.getReportId(url, startTime);
}
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto;
import lombok.Data;
@Data
public class ApiMonitorSearch {
private String url;
}

View File

@ -0,0 +1,20 @@
package io.metersphere.api.dto;
import lombok.Data;
@Data
public class ApiResponseCodeMonitor {
private String id;
private String reportId;
private String url;
private String apiName;
private String startTime;
private String responseCode;
}

View File

@ -0,0 +1,20 @@
package io.metersphere.api.dto;
import lombok.Data;
@Data
public class ApiResponseTimeMonitor {
private String id;
private String reportId;
private String url;
private String apiName;
private String startTime;
private String responseTime;
}

View File

@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.parse;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import lombok.Data;
import java.util.HashMap;
import java.util.List;
@Data
@ -10,4 +11,5 @@ public class ApiDefinitionImport {
private String projectName;
private String protocol;
private List<ApiDefinitionResult> data;
private HashMap<String, List<ApiDefinitionResult>> resultMap;
}

View File

@ -22,7 +22,6 @@ import lombok.Data;
@JSONType(seeAlso = {HttpRequest.class, DubboRequest.class, SqlRequest.class, TCPRequest.class}, typeKey = "type")
@Data
public abstract class Request {
private String type;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 2)

View File

@ -11,6 +11,8 @@ import io.metersphere.api.dto.definition.response.HttpResponse;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.api.dto.scenario.request.Request;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.commons.constants.SwaggerParameterType;
import io.swagger.models.*;
@ -36,7 +38,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
swagger = new SwaggerParser().readWithInfo(getApiTestStr(source)).getSwagger();
}
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
definitionImport.setData(parseRequests(swagger));
definitionImport.setResultMap(parseRequests(swagger));
return definitionImport;
}
@ -45,13 +47,15 @@ public class Swagger2Parser extends ApiImportAbstractParser {
return null;
}
private List<ApiDefinitionResult> parseRequests(Swagger swagger) {
List<ApiDefinitionResult> results = new LinkedList<>();
private HashMap<String, List<ApiDefinitionResult>> parseRequests(Swagger swagger) {
// List<ApiDefinitionResult> results = new LinkedList<>();
Map<String, Path> paths = swagger.getPaths();
Set<String> pathNames = paths.keySet();
this.definitions = swagger.getDefinitions();
HashMap<String, List<ApiDefinitionResult>> moduleMap = new HashMap<>();
for (String pathName : pathNames) {
Path path = paths.get(pathName);
Map<HttpMethod, Operation> operationMap = path.getOperationMap();
@ -64,33 +68,36 @@ public class Swagger2Parser extends ApiImportAbstractParser {
parseParameters(operation, request);
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setId(request.getId());
results.add(apiDefinition);
parseResponse(operation.getResponses());
// List<String> tags = operation.getTags();
// if (tags != null) {
// tags.forEach(tag -> {
// Scenario scenario = Optional.ofNullable(scenarioMap.get(tag)).orElse(new Scenario());
// List<Request> requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>());
// requests.add(request);
// scenario.setRequests(requests);
// scenario.setName(tag);
// scenarioMap.put(tag, scenario);
// });
// } else {
// Scenario scenario = Optional.ofNullable(scenarioMap.get("default")).orElse(new Scenario());
// List<Request> requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>());
// requests.add(request);
// scenario.setRequests(requests);
// scenarioMap.put("default", scenario);
// }
// results.add(apiDefinition);
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
buildResultMap(moduleMap, apiDefinition, operation);
}
}
this.definitions = null;
return moduleMap;
}
return results;
private void buildResultMap(HashMap<String, List<ApiDefinitionResult>> moduleMap,
ApiDefinitionResult apiDefinition, Operation operation) {
List<String> tags = operation.getTags();
if (tags != null) {
tags.forEach(tag -> {
List<ApiDefinitionResult> list = moduleMap.get(tag);
if (list == null) {
list = new ArrayList<>();
moduleMap.put(tag, list);
}
list.add(apiDefinition);
});
} else {
List<ApiDefinitionResult> list = moduleMap.get("default");
if (list == null) {
list = new ArrayList<>();
moduleMap.put("default", list);
}
list.add(apiDefinition);
}
}
private ApiDefinitionResult buildApiDefinition(Operation operation, String path, String method) {
@ -207,7 +214,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
addHeader(headers, headerParameter.getName(), "", getDefaultStringValue(headerParameter.getDescription()));
}
private void parseResponse(Map<String, Response> responses) {
private HttpResponse parseResponse(Map<String, Response> responses) {
HttpResponse msResponse = new HttpResponse();
msResponse.setBody(new Body());
msResponse.setHeaders(new ArrayList<>());
@ -221,6 +228,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
parseResponseBodyParameters(response, msResponse.getBody());
});
}
return msResponse;
}
private void parseResponseHeader(Response response, List<KeyValue> msHeaders) {

View File

@ -0,0 +1,35 @@
package io.metersphere.api.service;
import io.metersphere.api.dto.ApiMonitorSearch;
import io.metersphere.api.dto.ApiResponseCodeMonitor;
import io.metersphere.api.dto.ApiResponseTimeMonitor;
import io.metersphere.base.mapper.ApiDataViewMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class APIMonitorService {
@Resource
private ApiDataViewMapper apiDataViewMapper;
public List<ApiMonitorSearch> list() {
return apiDataViewMapper.selectAll();
}
public List<ApiResponseTimeMonitor> getApiResponseTimeData(String apiUrl, String startTime, String endTime) {
return apiDataViewMapper.selectResponseTimeByUrl(apiUrl, startTime, endTime);
}
public List<ApiResponseCodeMonitor> getApiResponseCodeData(String apiUrl, String startTime, String endTime) {
return apiDataViewMapper.selectResponseCodeByUrl(apiUrl, startTime, endTime);
}
public String getReportId(String apiUrl, String startTime) {
return apiDataViewMapper.selectReportIdByUrlAndStartTime(apiUrl, startTime);
}
}

View File

@ -1,17 +1,21 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.DeleteAPIReportRequest;
import io.metersphere.api.dto.QueryAPIReportRequest;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDataViewMapper;
import io.metersphere.base.mapper.ApiTestReportDetailMapper;
import io.metersphere.base.mapper.ApiTestReportMapper;
import io.metersphere.base.mapper.ext.ExtApiTestReportMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.i18n.Translator;
@ -21,8 +25,11 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@ -36,6 +43,8 @@ public class APIReportService {
private ApiTestReportDetailMapper apiTestReportDetailMapper;
@Resource
private ExtApiTestReportMapper extApiTestReportMapper;
@Resource
private ApiDataViewMapper apiDataViewMapper;
public List<APIReportResult> list(QueryAPIReportRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
@ -63,6 +72,8 @@ public class APIReportService {
public void delete(DeleteAPIReportRequest request) {
apiTestReportDetailMapper.deleteByPrimaryKey(request.getId());
apiTestReportMapper.deleteByPrimaryKey(request.getId());
apiDataViewMapper.deleteByReportId(request.getId());
}
public void deleteByTestId(String testId) {
@ -89,6 +100,8 @@ public class APIReportService {
// report
report.setUpdateTime(System.currentTimeMillis());
if (!StringUtils.equals(report.getStatus(), APITestStatus.Debug.name())) {
//新增每一条接口记录到api_data_view表中
creatApiDataView(new String(detail.getContent(), StandardCharsets.UTF_8), report.getId());
if (result.getError() > 0) {
report.setStatus(APITestStatus.Error.name());
} else {
@ -99,6 +112,44 @@ public class APIReportService {
apiTestReportMapper.updateByPrimaryKeySelective(report);
}
private void creatApiDataView(String jsonString, String reportId) {
List<ApiDataView> listApiDataView = new ArrayList<>();
JSONObject jsonObject = JSON.parseObject(jsonString, JSONObject.class);
JSONArray jsonArray = jsonObject.getJSONArray("scenarios");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonInArray = jsonArray.getJSONObject(i);
JSONArray jsonRequestResults = jsonInArray.getJSONArray("requestResults");
for (int j = 0; j < jsonRequestResults.size(); j++) {
JSONObject jsonInResponseResult = jsonRequestResults.getJSONObject(j).getJSONObject("responseResult");
String responseTime = jsonInResponseResult.getString("responseTime");
String responseCode = jsonInResponseResult.getString("responseCode");
String startTime = jsonRequestResults.getJSONObject(j).getString("startTime");
String name = jsonRequestResults.getJSONObject(j).getString("name");
String url = jsonRequestResults.getJSONObject(j).getString("url");
if (StringUtils.isBlank(url)){
//如果非http请求不入库
continue;
}
ApiDataView apiDataView = new ApiDataView();
apiDataView.setId(UUID.randomUUID().toString());
apiDataView.setReportId(reportId);
apiDataView.setApiName(name);
apiDataView.setUrl(StringUtils.substringBefore(url,"?"));
apiDataView.setResponseTime(responseTime);
apiDataView.setStartTime(sdf.format(new Date(Long.parseLong(startTime))));
apiDataView.setResponseCode(responseCode);
listApiDataView.add(apiDataView);
}
}
} catch (Exception e) {
LogUtil.error(e);
}
apiDataViewMapper.insertListApiData(listApiDataView);
}
public String create(ApiTest test, String triggerMode) {
ApiTestReport running = getRunningReport(test.getId());
if (running != null) {

View File

@ -61,6 +61,8 @@ public class ApiDefinitionService {
private JMeterService jMeterService;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ApiModuleService apiModuleService;
private static Cache cache = Cache.newHardMemoryCache(0, 3600 * 24);
@ -333,6 +335,12 @@ public class ApiDefinitionService {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiDefinitionMapper batchMapper = sqlSession.getMapper(ApiDefinitionMapper.class);
List<ApiDefinitionResult> data = apiImport.getData();
HashMap<String, List<ApiDefinitionResult>> resultMap = apiImport.getResultMap();
resultMap.forEach((module, apiDefinition) -> {
// apiModuleService
// apiModuleService.addNode();
});
for (int i = 0; i < data.size(); i++) {
ApiDefinitionResult item = data.get(i);
item.setProjectId(importRequest.getProjectId());

View File

@ -105,6 +105,17 @@ public class ApiModuleService {
return node.getId();
}
public ApiModule getNewModule(String name, String projectId, int level) {
ApiModule node = new ApiModule();
node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis());
node.setId(UUID.randomUUID().toString());
node.setLevel(level);
node.setName(name);
node.setProjectId(projectId);
return node;
}
private void validateNode(ApiModule node) {
if (node.getLevel() > TestCaseConstants.MAX_NODE_DEPTH) {
throw new RuntimeException(Translator.get("test_case_node_level_tip")
@ -128,7 +139,7 @@ public class ApiModuleService {
criteria.andIdNotEqualTo(node.getId());
}
if (apiModuleMapper.selectByExample(example).size() > 0) {
MSException.throwException(Translator.get("test_case_module_already_exists"));
MSException.throwException(Translator.get("test_case_module_already_exists") + ": " + node.getName());
}
}
}

View File

@ -0,0 +1,24 @@
package io.metersphere.base.domain;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiDataView implements Serializable {
private String id;
private String reportId;
private String url;
private String apiName;
private String startTime;
private String responseCode;
private String responseTime;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,23 @@
package io.metersphere.base.mapper;
import io.metersphere.api.dto.ApiMonitorSearch;
import io.metersphere.api.dto.ApiResponseCodeMonitor;
import io.metersphere.api.dto.ApiResponseTimeMonitor;
import io.metersphere.base.domain.ApiDataView;
import java.util.List;
public interface ApiDataViewMapper {
List<ApiMonitorSearch> selectAll();
List<ApiResponseTimeMonitor> selectResponseTimeByUrl(String url,String startTime,String endTime);
List<ApiResponseCodeMonitor> selectResponseCodeByUrl(String url,String startTime,String endTime);
Integer insertListApiData(List<ApiDataView> list);
Integer deleteByReportId(String reportId);
String selectReportIdByUrlAndStartTime(String apiUrl,String startTime);
}

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ApiDataViewMapper">
<resultMap id="apiDataView" type="io.metersphere.base.domain.ApiDataView">
<id column="id" property="id"/>
<result column="report_id" property="reportId"/>
<result column="api_name" property="apiName"/>
<result column="url" property="url"/>
<result column="response_code" property="startTime"/>
<result column="start_time" property="responseCode"/>
<result column="response_time" property="responseTime"/>
</resultMap>
<delete id="deleteByReportId" parameterType="java.lang.String">
delete from api_data_view where report_id = #{reportId,jdbcType=VARCHAR}
</delete>
<select id="selectAll" resultType="io.metersphere.api.dto.ApiMonitorSearch">
select distinct url from api_data_view;
</select>
<select id="selectResponseTimeByUrl" parameterType="java.lang.String"
resultType="io.metersphere.api.dto.ApiResponseTimeMonitor">
select id,report_id,api_name,start_time,response_time,url
from api_data_view
<where>
<if test="url != null and url != ''">
url = #{url}
</if>
<if test="startTime != null and startTime != '' and endTime != null and endTime != ''">
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
between #{startTime} and #{endTime}
</if>
<if test="startTime == null or startTime == '' or endTime == null or endTime == ''">
AND TO_DAYS(start_time) =TO_DAYS(NOW())
</if>
order by start_time;
</where>
</select>
<select id="selectResponseCodeByUrl" resultType="io.metersphere.api.dto.ApiResponseCodeMonitor">
select id,report_id,api_name,start_time,response_code,url
from api_data_view
<where>
<if test="url != null and url != ''">
url = #{url}
</if>
<if test="startTime != null and startTime != '' and endTime != null and endTime != ''">
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
between #{startTime} and #{endTime} and length(response_code)=3
</if>
<if test="startTime == null or startTime == '' or endTime == null or endTime == ''">
AND TO_DAYS(start_time) =TO_DAYS(NOW()) and length(response_code)=3
</if>
order by start_time;
</where>
</select>
<select id="selectReportIdByUrlAndStartTime" resultType="java.lang.String">
select report_id from api_data_view where response_code != 200 and url=#{apiUrl} and start_time=#{startTime};
</select>
<insert id="insertListApiData" parameterType="java.util.List">
insert into api_data_view(id, report_id, api_name,url, response_code, start_time,response_time)
values
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=VARCHAR},
#{item.reportId,jdbcType=VARCHAR},
#{item.apiName,jdbcType=VARCHAR},
#{item.url,jdbcType=VARCHAR},
#{item.responseCode,jdbcType=VARCHAR},
#{item.startTime,jdbcType=VARCHAR},
#{item.responseTime,jdbcType=VARCHAR}
)
</foreach>
</insert>
</mapper>

View File

@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS `api_data_view`
(
id varchar(50) NOT NULL primary key,
report_id varchar(255) NOT NULL,
api_name varchar(200) NULL,
url varchar(255) NULL,
response_code varchar(100) NULL,
start_time varchar(20) NULL,
response_time varchar(20) default '0' NULL,
create_time timestamp default CURRENT_TIMESTAMP NOT NULL,
update_time timestamp default CURRENT_TIMESTAMP NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -40,6 +40,11 @@
<el-divider class="menu-divider"/>
<ms-show-all :index="'/api/report/list/all'"/>
</el-submenu>
<el-menu-item v-permission="['test_manager','test_user','test_viewer']" :index="'/api/monitor/view'">
{{ $t('commons.monitor') }}
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="8">

View File

@ -0,0 +1,168 @@
<template>
<ms-container>
<ms-aside-container>
<ms-api-monitor-search @getApiUrl="getApiUrl(arguments)" @getTodayData="getTodayData"
@initPage="initPage(arguments)"></ms-api-monitor-search>
</ms-aside-container>
<ms-main-container>
<div class="menu-wrapper">
<div class="menu-left">
<el-radio-group id="date-radio" v-model=radioSelect size="small">
<el-radio-button :label="$t('api_monitor.today')" @click.native.prevent="getTodayData()"/>
<el-radio-button :label="$t('api_monitor.this_week')" @click.native.prevent="getWeekData()"/>
<el-radio-button :label="$t('api_monitor.this_mouth')" @click.native.prevent="getMouthData()"/>
</el-radio-group>
<el-date-picker id="date-picker"
v-model="datePicker"
:end-placeholder="$t('api_monitor.end_time')"
:range-separator="$t('api_monitor.to')"
:start-placeholder="$t('api_monitor.start_time')"
class="sales-view-date-picker"
size="small"
type="daterange"
unlink-panels
value-format="yyyy-MM-dd HH:mm:ss" @blur="getDatePicker"
/>
<el-tag id="apiInfo" type="info"><a id="api-url-title">{{ apiUrl }}</a></el-tag>
</div>
</div>
<ms-api-monitor-chart :api-url="apiUrl" :rspCodeData=this.rspCodeData
:rspCodexAxis=this.rspCodexAxis :rspTimeData=this.rspTimeData
:rspTimexAxis=this.rspTimexAxis></ms-api-monitor-chart>
</ms-main-container>
</ms-container>
</template>
<script>
import ApiMonitorCharts from '@/business/components/api/monitor/ApiMonitorChart';
import MsApiMonitorChart from '@/business/components/api/monitor/ApiMonitorChart';
import MsApiMonitorSearch from '@/business/components/api/monitor/ApiMonitorSearch';
import MsMainContainer from '@/business/components/common/components/MsMainContainer';
import MsAsideContainer from '@/business/components/common/components/MsAsideContainer';
import MsContainer from '@/business/components/common/components/MsContainer';
import {formatTime} from '@/common/js/format-utils';
export default {
name: 'MsApiMonitor',
data() {
return {
datePicker: null,
rspTimeData: [],
rspTimexAxis: [],
rspCodeData: [],
rspCodexAxis: [],
apiUrl: '',
radioSelect: this.$t('api_monitor.today'),
};
},
activated() {
this.initData();
},
methods: {
initData() {
this.rspTimeData = [];
this.rspTimexAxis = [];
this.rspCodeData = [];
this.rspCodexAxis = [];
},
initPage(url) {
this.apiUrl = url[0];
let date1 = new Date();
let today1 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()));
let today2 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate() + 1));
this.initData();
this.getResponseTime(this.apiUrl, today1, today2);
this.getResponseCode(this.apiUrl, today1, today2);
},
getResponseTime(apiUrl, startTime, endTime) {
return this.$$get('/api/monitor/getResponseTime',
{'startTime': startTime, 'endTime': endTime},
{'apiUrl': apiUrl}, response => {
Object.values(response.data).forEach(value => {
this.rspTimexAxis.push(value.startTime);
this.rspTimeData.push(value.responseTime);
});
});
},
getResponseCode(apiUrl, startTime, endTime) {
return this.$$get('/api/monitor/getResponseCode',
{'startTime': startTime, 'endTime': endTime},
{'apiUrl': this.apiUrl}, response => {
Object.values(response.data).forEach(value => {
this.rspCodexAxis.push(value.startTime);
this.rspCodeData.push(value.responseCode);
});
});
},
getDatePicker() {
this.initData();
this.getResponseTime(this.apiUrl, this.datePicker[0], this.datePicker[1]);
this.getResponseCode(this.apiUrl, this.datePicker[0], this.datePicker[1]);
},
//
getFirstDayOfWeek(date) {
let day = date.getDay() || 7;
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1 - day);
},
getApiUrl(payload) {
this.apiUrl = payload[0];
},
//
getTodayData() {
this.radioSelect = this.$t('api_monitor.today');
let date1 = new Date();
let today1 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()));
let today2 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate() + 1));
this.initData();
this.getResponseTime(this.apiUrl, today1, today2);
this.getResponseCode(this.apiUrl, today1, today2);
},
//
getWeekData() {
this.radioSelect = this.$t('api_monitor.this_week');
const date1 = new Date();
let today = formatTime(date1);
let monday = formatTime(this.getFirstDayOfWeek(date1));
this.initData();
this.getResponseTime(this.apiUrl, monday, today);
this.getResponseCode(this.apiUrl, monday, today);
},
//
getMouthData() {
this.radioSelect = this.$t('api_monitor.this_mouth');
const day = new Date(), y = day.getFullYear(), m = day.getMonth();
let firstDay = formatTime(new Date(y, m, 1));
let today = formatTime(day);
this.initData();
this.getResponseTime(this.apiUrl, firstDay, today);
this.getResponseCode(this.apiUrl, firstDay, today);
},
},
components: {
MsApiMonitorChart,
MsContainer,
MsAsideContainer,
MsMainContainer,
MsApiMonitorSearch,
ApiMonitorCharts,
},
};
</script>
<style scoped>
.menu-left #apiInfo {
margin-left: 20px;
}
#api-url-title {
font-size: 15px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="monitor-view">
<div class="response-time-view">
<el-card shadow="hover">
<api-response-time-monitor-chart :data="rspTimeData" :xAxis="rspTimexAxis"></api-response-time-monitor-chart>
</el-card>
</div>
<div class="error-monitor-view">
<el-card shadow="hover">
<api-error-monitor-chart :api-url="apiUrl" :data="rspCodeData" :xAxis="rspCodexAxis"></api-error-monitor-chart>
</el-card>
</div>
</div>
</template>
<script>
import ApiResponseTimeMonitorChart from '@/business/components/api/monitor/components/ApiResponseTimeMonitorChart';
import ApiErrorMonitorChart from '@/business/components/api/monitor/components/ApiErrorMonitorChart';
export default {
name: 'MsApiMonitorChart',
props: [
'rspTimeData',
'rspTimexAxis',
'rspCodeData',
'rspCodexAxis',
'apiUrl'
],
data() {
return {};
},
components: {
ApiErrorMonitorChart,
ApiResponseTimeMonitorChart,
},
methods: {}
};
</script>
<style scoped>
.error-monitor-view {
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,100 @@
<template>
<div>
<el-input v-model="input" :placeholder="$t('api_monitor.please_search')" prefix-icon="el-icon-search" type="text"
@input="searchAction"></el-input>
<el-table
:data="searchResult"
:show-header="false"
border
class="question-tab2"
style="width: 100%"
@row-click="getRowInfo"
>
<el-table-column
:label="$t('api_monitor.date')"
prop="url"
style="width: 100%">
</el-table-column>
</el-table>
</div>
</template>
<script>
function throttle(fn, delay) {
let t = null,
begin = new Date().getTime();
return function () {
const _self = this,
args = arguments,
cur = new Date().getTime();
clearTimeout(t);
if (cur - begin >= delay) {
fn.apply(_self, args);
begin = cur;
} else {
t = setTimeout(function () {
fn.apply(_self, args);
}, delay);
}
};
}
export default {
name: 'MsApiMonitorSearch',
data() {
return {
result: {},
searchResult: [],
items: [],
input: '',
rowData: '',
apiUrl: '',
firstUrl: '',
rspTimeData: [],
rspTimexAxis: [],
rspCodeData: [],
rspCodexAxis: [],
}
},
methods: {
searchAction: throttle(function (e) {
this.searchResult = this.items.filter((item) => {
if (item.url.includes(this.input)) {
return item;
}
});
}, 500),
search() {
this.result = this.$get('/api/monitor/list', response => {
this.items = response.data;
});
},
getRowInfo(val) {
this.rowData = val;
this.apiUrl = this.rowData.url;
this.$emit('getApiUrl', this.apiUrl, this.firstUrl);
this.$emit('getTodayData');
},
},
activated() {
this.searchResult=[];
this.result = this.$get('/api/monitor/list', response => {
if (response.data.length !== 0) {
this.searchResult = response.data;
this.firstUrl = response.data[0].url;
this.$emit('initPage', this.firstUrl);
this.search();
}
});
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,93 @@
<template>
<common-monitor-chart>
<template>
<div id="response-time-chart" :style="{ width:'100%',height:'100%' }">
<chart :options="getOptions()" :style="{ width:'100%' }"></chart>
</div>
</template>
</common-monitor-chart>
</template>
<script>
import CommonMonitorChart from '@/business/components/api/monitor/components/CommonMonitorChart';
export default {
name: 'ApiErrorMonitorChart',
components: {CommonMonitorChart},
props: [
'data',
'xAxis',
'apiUrl',
],
data() {
return {
reportId: '',
};
},
methods: {
click(params) {
//2
if (params.value.substr(0, 1) !== '2') {
let startTime = params.name;
this.result = this.$$get('/api/monitor/getReportId', {'startTime': startTime}, {
'apiUrl': this.apiUrl
}, response => {
this.reportId = response.data;
let reportId = this.reportId
let url = '#/api/report/view/' + reportId;
let target = '_blank';
window.open(url, target);
});
}
},
getOptions() {
return {
title: {text: 'HTTP状态码趋势'},
tooltip: {},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {
readOnly: false
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [{
start: 0
}],
xAxis: {
type: 'category',
data: this.xAxis
},
yAxis: {
type: 'value',
min: 100,
max: 500,
splitNumber: 5
},
series: [{
type: 'line',
smooth: true,
data: this.data,
lineStyle: {
color: '#32CD32'
},
itemStyle: {},
}],
};
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,64 @@
<template>
<common-monitor-chart>
<template>
<div id="response-time-chart" :style="{ width:'100%',height:'100%' }">
<chart :options="getOptions()" :style="{ width:'100%' }"></chart>
</div>
</template>
</common-monitor-chart>
</template>
<script>
import CommonMonitorChart from '@/business/components/api/monitor/components/CommonMonitorChart';
export default {
name: 'ApiResponseTimeMonitorChart',
components: {CommonMonitorChart},
props: [
'data',
'xAxis'
],
methods: {
getOptions() {
return {
title: {text: '响应时间趋势'},
tooltip: {},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: {
readOnly: false
},
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
data: this.xAxis
},
yAxis: {
},
dataZoom: [{
start: 0
}],
series: [{
type: 'line',
smooth: true,
data: this.data
}],
};
}
},
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="common-card">
<div class="chart">
<slot></slot>
</div>
<div class="line"/>
</div>
</template>
<script>
export default {
props: {
title: String,
}
};
</script>
<style scoped>
.title {
font-size: 20px;
color: #999;
}
.line {
margin: 10px 0;
border-top: 1px solid #eee;
}
</style>
<style>
</style>

View File

@ -81,7 +81,7 @@ export default {
if (!this.response.headers) {
return;
}
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
if (this.response.headers.indexOf("application/json") > 0) {
this.mode = BODY_FORMAT.JSON;
}
},

View File

@ -118,4 +118,12 @@
max-height: 500px;
}
.el-table >>> th {
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
</style>

View File

@ -226,8 +226,9 @@ export default {
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
let color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
handler.options = {
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
color: color,
xAxis: {
type: 'category',
boundaryGap: false,
@ -261,10 +262,10 @@ export default {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: hexToRgba(handler.options.color[i], 0.3),
color: hexToRgba(color[i % color.length], 0.3),
}, {
offset: 0.8,
color: hexToRgba(handler.options.color[i], 0),
color: hexToRgba(color[i % color.length], 0),
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
@ -272,7 +273,7 @@ export default {
},
itemStyle: {
normal: {
color: hexToRgb(handler.options.color[i]),
color: hexToRgb(color[i % color.length]),
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}

View File

@ -268,8 +268,9 @@ export default {
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
let color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
handler.options = {
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
color: color,
xAxis: {
type: 'category',
boundaryGap: false,
@ -302,10 +303,10 @@ export default {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: hexToRgba(handler.options.color[i], 0.3),
color: hexToRgba(color[i % color.length], 0.3),
}, {
offset: 0.8,
color: hexToRgba(handler.options.color[i], 0),
color: hexToRgba(color[i % color.length], 0),
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
@ -313,7 +314,7 @@ export default {
},
itemStyle: {
normal: {
color: hexToRgb(handler.options.color[i]),
color: hexToRgb(color[i % color.length]),
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}

@ -1 +1 @@
Subproject commit 8a972a198775b3783ed6e4cef27197e53d1ebdc8
Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a

View File

@ -74,12 +74,12 @@ export default {
}
}
Vue.prototype.$$get = function (url, data, success) {
Vue.prototype.$$get = function (url, data, header, success) {
let result = {loading: true};
if (!success) {
return axios.get(url, {params: data});
return axios.get(url, {params: data, headers: header});
} else {
axios.get(url, {params: data}).then(response => {
axios.get(url, {params: data, headers: header}).then(response => {
then(success, response, result);
}).catch(error => {
exception(error, result, url);

View File

@ -4,6 +4,8 @@ import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/pie'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/title'
import 'echarts/lib/component/toolbox';
import 'echarts/lib/component/dataZoom';
import 'zrender/lib/svg/svg'
export default {

View File

@ -120,6 +120,42 @@ export function formatXml(text) {
return outputText.replace(/\s+$/g, '').replace(/\r/g, '\r\n');
}
/**
* @param time 时间
* @param cFormat 格式
* @returns {string|null} 字符串
* @example formatTime('2018-1-29', '{y}/{m}/{d} {h}:{i}:{s}') // -> 2018/01/29 00:00:00
*/
export function formatTime(time, cFormat) {
if (arguments.length === 0) return null;
if ((time + '').length === 10) {
time = +time * 1000;
}
let format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}', date;
if (typeof time === 'object') {
date = time;
} else {
date = new Date(time);
}
let formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
};
return format.replace(/{([ymdhisa])+}/g, (result, key) => {
let value = formatObj[key];
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
if (result.length > 0 && value < 10) {
value = '0' + value;
}
return value || 0;
});
}
function getPrefix(prefixIndex) {
var span = ' ';
var output = [];

View File

@ -162,7 +162,8 @@ export default {
between: "Between",
current_user: "Current user"
}
}
},
monitor:"monitor"
},
license: {
title: 'Authorization management',
@ -777,6 +778,16 @@ export default {
running: "The test is reporting",
not_exist: "Test report does not exist",
},
api_monitor: {
to:"to",
start_time:"Start Time",
end_time:"End Time",
today:"Today",
this_week:"This Week",
this_mouth:"This Mouth",
please_search:"Please Search",
date:"Date"
},
test_track: {
test_track: "Track",
confirm: "Confirm",

View File

@ -162,7 +162,8 @@ export default {
between: "之间",
current_user: "是当前用户"
}
}
},
monitor: "监控"
},
license: {
title: '授权管理',
@ -807,6 +808,16 @@ export default {
running: "测试报告导出中",
not_exist: "测试报告不存在",
},
api_monitor: {
to:"至",
start_time:"开始日期",
end_time:"结束日期",
today:"今日",
this_week:"本周",
this_mouth:"本月",
please_search:"请搜索",
date:"日期"
},
test_track: {
test_track: "测试跟踪",
confirm: "确 定",

View File

@ -162,7 +162,8 @@ export default {
between: "之間",
current_user: "是當前用戶"
}
}
},
monitor:"監控"
},
license: {
title: '授權管理',
@ -780,6 +781,16 @@ export default {
running: "測試報告導出中",
not_exist: "測試報告不存在",
},
api_monitor: {
to:"到",
start_time:"開始時間",
end_time:"結束時間",
today:"今天",
this_week:"本週",
this_mouth:"本月",
please_search:"請搜索",
date:"日期"
},
test_track: {
test_track: "測試跟蹤",
confirm: "確 定",