impl: 完成前端面板相关基本功能

ref: 补充修改后的建表sql文件
ref: 微调部分数据格式
ref: 调整springboot3.2下接口404的异常处理
fix: 修复特定分钟粒度下获取监测数据的sql错误问题
dev-lensfrex
lensfrex 7 months ago
parent c82c6d5799
commit 3fb9308f95
Signed by: lensfrex
GPG Key ID: B1E395B3C6CA0356
  1. 19
      docs/rition.sql
  2. 13
      rition-center/api/src/main/java/rition/backend/aop/exception/GlobalExceptionHandler.java
  3. 36
      rition-center/api/src/main/java/rition/backend/api/interceptor/NotFoundPathInterceptor.java
  4. 7
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/AlertResponse.java
  5. 2
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/ContractResponse.java
  6. 4
      rition-center/api/src/main/java/rition/backend/api/v1/dto/response/RuleResponse.java
  7. 17
      rition-center/api/src/main/java/rition/backend/api/v1/panel/AlertHistoryController.java
  8. 4
      rition-center/api/src/main/java/rition/backend/api/v1/panel/AlertRuleController.java
  9. 2
      rition-center/api/src/main/java/rition/backend/api/v1/panel/ContractController.java
  10. 8
      rition-center/api/src/main/java/rition/backend/configure/WebMvcConfigure.java
  11. 2
      rition-center/common/src/main/java/rition/common/data/dto/PagingData.java
  12. 5
      rition-center/common/src/main/java/rition/common/data/entity/AlertEntity.java
  13. 4
      rition-center/common/src/main/resources/mapper/MetricRecordMapper.xml
  14. 1
      rition-panel/package.json
  15. 2
      rition-panel/quasar.config.js
  16. 5
      rition-panel/src/boot/axios.js
  17. 2
      rition-panel/src/layouts/MainLayout.vue
  18. 45
      rition-panel/src/pages/AlertListPage.vue
  19. 113
      rition-panel/src/pages/ContractPage.vue
  20. 18
      rition-panel/src/pages/MetricPage.vue
  21. 102
      rition-panel/src/pages/RulePage.vue
  22. 5
      rition-panel/yarn.lock

@ -11,7 +11,7 @@
Target Server Version : 80027 (8.0.27) Target Server Version : 80027 (8.0.27)
File Encoding : 65001 File Encoding : 65001
Date: 08/05/2024 10:15:09 Date: 10/05/2024 14:22:09
*/ */
SET NAMES utf8mb4; SET NAMES utf8mb4;
@ -25,6 +25,7 @@ CREATE TABLE `alert` (
`id` bigint NOT NULL, `id` bigint NOT NULL,
`instance_id` varchar(64) NOT NULL COMMENT '出现警告的实例id', `instance_id` varchar(64) NOT NULL COMMENT '出现警告的实例id',
`rule` bigint NOT NULL COMMENT '触发的规则', `rule` bigint NOT NULL COMMENT '触发的规则',
`value` double NOT NULL COMMENT '警告时的数值',
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '警告出现的时间', `time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '警告出现的时间',
`status` tinyint NOT NULL DEFAULT '0', `status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@ -38,15 +39,13 @@ CREATE TABLE `alert` (
DROP TABLE IF EXISTS `contract`; DROP TABLE IF EXISTS `contract`;
CREATE TABLE `contract` ( CREATE TABLE `contract` (
`id` bigint NOT NULL, `id` bigint NOT NULL,
`instance_id` varchar(64) NOT NULL COMMENT '绑定的实例',
`contract` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '联系方式', `contract` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '联系方式',
`type` tinyint NOT NULL COMMENT '联系方式类型', `type` tinyint NOT NULL COMMENT '联系方式类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0', `status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `idx_id` (`id`), KEY `idx_id` (`id`)
KEY `idx_instance` (`instance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ---------------------------- -- ----------------------------
@ -87,17 +86,15 @@ CREATE TABLE `record` (
DROP TABLE IF EXISTS `rule`; DROP TABLE IF EXISTS `rule`;
CREATE TABLE `rule` ( CREATE TABLE `rule` (
`id` bigint NOT NULL COMMENT '规则id', `id` bigint NOT NULL COMMENT '规则id',
`instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规则对应的实例id', `expr` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '需要计算的指标项或者表达式',
`expression` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '需要计算的指标项或者表达式', `cond` tinyint NOT NULL COMMENT '触发条件',
`condition` tinyint NOT NULL COMMENT '触发条件',
`threshold` varchar(32) NOT NULL COMMENT '阈值', `threshold` varchar(32) NOT NULL COMMENT '阈值',
`trigger` tinyint NOT NULL COMMENT '触发方法,实时计算或定时计算', `trig` tinyint NOT NULL COMMENT '触发方法,实时计算或定时计算',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '规则描述', `comment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '规则描述',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0', `status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`)
KEY `idx_instance` (`instance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;

@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import rition.backend.api.v1.dto.response.Response; import rition.backend.api.v1.dto.response.Response;
import rition.common.exception.ServiceException; import rition.common.exception.ServiceException;
import rition.common.exception.code.ServiceCode; import rition.common.exception.code.ServiceCode;
@ -44,6 +45,18 @@ public class GlobalExceptionHandler {
return Response.error(ServiceCode.ApiNotImplement); return Response.error(ServiceCode.ApiNotImplement);
} }
/**
* 404
*
* @param e 异常
* @return 统一响应
*/
@ResponseBody
@ExceptionHandler(NoResourceFoundException.class)
public Response<Object> baseException(NoResourceFoundException e) {
return Response.error(ServiceCode.ApiNotImplement);
}
/** /**
* 处理参数不完整的请求异常 * 处理参数不完整的请求异常
* *

@ -0,0 +1,36 @@
package rition.backend.api.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Nonnull;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import rition.backend.api.v1.dto.response.Response;
import rition.common.exception.code.ServiceCode;
@Component
public class NotFoundPathInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper;
public NotFoundPathInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void postHandle(@Nonnull HttpServletRequest request,
@Nonnull HttpServletResponse servletResponse,
@Nonnull Object handler,
ModelAndView modelAndView) throws Exception {
if (servletResponse.getStatus() == HttpStatus.NOT_FOUND.value()) {
var response = Response.error(ServiceCode.ApiNotImplement);
var json = objectMapper.writeValueAsString(response);
servletResponse.setContentType("application/json");
servletResponse.setCharacterEncoding("utf-8");
servletResponse.getWriter().write(json);
}
}
}

@ -27,8 +27,13 @@ public class AlertResponse {
*/ */
private Long rule; private Long rule;
/**
* 触发时的数值
*/
private Double value;
/** /**
* 警告出现的时间 * 警告出现的时间
*/ */
private Instant time; private Long time;
} }

@ -30,5 +30,5 @@ public class ContractResponse {
/** /**
* create_time * create_time
*/ */
private Instant createTime; private Long createTime;
} }

@ -45,10 +45,10 @@ public class RuleResponse {
/** /**
* create_time * create_time
*/ */
private Instant createTime; private Long createTime;
/** /**
* update_time * update_time
*/ */
private Instant updateTime; private Long updateTime;
} }

@ -4,6 +4,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import rition.backend.api.v1.dto.response.AlertResponse;
import rition.backend.api.v1.dto.response.Response; import rition.backend.api.v1.dto.response.Response;
import rition.common.data.dto.PagingData; import rition.common.data.dto.PagingData;
import rition.common.data.entity.AlertEntity; import rition.common.data.entity.AlertEntity;
@ -19,7 +20,7 @@ public class AlertHistoryController {
} }
@GetMapping("/list") @GetMapping("/list")
public Response<PagingData<AlertEntity>> getContractList( public Response<PagingData<AlertResponse>> getContractList(
@RequestParam(value = "instance_id", required = false) String instanceId, @RequestParam(value = "instance_id", required = false) String instanceId,
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page, @RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "page_size", required = false, defaultValue = "10") Integer pageSize @RequestParam(value = "page_size", required = false, defaultValue = "10") Integer pageSize
@ -28,6 +29,18 @@ public class AlertHistoryController {
PagingData<AlertEntity> alertEntityPagingData = new PagingData<>(page, pageSize); PagingData<AlertEntity> alertEntityPagingData = new PagingData<>(page, pageSize);
var result = alertHistoryService.getAlertHistory(instanceId, alertEntityPagingData); var result = alertHistoryService.getAlertHistory(instanceId, alertEntityPagingData);
return Response.success(result); PagingData<AlertResponse> alertResponsePagingData = PagingData.copyOnlyPagingValues(result);
for (AlertEntity entity : result.getData()) {
alertResponsePagingData.getData().add(AlertResponse.builder()
.id(entity.getId())
.instanceId(entity.getInstanceId())
.rule(entity.getRule())
.value(entity.getValue())
.time(entity.getTime().toEpochMilli())
.build()
);
}
return Response.success(alertResponsePagingData);
} }
} }

@ -32,8 +32,8 @@ public class AlertRuleController {
response.setThreshold(ruleEntity.getThreshold()); response.setThreshold(ruleEntity.getThreshold());
response.setTrigger(ruleEntity.getTrig()); response.setTrigger(ruleEntity.getTrig());
response.setDescription(ruleEntity.getComment()); response.setDescription(ruleEntity.getComment());
response.setCreateTime(ruleEntity.getCreateTime()); response.setCreateTime(ruleEntity.getCreateTime().toEpochMilli());
response.setUpdateTime(ruleEntity.getUpdateTime()); response.setUpdateTime(ruleEntity.getUpdateTime().toEpochMilli());
ruleResponseList.add(response); ruleResponseList.add(response);
} }

@ -29,7 +29,7 @@ public class ContractController {
response.setId(contractEntity.getId()); response.setId(contractEntity.getId());
response.setContract(contractEntity.getContract()); response.setContract(contractEntity.getContract());
response.setType(contractEntity.getType()); response.setType(contractEntity.getType());
response.setCreateTime(contractEntity.getCreateTime()); response.setCreateTime(contractEntity.getCreateTime().toEpochMilli());
responseList.add(response); responseList.add(response);
} }

@ -6,6 +6,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import rition.backend.api.interceptor.NotFoundPathInterceptor;
import rition.backend.api.interceptor.ResponseIdInterceptor; import rition.backend.api.interceptor.ResponseIdInterceptor;
import rition.backend.api.resolver.RequestIdArgumentResolver; import rition.backend.api.resolver.RequestIdArgumentResolver;
@ -17,11 +18,13 @@ public class WebMvcConfigure implements WebMvcConfigurer {
private final RequestIdArgumentResolver requestIdArgumentResolver; private final RequestIdArgumentResolver requestIdArgumentResolver;
private final ResponseIdInterceptor requestIdInterceptor; private final ResponseIdInterceptor requestIdInterceptor;
private final NotFoundPathInterceptor notFoundPathInterceptor;
public WebMvcConfigure(RequestIdArgumentResolver requestIdArgumentResolver, public WebMvcConfigure(RequestIdArgumentResolver requestIdArgumentResolver,
ResponseIdInterceptor requestIdInterceptor) { ResponseIdInterceptor requestIdInterceptor, NotFoundPathInterceptor notFoundPathInterceptor) {
this.requestIdArgumentResolver = requestIdArgumentResolver; this.requestIdArgumentResolver = requestIdArgumentResolver;
this.requestIdInterceptor = requestIdInterceptor; this.requestIdInterceptor = requestIdInterceptor;
this.notFoundPathInterceptor = notFoundPathInterceptor;
} }
@Override @Override
@ -33,5 +36,8 @@ public class WebMvcConfigure implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestIdInterceptor) registry.addInterceptor(requestIdInterceptor)
.addPathPatterns("/**"); .addPathPatterns("/**");
registry.addInterceptor(notFoundPathInterceptor)
.addPathPatterns("/**");
} }
} }

@ -6,6 +6,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@Data @Data
@ -45,6 +46,7 @@ public class PagingData<T> {
pagingData.setPageSize(source.getPageSize()); pagingData.setPageSize(source.getPageSize());
pagingData.setTotalPage(source.getTotalPage()); pagingData.setTotalPage(source.getTotalPage());
pagingData.setTotalResult(source.getTotalResult()); pagingData.setTotalResult(source.getTotalResult());
pagingData.setData(new ArrayList<>(source.data.size()));
return pagingData; return pagingData;
} }

@ -29,6 +29,11 @@ public class AlertEntity {
*/ */
private Long rule; private Long rule;
/**
* 触发时的数值
*/
private Double value;
/** /**
* 警告出现的时间 * 警告出现的时间
*/ */

@ -68,8 +68,8 @@
avg(JSON_EXTRACT(metric_data, "$.${metricItem}")) as ${metricItem}, avg(JSON_EXTRACT(metric_data, "$.${metricItem}")) as ${metricItem},
</foreach> </foreach>
DATE_FORMAT( DATE_FORMAT(
concat( date( TimeStart ), ' ', HOUR ( TimeStart ), ':', floor( MINUTE ( TimeStart ) / #{minutes} ) * ${minutes} ), concat( date( `time` ), ' ', HOUR ( `time` ), ':', floor( MINUTE ( `time` ) / #{minutes} ) * ${minutes} ),
'%Y-%m-%d %H:%i' '%Y-%m-%d %H:%i:00'
) as t ) as t
FROM record WHERE `time` BETWEEN #{startTime} and #{endTime} and `instance_id` = #{instanceId} FROM record WHERE `time` BETWEEN #{startTime} and #{endTime} and `instance_id` = #{instanceId}
GROUP BY t ORDER BY t GROUP BY t ORDER BY t

@ -17,6 +17,7 @@
"axios": "^1.2.1", "axios": "^1.2.1",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.7.2", "element-plus": "^2.7.2",
"moment": "^2.30.1",
"quasar": "^2.16.0", "quasar": "^2.16.0",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"vue": "^3.4.18", "vue": "^3.4.18",

@ -108,7 +108,7 @@ module.exports = configure(function (/* ctx */) {
// directives: [], // directives: [],
lang: 'zh-CN', lang: 'zh-CN',
// Quasar plugins // Quasar plugins
plugins: [] plugins: ['Dialog', 'Loading']
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations

@ -7,7 +7,10 @@ import axios from 'axios'
// good idea to move this instance creation inside of the // good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually // "export default () => {}" function below (which runs individually
// for each client) // for each client)
const api = axios.create({ baseURL: window.location.origin + "/api" }) const api = axios.create({
baseURL: window.location.origin + "/api",
timeout: 10 * 1000
})
export default boot(({ app }) => { export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api // for use inside Vue files (Options API) through this.$axios and this.$api

@ -45,7 +45,7 @@ const linksList = [
link: '/' link: '/'
}, },
{ {
title: '通知信息', title: '通知设置',
icon: 'notifications', icon: 'notifications',
link: '/contract' link: '/contract'
}, },

@ -1,17 +1,58 @@
<script setup> <script setup>
import {ref} from "vue"; import {ref} from "vue";
import {api} from "boot/axios";
import moment from "moment";
import {useQuasar} from "quasar";
const quasar = useQuasar()
const alertList = ref([])
const instanceId = ref()
function getAlerts() {
api.get('/panel/alerts/list')
.then(resp => {
const response = resp.data
const data = response.data
alertList.value = data.data
})
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
}
getAlerts()
</script> </script>
<template> <template>
<div class="flex flex-col gap-2 p-2"> <div class="flex flex-col gap-2 p-2">
<div class="flex flex-row gap-4 items-center">
<q-input class="w-96" v-model="instanceId" label="实例ID"/>
<q-separator vertical></q-separator>
<q-btn unelevated color="primary" @click="getAlerts">刷新</q-btn>
</div>
<el-table border style="width: 100%;height: 100%"> <el-table :data="alertList" border style="width: 100%;height: 100%">
<el-table-column fixed prop="id" label="ID"/> <el-table-column fixed prop="id" label="ID"/>
<el-table-column prop="instanceId" label="实例ID"/> <el-table-column prop="instanceId" label="实例ID"/>
<el-table-column prop="rule" label="触发规则"/> <el-table-column prop="rule" label="触发规则"/>
<el-table-column prop="value" label="触发值"/> <el-table-column prop="value" label="触发值"/>
<el-table-column prop="time" label="时间"/> <el-table-column label="时间">
<template #default="scope">
<span>{{ moment(scope.row.time).format("YYYY-MM-DD HH:mm:ss") }}</span>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>

@ -1,5 +1,10 @@
<script setup> <script setup>
import {reactive, ref} from "vue"; import {reactive, ref} from "vue";
import {api} from "boot/axios";
import moment from "moment/moment";
import {useQuasar} from "quasar";
const quasar = useQuasar()
const addDialogPanelOpen = ref(false) const addDialogPanelOpen = ref(false)
@ -8,10 +13,74 @@ const addDialogPanelOpen = ref(false)
* @type {{contract: string, type: number}} * @type {{contract: string, type: number}}
*/ */
const form = reactive({ const form = reactive({
contract:'', contract: '',
type: undefined type: undefined
}) })
const contractList = ref([])
function getContractList() {
api.get('/panel/contract/list')
.then(r => {
contractList.value = r.data.data
})
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
}
getContractList()
function addContract() {
api.post('/panel/contract/add', form)
.then(() => getContractList())
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
}
function deleteContract(id) {
api.post('/panel/contract/delete', undefined, {
params: {
id: id
}
})
.then(() => getContractList())
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
}
const deleteDialogOpen = ref(false)
const deleteId = ref()
const contractTypeText = {
"0": "电话",
"1": "短信",
"2": "邮件",
}
</script> </script>
<template> <template>
@ -20,24 +89,34 @@ const form = reactive({
<q-btn unelevated color="primary" @click="addDialogPanelOpen = !addDialogPanelOpen">添加</q-btn> <q-btn unelevated color="primary" @click="addDialogPanelOpen = !addDialogPanelOpen">添加</q-btn>
</div> </div>
<el-table border style="width: 100%;height: 100%"> <el-table :data="contractList" border style="width: 100%;height: 100%">
<el-table-column fixed prop="id" label="ID"/> <el-table-column fixed prop="id" label="ID"/>
<el-table-column prop="instanceId" label="实例ID"/>
<el-table-column prop="contract" label="联系信息"/> <el-table-column prop="contract" label="联系信息"/>
<el-table-column prop="type" label="类型"/> <el-table-column label="类型">
<el-table-column prop="addTime" label="添加时间"/> <template #default="scope">
<span>{{ contractTypeText[scope.row.type] }}</span>
</template>
</el-table-column>
<el-table-column label="添加时间">
<template #default="scope">
<span>{{ moment(scope.row.createTime).format("YYYY-MM-DD HH:mm:ss") }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<q-btn unelevated color="red" @click="deleteDialogOpen = true;deleteId = scope.row.id">删除</q-btn>
</template>
</el-table-column>
</el-table> </el-table>
<el-dialog v-model="addDialogPanelOpen" title="添加联系信息" width="500"> <el-dialog v-model="addDialogPanelOpen" title="添加联系信息" width="500">
<el-form :model="form"> <el-form :model="form">
<el-form-item label="联系信息" > <el-form-item label="联系信息">
<el-input v-model="form.contract" autocomplete="off" /> <el-input v-model="form.contract" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="联系方式"> <el-form-item label="联系方式">
<el-select v-model="form.type" placeholder="选择一个联系方式"> <el-select v-model="form.type" placeholder="选择一个联系方式">
<el-option label="电话" value="0" /> <el-option v-for="(name, val) in contractTypeText" :key="val" :label="name" :value="val"/>
<el-option label="短信" value="1" />
<el-option label="邮件" value="2" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -45,7 +124,19 @@ const form = reactive({
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<q-btn unelevated @click="addDialogPanelOpen = false">取消</q-btn> <q-btn unelevated @click="addDialogPanelOpen = false">取消</q-btn>
<q-btn class="ml-2" unelevated color="primary" @click="addDialogPanelOpen = false">确认</q-btn> <q-btn class="ml-2" unelevated color="primary" @click="addDialogPanelOpen = false;addContract()">确认</q-btn>
</div>
</template>
</el-dialog>
<el-dialog v-model="deleteDialogOpen" title="确认" width="500">
<span>确认删除</span>
<template #footer>
<div class="dialog-footer flex flex-row gap-2 justify-end">
<q-btn unelevated @click="deleteDialogOpen = false">取消</q-btn>
<q-btn unelevated color="red" @click="deleteDialogOpen = false;deleteContract(deleteId)">
确认
</q-btn>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

@ -8,6 +8,7 @@ import {LineChart} from 'echarts/charts'
import {GridComponent, LegendComponent, TitleComponent, TooltipComponent} from 'echarts/components' import {GridComponent, LegendComponent, TitleComponent, TooltipComponent} from 'echarts/components'
import {CanvasRenderer} from 'echarts/renderers' import {CanvasRenderer} from 'echarts/renderers'
import {api} from "boot/axios"; import {api} from "boot/axios";
import {useQuasar} from "quasar";
use([ use([
GridComponent, GridComponent,
@ -95,6 +96,8 @@ const selectedMetric = ref([
const instanceId = ref('7273a1ea-0089-4674-b606-b1b8d809d866') const instanceId = ref('7273a1ea-0089-4674-b606-b1b8d809d866')
const quasar = useQuasar()
/** /**
* @typedef {{ * @typedef {{
* "instanceId": string, * "instanceId": string,
@ -112,6 +115,9 @@ function getMetricData() {
}) })
.then(r => { .then(r => {
const response = r.data const response = r.data
if (response.code != 0) {
throw response.message
}
const opts = [] const opts = []
const metricOptionsMap = new Map const metricOptionsMap = new Map
for (const selectedMetricElement of selectedMetric.value) { for (const selectedMetricElement of selectedMetric.value) {
@ -157,6 +163,16 @@ function getMetricData() {
options.value = opts options.value = opts
}) })
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
} }
getMetricData() getMetricData()
@ -179,6 +195,8 @@ getMetricData()
:option-label="opt => metricText[opt]" :option-label="opt => metricText[opt]"
label="想要查看的指标数据" label="想要查看的指标数据"
style="width: 35rem" style="width: 35rem"
transition-show="jump-up"
transition-hide="jump-up"
/> />
<q-separator vertical></q-separator> <q-separator vertical></q-separator>

@ -1,6 +1,10 @@
<script setup> <script setup>
import {reactive, ref} from "vue"; import {reactive, ref} from "vue";
import {api} from "boot/axios"; import {api} from "boot/axios";
import moment from "moment/moment";
import {useQuasar} from "quasar";
const quasar = useQuasar()
const addDialogPanelOpen = ref(false) const addDialogPanelOpen = ref(false)
@ -24,12 +28,33 @@ function getRules() {
const response = r.data const response = r.data
rules.value = response.data rules.value = response.data
}) })
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
} }
getRules() getRules()
function addRule() { function addRule() {
api.post('/panel/rules/add', ruleForm) api.post('/panel/rules/add', ruleForm)
.then(() => getRules()) .then(() => getRules())
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
} }
function deleteRule(id) { function deleteRule(id) {
@ -39,6 +64,32 @@ function deleteRule(id) {
} }
}) })
.then(() => getRules()) .then(() => getRules())
.catch(msg => {
quasar.dialog({
title: '啊哈',
message: `请求时出现问题:${msg}`
})
})
.finally(() => quasar.loading.hide())
quasar.loading.show({
message: '与服务器通信中...'
})
}
const deleteDialogOpen = ref(false)
const deleteId = ref()
const triggerTypeNames = {
"0": "立即计算",
"1": "定时计算",
}
const conditionNames = {
"0": "小于\t<",
"1": "小于等于\t<=",
"2": "等于\t==",
"3": "大于等于\t>=",
"4": "大于\t>",
} }
</script> </script>
@ -50,40 +101,47 @@ function deleteRule(id) {
<el-table class="h-full" :data="rules" border style="width: 100%;height: 100%"> <el-table class="h-full" :data="rules" border style="width: 100%;height: 100%">
<el-table-column fixed prop="id" label="ID"/> <el-table-column fixed prop="id" label="ID"/>
<el-table-column prop="expression" label="计算值"/> <el-table-column prop="expression" label="计算指标或表达式"/>
<el-table-column prop="condition" label="条件"/> <el-table-column label="判断条件">
<template #default="scope">
<span>{{ conditionNames[scope.row.condition] }}</span>
</template>
</el-table-column>
<el-table-column prop="threshold" label="阈值"/> <el-table-column prop="threshold" label="阈值"/>
<el-table-column prop="trigger" label="触发类型"/> <el-table-column label="触发类型">
<template #default="scope">
<span>{{ triggerTypeNames[scope.row.trigger] }}</span>
</template>
</el-table-column>
<el-table-column prop="description" label="备注"/> <el-table-column prop="description" label="备注"/>
<el-table-column prop="createTime" label="添加时间"/> <el-table-column label="时间">
<template #default="scope">
<span>{{ moment(scope.row.createTime).format("YYYY-MM-DD HH:mm:ss") }}</span>
</template>
</el-table-column>
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-button size="small" type="danger" @click="deleteRule(scope.row.id)">删除</el-button> <q-btn unelevated color="red" @click="deleteDialogOpen = true;deleteId = scope.row.id">删除</q-btn>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog v-model="addDialogPanelOpen" title="添加联系信息" width="500"> <el-dialog v-model="addDialogPanelOpen" title="添加联系信息" width="500">
<el-form :model="ruleForm"> <el-form :model="ruleForm">
<el-form-item label="值" > <el-form-item label="值">
<el-input v-model="ruleForm.expression" autocomplete="off" /> <el-input v-model="ruleForm.expression" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="条件"> <el-form-item label="条件">
<el-select v-model="ruleForm.condition" placeholder="选择判断条件"> <el-select v-model="ruleForm.condition" placeholder="选择判断条件">
<el-option label="<" value="0" /> <el-option v-for="(name, val) in conditionNames" :key="val" :label="name" :value="val"/>
<el-option label="<=" value="1" />
<el-option label="=" value="2" />
<el-option label=">=" value="3" />
<el-option label=">" value="4" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="阈值" > <el-form-item label="阈值">
<el-input v-model="ruleForm.threshold" autocomplete="off" /> <el-input v-model="ruleForm.threshold" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="触发类型"> <el-form-item label="触发类型">
<el-select v-model="ruleForm.trigger" placeholder="选择触发类型"> <el-select v-model="ruleForm.trigger" placeholder="选择触发类型">
<el-option label="立即计算" value="0" /> <el-option v-for="(name, val) in triggerTypeNames" :key="val" :label="name" :value="val"/>
<el-option label="定时计算" value="1" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="备注"> <el-form-item label="备注">
@ -98,6 +156,18 @@ function deleteRule(id) {
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="deleteDialogOpen" title="确认" width="500">
<span>确认删除</span>
<template #footer>
<div class="dialog-footer flex flex-row gap-2 justify-end">
<q-btn unelevated @click="deleteDialogOpen = false">取消</q-btn>
<q-btn unelevated color="red" @click="deleteDialogOpen = false;deleteRule(deleteId)">
确认
</q-btn>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>

@ -2455,6 +2455,11 @@ minimist@^1.2.6:
resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.0.tgz#b545f84af94e567386770159302ca113469c80b8" resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.0.tgz#b545f84af94e567386770159302ca113469c80b8"
integrity sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig== integrity sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==
moment@^2.30.1:
version "2.30.1"
resolved "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"

Loading…
Cancel
Save