Commit e071db8f authored by 张耀's avatar 张耀

feat:

预警管理指标配置接口对接
parent 72566ef3
......@@ -29,7 +29,9 @@ const add_form = ref(null);
const SaveSubmit = async () => {
let res = await add_form.value.Submit();
if (!res) return;
Save(res, { id, class_id });
Save(res, { id, class_id }, () => {
Cancle();
});
};
const Cancle = () => {
router.go(-1);
......@@ -43,7 +45,6 @@ const getInfoData = () => {
})
.then((res) => {
if (res.data.code == 200) {
console.log(res.data.data);
let res_d = res.data.data;
infoData.value = {
name: res_d.metric_name,
......
......@@ -11,96 +11,13 @@
</div>
</el-form-item>
<el-form-item label="预警范围" prop="">
<el-form
:model="state.form.warningScopeRows"
<warningScope
:indicator_expression="state.form.indicator_expression"
ref="table_form_ref"
:rules="state.tableRules"
label-width="0"
style="width: 100%">
<el-table
:data="state.form.warningScopeRows"
stripe
border
empty-text="请输入表达式,根据表达式$...$自动生成表格">
<el-table-column
v-for="header in warningScopeHeaders"
:prop="header.prop"
:key="header.prop"
:label="header.label"
:width="header.width">
<template #default="{ $index }">
<div v-if="header.prop == 'indicator_tag'">
<el-form-item
:prop="`[${$index}].input_indicator_tag`"
:rules="state.tableRules.input_indicator_tag">
<div class="indictor-tag">
<el-popover popper-class="cascader-operation" placement="bottom-start">
<template #reference>
<el-input
v-model="state.form.warningScopeRows[$index].input_indicator_tag"
placeholder="请选择指标标签"
@input="getDataTree"></el-input>
</template>
<div class="is-loading" v-show="isLoading">
<el-icon>
<Loading />
</el-icon>
<span>加载中</span>
</div>
<el-cascader-panel
v-show="!isLoading"
:options="dataTree"
:props="indicator_tag_props"
@change="(val) => handleChange(val, $index)" />
</el-popover>
</div>
</el-form-item>
</div>
<div v-else-if="header.prop == 'cname'">
<el-form-item :prop="`[${$index}].cname`" :rules="state.tableRules.cname">
<el-input
style="flex: 1"
v-model="state.form.warningScopeRows[$index].cname"
:maxlength="20"
show-word-limit
placeholder="请输入中文名称">
</el-input>
</el-form-item>
</div>
<div v-else-if="header.prop == 'is_required'">
<el-form-item :prop="`[${$index}].is_required`" :rules="state.tableRules.is_required">
<el-select
v-model="state.form.warningScopeRows[$index].is_required"
placeholder="请选择"
style="flex: 1">
<el-option v-for="(value, key) in tableSelOptions" :key="key" :label="value" :value="key">
</el-option>
</el-select>
</el-form-item>
</div>
<div v-else-if="header.prop == 'is_linkage'">
<el-form-item :prop="`[${$index}].is_linkage`" :rules="state.tableRules.is_linkage">
<el-select
v-model="state.form.warningScopeRows[$index].is_linkage"
placeholder="请选择"
style="flex: 1">
<el-option v-for="(value, key) in tableSelOptions" :key="key" :label="value" :value="key">
</el-option>
</el-select>
</el-form-item>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
:warningScopeRows="warningScopeRows" />
</el-form-item>
<el-form-item label="预警规则类型" prop="rule_type">
<el-select
style="flex: 1"
v-model="state.form.rule_type"
placeholder="请选择"
filterable
@change="changeRuleType">
<el-select style="flex: 1" v-model="state.form.rule_type" placeholder="请选择" filterable>
<el-option v-for="(value, key) in ruleTypeOptions" :key="key" :label="value" :value="key"> </el-option>
</el-select>
</el-form-item>
......@@ -137,28 +54,36 @@
</template>
<script setup>
import { reactive, ref, shallowReactive, computed, nextTick, watch } from "vue";
import { reactive, ref, computed, watch, onMounted } from "vue";
import gapTitle from "@/components/gap-title.vue";
import ManualDistributionForm from "@/components/manual-distribution/form.vue";
import { MAX_DAY, ONLY_INPUT_NUM } from "@/components/env.js";
import { ElMessage } from "element-plus";
import axios from "@/request/http.js";
import warningScope from "./warning-scope.vue";
const props = defineProps({
row: {
type: Object,
default: null,
},
});
const tableSelOptions = ["", ""];
const ruleTypeOptions = {
empty: "",
"9f1e6170-65e8-4e14-9c17-6a7b87a900a7": "百分比范围",
2: "毫秒范围",
3: "秒范围",
4: "个范围",
5: "温度范围",
// 预警规则类型下拉
const ruleTypeOptions = ref({});
const getRuleTypeOptions = () => {
ruleTypeOptions.value = {
empty: "",
1: "百分比范围",
2: "毫秒范围",
3: "秒范围",
4: "个范围",
5: "温度范围",
};
};
// 当前是否是编辑
const isEdit = computed(() => !!props.row);
const history = computed(() => props.row?.manual_distribution_form || null);
const typrFormData = computed(() => props.row?.type_com_ref || null);
// 获取旧数据
const warningScopeRows = computed(() => props.row?.warningScopeRows || []);
// 持续时间单位
const unitOptions = {
s: { label: "", max: MAX_DAY * 24 * 3600 },
m: { label: "分钟", max: MAX_DAY * 24 * 60 },
......@@ -172,7 +97,6 @@ const state = reactive({
time: 10,
unit: "s",
inspection_cycle: 1,
warningScopeRows: [],
state: 1,
},
rules: {
......@@ -180,124 +104,8 @@ const state = reactive({
indicator_expression: [{ required: true, message: "请输入指标表达式", trigger: "blur" }],
time: [{ required: true, message: "请输入持续时间", trigger: "blur" }],
},
tableRules: {
input_indicator_tag: [{ required: true, message: "请选择", trigger: "blur" }],
cname: [{ required: true, message: "请输入", trigger: "blur" }],
},
});
const warningScopeHeaders = [
{
prop: "key",
label: "变量名称",
width: 100,
},
{
prop: "indicator_tag",
label: "指标标签",
},
{
prop: "cname",
label: "中文名称",
},
{
prop: "is_required",
label: "是否必填",
width: 150,
},
{
prop: "is_linkage",
label: "是否联动",
width: 150,
},
];
const dataTree = ref([]);
const isLoading = ref(false);
const getDataTree = () => {
isLoading.value = true;
setTimeout(() => {
const res = {
code: 200,
msg: "OK",
data: [
{
name: "http_requests_total",
children: [
{
name: "instance",
value: "localhost:2023",
},
{
name: "job",
value: "prometheus",
},
{
name: "method",
value: "GET",
},
],
},
{
name: "system_monitor_counter",
children: [
{
name: "instance",
value: "localhost:2023",
},
{
name: "job",
value: "prometheus",
},
{
name: "system_id",
value: "YW230177",
},
{
name: "user_id",
value: "xc-admin",
},
],
},
],
};
dataTree.value = res.data;
isLoading.value = false;
}, 2000);
};
const handleChange = async (val, index) => {
state.form.warningScopeRows[index].indicator_scope = val[0];
state.form.warningScopeRows[index].input_indicator_tag = val[1];
state.form.warningScopeRows[index].indicator_tag = val[1];
};
const indicator_tag_props = {
value: "name",
label: "name",
};
const tableCreateKeys = ref([]);
watch(
() => state.form.indicator_expression,
(n) => {
tableCreateKeys.value = n?.match(/\$(.+?)\$/g) || [];
let arr = [];
tableCreateKeys.value.forEach((e) => {
let i = state.form.warningScopeRows.findIndex((el) => el.key == e);
arr.push(
i != -1
? state.form.warningScopeRows[i]
: {
key: e,
input_indicator_tag: "",
indicator_scope: "",
indicator_tag: "",
cname: "",
is_required: 1,
is_linkage: 0,
}
);
});
state.form.warningScopeRows = arr;
}
);
// 当预警持续输入限制
const inputNum = () => {
state.form.time = state.form.time.replace(/[^\d]/g, "");
let time = +state.form.time;
......@@ -306,40 +114,31 @@ const inputNum = () => {
state.form.time = max;
}
};
// 检查周期下拉数据
const inspectionCycleOptions = ref([1, 3, 5, 10, 20]);
// form表单元素
const form_ref = ref(null);
// table的form表单元素
const table_form_ref = ref(null);
// 提交调用
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
let table_form_valid = await new Promise((resolve, reject) => {
table_form_ref.value.validate((res) => resolve(res));
});
let table_form_valid = await table_form_ref.value.Submit();
if (form_valid && table_form_valid) {
return {
...state.form,
...table_form_ref.value?.form,
};
}
return;
};
const changeRuleType = () => {};
// 监听是否是编辑,是则插入原数据
watch(
() => props.row,
async (n) => {
if (!n) return;
state.form.warningScopeRows =
n.warningScopeRows?.map((e) => {
return {
key: e.key,
input_indicator_tag: e.input_indicator_tag,
indicator_scope: e.indicator_scope,
indicator_tag: e.indicator_tag,
cname: e.cname,
is_required: e.is_required || 1,
is_linkage: e.is_linkage || 0,
};
}) || [];
state.form.name = n.name;
state.form.rule_type = n.rule_type || "empty";
state.form.inspection_cycle = n.inspection_cycle || 1;
......@@ -350,6 +149,10 @@ watch(
},
{ deep: true, immediate: true }
);
// 获取第一级指标范围列表
onMounted(() => {
getRuleTypeOptions();
});
defineExpose({
Submit,
});
......@@ -385,6 +188,7 @@ defineExpose({
}
.no-el-label {
:deep(.el-form-item__content) {
margin-left: 0 !important;
.el-select {
width: 80px;
}
......@@ -403,21 +207,6 @@ defineExpose({
}
}
}
.no-el-label {
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
}
.manual-distribution-form {
:deep(.el-form-item) {
&:not(:last-child) {
margin-bottom: 18px;
}
}
}
.indictor-tag {
width: 100%;
}
</style>
<style lang="scss">
.el-form-item {
......@@ -430,40 +219,4 @@ defineExpose({
min-height: 34px;
}
}
.cascader-operation {
width: auto !important;
padding: 0 !important;
min-width: 180px !important;
position: relative;
min-height: 204px;
.is-loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
svg {
animation: rotate 3s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
.el-scrollbar {
border-radius: 0;
}
.el-cascader-panel {
border: 0 !important;
box-shadow: none !important;
}
}
</style>
......@@ -2,7 +2,7 @@ import { ElMessage } from "element-plus";
import axios from "@/request/http.js";
const setParams = (res, { id, class_id }) => {
let params = {
class_id,
class_id: +class_id,
metric_name: res.name,
expr: res.indicator_expression,
alert_range: res.warningScopeRows.map((e) => {
......
<template>
<el-form :model="state.form.warningScopeRows" ref="form_ref" :rules="tableRules" label-width="0" style="width: 100%">
<el-table :data="state.form.warningScopeRows" stripe border empty-text="请输入表达式,根据表达式$...$自动生成表格">
<el-table-column
v-for="header in warningScopeHeaders"
:prop="header.prop"
:key="header.prop"
:label="header.label"
:width="header.width">
<template #default="{ $index }">
<div v-if="header.prop == 'indicator_tag'">
<el-form-item :prop="`[${$index}].input_indicator_tag`" :rules="tableRules.input_indicator_tag">
<div class="indictor-tag">
<el-popover
:visible="state.form.warningScopeRows[$index].visible"
popper-class="cascader-operation"
placement="bottom-start">
<template #reference>
<el-input
v-model="state.form.warningScopeRows[$index].input_indicator_tag"
placeholder="请选择指标标签"
:ref="
(i_el) => {
inputRef[$index] = i_el;
}
"
@blur="blurInput($index)"
@focus="inputLabel($index, false)"
@input="inputLabel($index, true)"></el-input>
</template>
<div class="filter-first-tree-lists">
<ul class="bg-scroll" v-show="dataTree($index).length > 0">
<li
v-for="data in dataTree($index)"
:key="data"
:class="{ active: state.form.warningScopeRows[$index].indicator_scope == data.label }">
<div class="first-label-main" @click="chooseTreeFirst($index, data)">
<div class="tree-label">{{ data.label }}</div>
<div class="tree-icon">
<bg-icon
style="font-size: 16px; color: #404a62"
class="is-loading"
:class="{ 'loading-show': data.isLoading }"
icon="#bg-ic-loading"></bg-icon>
<bg-icon style="font-size: 14px; color: #404a62" icon="#bg-ic-arrow-right"></bg-icon>
</div>
</div>
</li>
</ul>
<ul v-show="dataTree($index).length == 0">
<li class="no-data">未查询到相关指标范围数据</li>
</ul>
<ul class="bg-scroll" v-show="state.form.warningScopeRows[$index].lastOptions?.length > 0">
<li
v-for="child in state.form.warningScopeRows[$index].lastOptions"
:key="child"
@click="chooseTreeLast(child, $index, false)"
:class="{ active: state.form.warningScopeRows[$index].indicator_tag == child }">
{{ child }}
</li>
</ul>
</div>
</el-popover>
</div>
</el-form-item>
</div>
<div v-else-if="header.prop == 'cname'">
<el-form-item :prop="`[${$index}].cname`" :rules="tableRules.cname">
<el-input
style="flex: 1"
v-model="state.form.warningScopeRows[$index].cname"
:maxlength="20"
show-word-limit
placeholder="请输入中文名称">
</el-input>
</el-form-item>
</div>
<div v-else-if="header.prop == 'is_required'">
<el-form-item :prop="`[${$index}].is_required`" :rules="tableRules.is_required">
<el-select v-model="state.form.warningScopeRows[$index].is_required" placeholder="请选择" style="flex: 1">
<el-option v-for="(value, key) in tableSelOptions" :key="key" :label="value" :value="key"> </el-option>
</el-select>
</el-form-item>
</div>
<div v-else-if="header.prop == 'is_linkage'">
<el-form-item :prop="`[${$index}].is_linkage`" :rules="tableRules.is_linkage">
<el-select v-model="state.form.warningScopeRows[$index].is_linkage" placeholder="请选择" style="flex: 1">
<el-option v-for="(value, key) in tableSelOptions" :key="key" :label="value" :value="key"> </el-option>
</el-select>
</el-form-item>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</template>
<script setup>
import { reactive, watch, nextTick, computed, ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import axios from "@/request/http.js";
const props = defineProps({
indicator_expression: {
type: String,
default: "",
},
warningScopeRows: {
type: Array,
default: () => [],
},
});
// 是否是必填和是否联动下拉数据
const tableSelOptions = ["", ""];
// 表格表头
const warningScopeHeaders = [
{
prop: "key",
label: "变量名称",
width: 100,
},
{
prop: "indicator_tag",
label: "指标标签",
},
{
prop: "cname",
label: "中文名称",
},
{
prop: "is_required",
label: "是否必填",
width: 150,
},
{
prop: "is_linkage",
label: "是否联动",
width: 150,
},
];
// 表单数据
const state = reactive({
form: {
warningScopeRows: [],
},
});
// 表单验证
const tableRules = {
input_indicator_tag: [{ required: true, message: "请输入", trigger: "blur" }],
cname: [{ required: true, message: "请输入", trigger: "blur" }],
};
// 获取到的第一级指标范围基础数据集合
const dataTreeDefault = ref([]);
// 是否是输入触发
const isInput = ref(false);
// 当前操作的表格下标
const activeIndex = ref(-1);
// 指标输入框元素集合
const inputRef = ref([]);
// 下拉数据处理
const dataTree = computed(() => {
return (index) => {
const { firstOptions, input_indicator_tag, oldQuery } = state.form.warningScopeRows[index];
let query = isInput.value ? input_indicator_tag : oldQuery;
if (!query) return firstOptions;
let arr = firstOptions.filter((e) => e.label.includes(query));
if (arr == "") {
state.form.warningScopeRows[index].lastOptions = [];
state.form.warningScopeRows[index].indicator_scope = "";
}
return arr;
};
});
// 监听父组件传过来的指标表达式
watch(
() => props.indicator_expression,
async (n) => {
await nextTick();
let tableCreateKeys = n?.match(/\$(.+?)\$/g) || [];
let arr = [];
tableCreateKeys.forEach((e) => {
let i = state.form.warningScopeRows.findIndex((el) => el.key == e);
if (i >= 0) {
arr[i] = state.form.warningScopeRows[i];
} else {
arr.push({
key: e,
input_indicator_tag: "",
indicator_scope: "",
indicator_tag: "",
oldQuery: "",
firstOptions: dataTreeDefault.value,
lastOptions: [],
cname: "",
visible: false,
is_required: 1,
is_linkage: 0,
});
}
});
state.form.warningScopeRows = arr;
}
);
// 监听编辑的原数据,并显示
watch(
() => props.warningScopeRows,
(n) => {
state.form.warningScopeRows =
n?.map((e) => {
return {
key: e.key,
input_indicator_tag: e.input_indicator_tag,
indicator_scope: e.indicator_scope,
indicator_tag: e.indicator_tag,
firstOptions: dataTreeDefault.value,
lastOptions: [],
oldQuery: "",
visible: false,
cname: e.cname,
is_required: e.is_required,
is_linkage: e.is_linkage,
};
}) || [];
},
{
deep: true,
immediate: true,
}
);
// 选择第一级,获取第二级数据
const chooseTreeFirst = (index, data) => {
inputRef.value[index].focus();
state.form.warningScopeRows[activeIndex.value].indicator_tag = "";
state.form.warningScopeRows[index].indicator_scope = data.label;
data.isLoading = true;
state.form.warningScopeRows[index].lastOptions = [];
const params = {
label_name: data.label,
};
axios
.get("/v1/api/prometheus", { params })
.then((res) => {
if (res.data.code == 200) {
state.form.warningScopeRows[index].lastOptions = res.data.data?.list || [];
} else {
ElMessage.error(res.data.msg);
}
})
.finally(() => {
data.isLoading = false;
});
};
// 获取第一级数据
const getDataTreeLevelFirst = () => {
axios.get("/v1/api/prometheus").then((res) => {
if (res.data.code == 200) {
dataTreeDefault.value =
res.data.data.list.map((e) => {
return {
label: e,
isLoading: false,
};
}) || [];
} else {
ElMessage.error(res.data.msg);
}
});
};
let timer = null;
// 输入模糊查询数据
const inputLabel = (index, type) => {
state.form.warningScopeRows[index].visible = true;
activeIndex.value = index;
isInput.value = type;
if (state.form.warningScopeRows[index].input_indicator_tag == "") {
state.form.warningScopeRows[index].lastOptions = [];
}
// 用户输入节流
timer && clearTimeout(timer);
timer = setTimeout(() => {
if (type) {
state.form.warningScopeRows[index].oldQuery = state.form.warningScopeRows[index].input_indicator_tag;
state.form.warningScopeRows[index].indicator_scope = "";
state.form.warningScopeRows[index].indicator_tag = "";
state.form.warningScopeRows[index].lastOptions = [];
}
}, 200);
};
// 失去焦点是隐藏popper
const blurInput = (index) => {
state.form.warningScopeRows[index].visible = false;
};
// 选择范围中的属性
const chooseTreeLast = (last, index, type) => {
isInput.value = type;
state.form.warningScopeRows[activeIndex.value].input_indicator_tag = last;
state.form.warningScopeRows[activeIndex.value].indicator_tag = last;
};
// form表单元素
const form_ref = ref(null);
// 提交操作调用
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
let isFull = true;
try {
state.form.warningScopeRows.forEach((e, i) => {
if (e.indicator_scope == "" || e.indicator_tag == "") {
throw new Error(`第${i}条数据的指标没有选择!`);
}
});
} catch (e) {
if (e) {
ElMessage.error(e);
isFull = false;
}
}
return form_valid && isFull;
};
// 获取第一级指标范围列表
onMounted(() => {
getDataTreeLevelFirst();
});
defineExpose({
Submit,
form: state.form,
form_ref,
});
</script>
<style lang="scss" scoped>
.indictor-tag {
width: 100%;
}
</style>
<style lang="scss">
.cascader-operation {
width: auto !important;
padding: 0 !important;
min-width: 180px !important;
position: relative;
min-height: 204px;
.el-scrollbar {
border-radius: 0;
}
.filter-first-tree-lists {
height: 240px;
display: flex;
ul {
min-width: 180px;
height: 100%;
&:not(:first-child) {
border-left: 1px solid #ddd;
}
li {
padding-left: 20px;
line-height: 34px;
max-width: 400px;
word-break: break-all;
transition: all 300ms;
&:hover {
background-color: #f1f1f1;
}
cursor: pointer;
&.active {
font-weight: bold;
background-color: #f1f1f1;
}
}
}
.first-label-main {
padding-right: 48px;
position: relative;
.tree-label {
width: 100%;
}
.tree-icon {
width: 40px;
height: 100%;
position: absolute;
right: 0;
top: 0;
display: flex;
align-items: center;
svg {
width: 20px;
}
.is-loading {
animation: rotate 3s linear infinite;
opacity: 0;
&.loading-show {
opacity: 1;
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
}
.no-data {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: gray;
box-sizing: border-box;
padding: 0 20px;
}
}
}
</style>
......@@ -155,26 +155,24 @@
v-model="closeWarningDialog"
width="400px"
:before-close="cancelCloseWarningDialog">
<div style="padding-top: 20px">
<el-form ref="closeForm" :model="closeFormData" :rules="closeRules" label-width="80px" class="bg_form">
<el-form-item label="关闭备注" prop="close_notes">
<el-input
v-model="closeFormData.close_notes"
type="textarea"
:autosize="{ minRows: 2 }"
show-word-limit
maxlength="30"
resize="vertical"
placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item label="" class="no-el-label" prop="close_remind" style="margin-bottom: 0px">
<el-checkbox v-model="closeFormData.close_remind">
<div>三天内将不再自动推送该告警信息给处置人员,</div>
<div>可手动推送,但告警数据依然会出现</div>
</el-checkbox>
</el-form-item>
</el-form>
</div>
<el-form ref="closeForm" :model="closeFormData" :rules="closeRules" label-width="80px" class="bg_form">
<el-form-item label="关闭备注" prop="close_notes">
<el-input
v-model="closeFormData.close_notes"
type="textarea"
:autosize="{ minRows: 2 }"
show-word-limit
maxlength="30"
resize="vertical"
placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item label="" class="no-el-label" prop="close_remind" style="margin-bottom: 0px">
<el-checkbox v-model="closeFormData.close_remind">
<div>三天内将不再自动推送该告警信息给处置人员,</div>
<div>可手动推送,但告警数据依然会出现</div>
</el-checkbox>
</el-form-item>
</el-form>
<template v-slot:footer>
<div class="apaas_button">
<el-button type="default" @click="cancelCloseWarningDialog">取消</el-button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment