Commit 302fb2f2 authored by 张耀's avatar 张耀

feat:

预警管理静态页面开发
parent e849bae2
...@@ -82,20 +82,28 @@ const states = reactive({ ...@@ -82,20 +82,28 @@ const states = reactive({
const codeChange = () => { const codeChange = () => {
emit("update:modelValue", states.content); emit("update:modelValue", states.content);
}; };
const init = (n) => {
onMounted(() => {
let obj = ""; let obj = "";
// console.log(typeof JSON.parse(this.datas)); // console.log(typeof JSON.parse(this.datas));
try { try {
if (typeof JSON.parse(props.modelValue) == "object") { if (typeof JSON.parse(n) == "object") {
obj = JSON.stringify(JSON.parse(props.modelValue), null, "\t"); obj = JSON.stringify(JSON.parse(n), null, "\t");
} else { } else {
obj = props.modelValue; obj = n;
} }
} catch (e) { } catch (e) {
obj = props.modelValue; obj = n;
} }
states.content = obj; states.content = obj;
};
watch(
() => props.modelValue,
(n) => {
init(n);
}
);
onMounted(() => {
init(props.modelValue);
}); });
const { content } = toRefs(states); const { content } = toRefs(states);
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<!-- 序号 --> <!-- 序号 -->
</el-table-column> </el-table-column>
<el-table-column <el-table-column
v-for="(header) in headers" v-for="header in headers"
:width="header.width" :width="header.width"
:min-width="header.minWidth" :min-width="header.minWidth"
:align="header.align" :align="header.align"
...@@ -52,15 +52,8 @@ ...@@ -52,15 +52,8 @@
<script setup> <script setup>
import { watch, ref, nextTick } from "vue"; import { watch, ref, nextTick } from "vue";
import { selectTableMixin } from "./hook/mixin-select-table"; import { selectTableMixin } from "./hook/mixin-select-table";
const { const { nowSelectData, allSelectData, initAllSelectData, selectData, initSelectTableData, runPage, dealSelectData } =
nowSelectData, selectTableMixin();
allSelectData,
initAllSelectData,
selectData,
initSelectTableData,
runPage,
dealSelectData,
} = selectTableMixin();
const props = defineProps({ const props = defineProps({
height: { height: {
...@@ -112,6 +105,11 @@ const props = defineProps({ ...@@ -112,6 +105,11 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
// 自定义返回值用来决定这一行的 CheckBox 是否可以勾选
selectable: {
type: Function,
default: null,
},
}); });
const table = ref(null); const table = ref(null);
...@@ -198,6 +196,10 @@ const tableRowClassName = ({ row, rowIndex }) => { ...@@ -198,6 +196,10 @@ const tableRowClassName = ({ row, rowIndex }) => {
} }
}; };
const selectable = (row, index) => { const selectable = (row, index) => {
// 判断是否传入自定义返回值用来决定这一行的 CheckBox 是否可以勾选,否则使用默认勾选逻辑
if (props.selectable) {
return props.selectable(row, index);
}
if (props.canEdit) { if (props.canEdit) {
if (row[props.canEditFlag] && row[props.canEditFlag] == 1) { if (row[props.canEditFlag] && row[props.canEditFlag] == 1) {
return false; return false;
......
...@@ -3,3 +3,7 @@ export const TIMEING_RULES = { ...@@ -3,3 +3,7 @@ export const TIMEING_RULES = {
2: '按周', 2: '按周',
3: '自定义时间' 3: '自定义时间'
} }
export const MAX_DAY = 7;
export const ONLY_INPUT_NUM = (value) => {
return value.replace(/[^\d]/g, '')
}
\ No newline at end of file
...@@ -268,7 +268,6 @@ defineExpose({ ...@@ -268,7 +268,6 @@ defineExpose({
} }
} }
.user-table { .user-table {
max-height: 345px;
:deep(.el-table thead th) { :deep(.el-table thead th) {
background-color: #f5f6f9; background-color: #f5f6f9;
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div> <div>
<div class="info-list" v-for="(list, index) in labelData" :key="`warn-${index}`"> <div class="info-list" v-for="(list, index) in labelData" :key="`warn-${index}`">
<div class="info-item" v-for="(item, i) in list" :key="`warn-${index}-${i}`"> <div class="info-item" v-for="(item, i) in list" :key="`warn-${index}-${i}`">
<div class="label"> <div class="label" :title="item.label">
{{ item.label }} {{ item.label }}
</div> </div>
<div class="value"> <div class="value">
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<span class="status" :class="`status-${valueData.status}`"></span> <span class="status" :class="`status-${valueData.status}`"></span>
<span>{{ STATUS_OBJ[valueData[item.prop]] }}</span> <span>{{ STATUS_OBJ[valueData[item.prop]] }}</span>
</span> </span>
<span v-else>{{ valueData[item.prop] }}</span> <span v-else :title="valueData[item.prop]">{{ valueData[item.prop] }}</span>
</span> </span>
</div> </div>
</div> </div>
...@@ -60,7 +60,7 @@ const status_obj = computed(() => { ...@@ -60,7 +60,7 @@ const status_obj = computed(() => {
align-items: center; align-items: center;
flex: 1; flex: 1;
height: 48px; height: 48px;
line-height: 46px; line-height: 48px;
font-size: 14px; font-size: 14px;
color: #404a62; color: #404a62;
.label { .label {
......
<template>
<div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="main_container">
<div class="add-form-main bg-scroll">
<add-form ref="add_form"></add-form>
</div>
<div class="add-btns">
<el-button size="default" @click="Cancle">取消</el-button>
<el-button type="primary" size="default" @click="SaveSubmit">保存</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import addForm from "../modules/add-form.vue";
import { Save } from "../modules/interface.js";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const { id, class_id } = route.query;
const add_form = ref(null);
const params = {};
const SaveSubmit = async () => {
let res = await add_form.value.Submit();
if (!res) return;
Save(res);
};
const Cancle = () => {
router.go(-1);
};
</script>
<style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px 16px;
min-height: 100%;
.main_container {
height: calc(100% - 46px);
padding: 0;
.add-form-main {
padding: 24px 24px 16px;
height: calc(100% - 68px);
}
.add-btns {
height: 68px;
display: flex;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #ddd;
padding: 0 24px;
}
}
}
</style>
<template>
<div>指标配置详情</div>
</template>
<script setup></script>
<style lang="scss" scoped></style>
<template>
<div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="main_container">
<div class="add-form-main bg-scroll">
<add-form ref="add_form" :row="infoData"></add-form>
</div>
<div class="add-btns">
<el-button size="default" @click="Cancle">取消</el-button>
<el-button type="primary" size="default" @click="SaveSubmit">保存</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { onBeforeMount, ref } from "vue";
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import addForm from "../modules/add-form.vue";
import { Save, URL } from "../modules/interface.js";
import { ElMessage } from "element-plus";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const { id, class_id } = route.query;
const infoData = ref({});
const add_form = ref(null);
const SaveSubmit = async () => {
let res = await add_form.value.Submit();
if (!res) return;
Save(res, { id, class_id });
};
const Cancle = () => {
router.go(-1);
};
const getInfoData = () => {
// axios
// .get(URL, {
// params: {
// id: id,
// },
// })
// .then((res) => {
// if (res.data.code == 200) {
// console.log(res.data.data);
// } else {
// ElMessage.error(res.data.data);
// }
// });
let res = {
id: "20da8f87-628a-4f0c-bd9c-0ad176d18d59",
class_id: 101,
metric_name: "xx请求次数告警",
expr: 'shttp_requests_total{method="GET",$pod$}',
duration: 5,
duration_unit: "m",
check_period: 3,
is_enabled: 1,
alert_rule_type: "9f1e6170-65e8-4e14-9c17-6a7b87a900a7",
created_by: "",
created_at: "2023-06-28 17:28:29",
updated_by: "",
updated_at: "2023-06-28 17:54:33",
alert_range: [
{
variable_name: "$pod$",
metric_name: "shttp_requests_total",
metric_label: "pod",
chinese_name: "demoString",
is_required: true,
is_linked: true,
},
],
};
infoData.value = {
name: res.metric_name,
indicator_expression: res.expr,
rule_type: res.alert_rule_type,
time: res.duration,
unit: res.duration_unit,
inspection_cycle: res.check_period,
warningScopeRows:
res.alert_range.map((e) => {
return {
key: e.variable_name,
input_indicator_tag: e.metric_label,
indicator_scope: e.metric_name,
indicator_tag: e.metric_label,
cname: e.chinese_name,
is_required: e.is_required ? 1 : 0,
is_linkage: e.is_linked ? 1 : 0,
};
}) || [],
state: res.is_enabled,
res: res,
};
};
onBeforeMount(() => {
getInfoData();
});
</script>
<style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px 16px;
min-height: 100%;
.main_container {
height: calc(100% - 46px);
padding: 0;
.add-form-main {
padding: 24px;
height: calc(100% - 68px);
}
.add-btns {
height: 68px;
display: flex;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #ddd;
padding: 0 24px;
}
}
}
</style>
<template>
<div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="detail_container-main">
<Slide v-model="node" />
<div class="main-content">
<bg-filter-group @search="changeSearch" v-model="filter.search" placeholder="请输入指标名称">
<template v-slot:left_action>
<div class="apaas_button">
<el-button type="primary" @click="addRule">
<bg-icon style="font-size: 12px; color: #fff; margin-right: 8px" icon="#bg-ic-add"></bg-icon>
新增
</el-button>
<el-button type="default" @click="batchDelete">批量删除</el-button>
<span class="header_info">
已选择 <span style="color: #202531; font-weight: bold"> {{ state.selected.length }} </span>
</span>
<span class="header_info can_click_text" @click="clearSelected">清空</span>
</div>
</template>
<template v-slot:filter_group>
<div class="left-filter filter_list">
<div class="filter_item">
<span class="filter_title">启用状态</span>
<el-select v-model="filter.state" placeholder="请选择" style="width: 300px">
<el-option
v-for="(item, index) in stateOptions"
:key="'stateOptions' + index"
:label="item.name"
:value="item.value">
</el-option>
</el-select>
</div>
<div class="filter_item">
<span class="filter_title">创建时间</span>
<el-date-picker
style="width: 400px"
v-model="filter.time"
type="datetimerange"
value-format="yyyy-MM-DD HH:mm:ss"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间" />
</div>
</div>
<div class="right-action apaas_button">
<el-button type="primary" @click="filterAction"> 查询 </el-button>
<el-button type="default" @click="filterClear"> 重置 </el-button>
</div>
</template>
</bg-filter-group>
<div class="table_container">
<div class="table bg-scroll">
<bg-table
ref="dataTable"
:headers="headers"
:rows="tableRows"
@selectAc="selectRows"
:selectable="selectable"
:isIndex="true"
:select="true"
:stripe="true">
<template v-slot:indicator_name="{ row }">
<span class="can_click_text" @click="goDetail(row)">
{{ row.indicator_name }}
</span>
</template>
<template #state="{ row }">
<bg-switch
@click="stateChange(row)"
:labels="['否', '是']"
:values="[0, 1]"
v-model="row.state"></bg-switch>
</template>
<template v-slot:created_time="{ row }">
{{ row.created_time ? row.created_time.split("+")[0].replace("T", " ").replace("Z", " ") : "-" }}
</template>
<template v-slot:action="{ row }">
<bg-table-btns2 :limit="3" :tableData="tableRows">
<bg-table-btn :disabled="row.state != 0" @click="editRow(row)">编辑</bg-table-btn>
<bg-table-btn :disabled="row.state != 0" @click="deleteRow(row)">删除</bg-table-btn>
</bg-table-btns2>
</template>
</bg-table>
</div>
<bg-pagination
:page="filter.page"
:size="filter.size"
:total="tableTotal"
@change-page="changePage"
@change-size="changeSize">
</bg-pagination>
</div>
</div>
</div>
<!-- 删除 -->
<el-dialog class="dialog_box" title="删除" v-model="delDialog" width="420px">
<div style="padding: 20px 0">确定要删除吗?</div>
<template v-slot:footer>
<div class="apaas_button">
<el-button type="default" @click="delDialog = false">取消</el-button>
<el-button type="primary" @click="delConfirm">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import { reactive, ref, onBeforeMount, toRefs, computed, watch, nextTick, watchEffect } from "vue";
import Slide from "./modules/slide.vue";
import { ElMessage } from "element-plus";
import axios from "@/request/http.js";
import { useRouter } from "vue-router";
const router = useRouter();
const node = ref({});
watch(
() => node.value,
(n) => {
console.log(n);
},
{ deep: true }
);
const dataTable = ref(null);
const state = reactive({
stateOptions: [
{
name: "全部",
value: "",
},
{
name: "启用",
value: 1,
},
{
name: "停用",
value: 2,
},
], // 状态
headers: [
{
label: "指标名称",
prop: "indicator_name",
width: 200,
},
{
label: "是否启用",
prop: "state",
},
{
label: "创建人",
prop: "created_user",
},
{
label: "创建时间",
prop: "created_time",
width: 160,
},
{
label: "操作",
prop: "action",
width: 136,
fixed: "right",
},
],
filter: {
notice_method: "", // 通知方式
state: "", // 状态
time: [],
search: "",
page: 1,
limit: 10,
},
tableRows: [], // 表格数据
selected: [], //选择数据
tableTotal: 0, // 表格数据条数
actionRow: null, // 当前操作的数据
delDialog: false, // 删除弹窗
delType: 0, // 1-单条删除 2-批量删除
});
const changePage = (page) => {
state.filter.page = page;
getTableRows();
}; // 改变页码
const changeSize = (size) => {
state.filter.limit = size;
changePage(1);
}; // 改变每页条数
const changeSearch = (val) => {
state.filter.search = val;
changePage(1);
};
const filterAction = () => {
changePage(1);
};
const filterClear = () => {
state.filter = {
state: "", // 状态
time: [],
search: "",
page: 1,
limit: 10,
};
changePage(1);
};
const selectable = (row, index) => {
return row.state === 0;
};
const getTableRows = () => {
let params = { ...state.filter };
state.tableTotal = 23;
state.tableRows = [
{
id: 1,
indicator_name: "磁盘使用率",
state: 1,
created_user: "李四",
created_time: "2020-01-01 00:00:00",
},
{
id: 2,
indicator_name: "磁盘使用率",
state: 0,
created_user: "李四",
created_time: "2020-01-01 00:00:00",
},
{
id: 3,
indicator_name: "磁盘使用率",
state: 1,
created_user: "李四",
created_time: "2020-01-01 00:00:00",
},
];
};
const goDetail = (row) => {
router.push({
path: "/forewarning/rule-set/detail",
query: {
id: row.id,
class_id: node.value.data.id,
},
});
}; // 查看详情
const selectRows = (data) => {
state.selected = data.selection;
};
const deleteRow = (row) => {
state.actionRow = row;
state.delType = 1;
state.delDialog = true;
console.log("删除");
}; // 删除
const batchDelete = () => {
console.log("批量删除");
if (!state.selected || state.selected.length == 0) {
ElMessage.error("请先勾选要删除的数据");
return;
}
state.delType = 2;
state.delDialog = true;
}; // 批量删除
const delConfirm = () => {
let ids = [];
if (state.delType == 1) {
ids.push(state.actionRow.id);
} else {
ids = state.selected.map((e) => {
return e.id;
});
}
console.log(ids);
state.delDialog = false;
setTimeout(() => {
clearSelected();
changePage(1);
}, 200);
}; // 确定删除
const clearSelected = () => {
dataTable.value.clearTable();
}; // 清空
const addRule = () => {
console.log("新增");
router.push({
path: `/forewarning/indicator-config/add`,
query: {
class_id: node.value.data.id,
},
});
}; // 新增规则
const editRow = (row) => {
console.log("编辑");
router.push({
path: `/forewarning/indicator-config/edit`,
query: {
id: row.id,
class_id: node.value.data.id,
},
});
}; // 编辑
const stateChange = (row) => {
console.log("更改状态");
// axios
// .put(`/xxx/xxx?id=${row.id}&state=${row.state}`)
// .then((res) => {
// if (res.data.code == 200) {
// ElMessage.success(res.data.msg);
// changePage(1);
// } else {
// ElMessage.error(res.data.data);
// row.state = row.state == 0 ? 1 : 0;
// }
// });
};
onBeforeMount(() => {
getTableRows();
});
const { headers, tableRows, tableTotal, filter, noticeTypes, stateOptions, delDialog } = toRefs(state);
</script>
<style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px;
min-height: 100%;
&-main {
width: 100%;
height: calc(100% - 46px);
display: flex;
align-items: center;
padding-bottom: 16px;
}
.main-content {
height: 100%;
width: calc(100% - 336px);
background-color: #fff;
box-shadow: 0 1px 4px 0 rgba(0, 7, 101, 0.15);
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
.filter-group {
.left-filter {
flex: 1;
display: flex;
justify-content: start;
flex-wrap: wrap;
}
.right-action {
width: 144px;
padding-bottom: 16px;
.el-button {
width: 64px;
}
}
}
.table_container {
flex: 1;
width: 100%;
padding: 0 16px;
position: relative;
.table {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100% - 64px);
}
.bg-pagination {
height: 64px;
position: absolute;
right: 0;
bottom: 0;
padding: 16px;
margin-top: 0;
}
}
}
}
</style>
<template>
<div class="add-form">
<el-form :model="state.form" ref="form_ref" :rules="state.rules" label-width="100px">
<div class="add-form-item">
<el-form-item label="指标名称" prop="name">
<el-input v-model="state.form.name" :disabled="isEdit" placeholder="请输入指标名称"></el-input>
</el-form-item>
<el-form-item label="指标表达式" prop="indicator_expression">
<div class="indicator-expression">
<bg-code-editor v-model="state.form.indicator_expression"></bg-code-editor>
</div>
</el-form-item>
<el-form-item label="预警范围" prop="">
<el-form
:model="state.form.warningScopeRows"
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>
</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-option v-for="(value, key) in ruleTypeOptions" :key="key" :label="value" :value="key"> </el-option>
</el-select>
</el-form-item>
<div class="duration">
<el-form-item label="持续时间" prop="time">
<span>当预警持续</span>
<el-input v-model="state.form.time" placeholder="请输入持续时间" @input="inputNum"></el-input>
</el-form-item>
<el-form-item label="" class="no-el-label">
<el-select v-model="state.form.unit" placeholder="请选择">
<el-option v-for="(value, key) in unitOptions" :key="key" :label="value.label" :value="key"> </el-option>
</el-select>
<span>时产生报警</span>
</el-form-item>
</div>
<el-form-item label="检查周期" prop="inspection_cycle">
<el-select style="flex: 1" v-model="state.form.inspection_cycle" placeholder="请选择检查周期">
<el-option v-for="item in inspectionCycleOptions" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="是否立即启用">
<el-switch
class="bg-switch-ele"
v-model="state.form.state"
:active-value="1"
:inactive-value="0"
inline-prompt
active-text="是"
inactive-text="否" />
</el-form-item>
</div>
</el-form>
</div>
</template>
<script setup>
import { reactive, ref, shallowReactive, computed, nextTick, watch } 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";
const props = defineProps({
row: {
type: Object,
default: null,
},
});
const tableSelOptions = ["", ""];
const ruleTypeOptions = {
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 unitOptions = {
s: { label: "", max: MAX_DAY * 24 * 3600 },
m: { label: "分钟", max: MAX_DAY * 24 * 60 },
h: { label: "小时", max: MAX_DAY * 24 },
};
const state = reactive({
form: {
name: "",
indicator_expression: "",
rule_type: "empty",
time: 10,
unit: "s",
inspection_cycle: 1,
warningScopeRows: [],
state: 1,
},
rules: {
name: [{ required: true, message: "请输入指标名称", trigger: "blur" }],
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;
let { max } = unitOptions[state.form.unit];
if (time > +max) {
state.form.time = max;
}
};
const inspectionCycleOptions = ref([1, 3, 5, 10, 20]);
const form_ref = ref(null);
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));
});
if (form_valid && table_form_valid) {
return {
...state.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;
state.form.state = n.state || 1;
state.form.time = n.time || 10;
state.form.unit = n.unit || "s";
state.form.indicator_expression = n.indicator_expression;
},
{ deep: true, immediate: true }
);
defineExpose({
Submit,
});
</script>
<style lang="scss" scoped>
.add-form {
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
.indicator-expression {
height: 300px;
width: 100%;
:deep(.vue-ace-editor) {
margin-top: 0;
}
}
.duration {
display: flex;
align-items: center;
gap: 8px;
:deep(.el-form-item__content) {
display: flex;
align-items: center;
gap: 8px;
> .el-input {
flex: 1;
width: 80px;
}
}
.no-el-label {
:deep(.el-form-item__content) {
.el-select {
width: 80px;
}
}
}
}
:deep(.el-switch__inner) {
.is-hide {
display: none;
}
}
:deep(.el-input-group__append, .el-input-group__prepend) {
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
.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 {
min-height: 36px;
label {
height: 36px;
line-height: 36px;
}
.el-input__inner {
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>
import { ElMessage } from "element-plus";
const setParams = (res, { id, class_id }) => {
let params = {
class_id,
metric_name: res.name,
expr: res.indicator_expression,
alert_range: res.warningScopeRows.map((e) => {
return {
variable_name: e.key,
metric_name: e.indicator_scope,
metric_label: e.indicator_tag,
chinese_name: e.cname,
is_required: e.is_required == 1,
is_linked: e.is_linkage == 1,
};
}) || [],
duration: res.time,
duration_unit: res.unit,
check_period: res.inspection_cycle,
is_enabled: res.state,
alert_rule_type: res.rule_type,
}
if (id) {
params.id = id
}
return params;
}
export const URL = "https://so.wodcloud.co/v1/api/metric_config"
export const Save = (res, p) => {
let params = setParams(res, p);
console.log("params: ", params);
// axios[id ? 'put' : 'post'](URL, params).then(res => {
// if(res.data.code == 200){
// console.log('success');
// }else{
// ElMessage.error(res.data.data)
// }
// })
}
\ No newline at end of file
<template>
<div class="main-slide">
<div class="slide-title">预警对象</div>
<div class="slide-search">
<el-input v-model="search" placeholder="请输入关键字" clearable @input="Search" @clear="Search">
<template #prefix>
<bg-icon style="font-size: 12px; color: #404a62" icon="#bg-ic-search"></bg-icon>
</template>
</el-input>
<el-button type="primary" size="default" @click="Add">
<bg-icon style="font-size: 12px; color: #fff" icon="#bg-ic-add"></bg-icon>
</el-button>
</div>
<div class="slide-tree bg-scroll">
<el-tree
ref="treeRef"
default-expand-all
:data="slideTree"
:current-node-key="selectId"
node-key="id"
empty-text="暂无数据"
:highlight-current="true"
:expand-on-click-node="false"
:filter-node-method="filterNode"
@node-click="treeNodeChoose">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<el-popover popper-class="tree-operation" placement="right-start" :width="120" trigger="click">
<template #reference>
<span class="operation" @click.stop="">
<bg-icon style="font-size: 12px; color: #404a62" icon="#bg-ic-s-more"></bg-icon>
</span>
</template>
<template #default>
<ul class="operation-lists">
<li v-for="(value, key) in operations" :key="key" @click.stop="Operation(key, node, data)">
<el-button link :disabled="value.disabled(node, data)">{{ value.label(node) }}</el-button>
</li>
</ul>
</template>
</el-popover>
</span>
</template>
</el-tree>
</div>
</div>
<el-dialog :close-on-click-modal="false" v-model="addWarnType" width="558px" :before-close="Cancel">
<template #header>
<GapTitle :title="isEditWarnType ? '编辑预警分类' : '新增预警分类'"></GapTitle>
</template>
<div style="padding-top: 20px">
<el-form :model="state.form" ref="add_warn_type_form" :rules="state.rules" label-width="80px">
<el-form-item label="预警分类" prop="name">
<el-input v-model="state.form.name" placeholder="请输入预警分类" maxlength="20" show-word-limit></el-input>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button size="default" @click="Cancel">取消</el-button>
<el-button type="primary" size="default" @click="Save"> 保存 </el-button>
</template>
</el-dialog>
<el-dialog :close-on-click-modal="false" v-model="addWarnTarget" width="558px" :before-close="Cancel">
<template #header>
<GapTitle :title="isEditWarnType ? '编辑预警对象' : '新增预警对象'"></GapTitle>
</template>
<div style="padding-top: 20px">
<el-form :model="state.form_target" ref="add_warn_target_form" :rules="state.target_rules" label-width="80px">
<el-form-item label="预警分类" prop="type">
<el-select style="flex: 1" v-model="state.form_target.type" placeholder="请选择预警分类" filterable>
<el-option v-for="item in slideTree" :key="item.id" :label="item.label" :value="item.id"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="预警对象" prop="target">
<el-input
v-model="state.form_target.target"
placeholder="请输入预警对象"
maxlength="20"
show-word-limit></el-input>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button size="default" @click="CancelAddWarnTarget">取消</el-button>
<el-button type="primary" size="default" @click="SaveAddWarnTarget"> 保存 </el-button>
</template>
</el-dialog>
<el-dialog v-model="delWarnType" width="400px" :before-close="Cancel">
<template #header>
<GapTitle title="删除预警分类"></GapTitle>
</template>
<div style="padding: 20px 0">是否确定删除 {{ activeTree.own.label }} 这个预警分类</div>
<template #footer>
<el-button size="default" @click="CancelDel">取消</el-button>
<el-button type="primary" size="default" @click="ConfirmDel"> 确定 </el-button>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick, onBeforeMount, reactive, ref } from "vue";
import GapTitle from "@/components/gap-title.vue";
const props = defineProps({
modelValue: {
type: Object,
default: () => ({}),
},
});
const search = ref("");
let slideTree = ref([]);
const treeRef = ref(null);
const selectId = ref("");
const getSlideTree = async () => {
slideTree.value = [
{
id: 1,
label: "容器云",
children: [
{
id: 11,
label: "容器集群",
},
{
id: 12,
label: "容器节点",
},
{
id: 13,
label: "工作负载",
},
{
id: 4,
label: "网关",
},
],
},
{
id: 2,
label: "机房",
},
{
id: 3,
label: "机柜",
},
{
id: 4,
label: "服务器",
},
{
id: 5,
label: "数据库/达梦数据库",
},
];
await nextTick();
treeRef.value.setCurrentKey(slideTree.value[0].children[0].id);
setTimeout(() => {
const node = treeRef.value.getNode(slideTree.value[0].children[0]);
treeNodeChoose(slideTree.value[0].children[0], node);
}, 1000);
};
const filterNode = (value, data) => {
if (!value) return data;
return data.label.includes(value);
};
const Search = async () => {
await nextTick();
treeRef.value.filter(search.value);
};
const Add = () => {
addWarnType.value = true;
};
const emits = defineEmits(["undate:modelValue"]);
const treeNodeChoose = (data, node) => {
if (node.level == 1) return;
emits("update:modelValue", { data, node });
};
const getBrotherNodes = (node) => {
let arr = [];
if (node.level == 1) {
arr = slideTree.value;
} else {
arr = node.parent.data.children;
}
return arr;
};
const operations = {
1: {
label: (node) => {
return node.level == 1 ? "编辑预警分类" : "编辑预警对象";
},
disabled: () => {
return false;
},
},
2: {
label: () => "新增预警对象",
disabled: () => {
return false;
},
},
3: {
label: () => "删除",
disabled: () => {
return false;
},
},
4: {
label: () => "上移",
disabled: (node, data) => {
let arr = getBrotherNodes(node);
let id = data.id;
let i = arr.findIndex((e) => e.id == id);
return 0 == i;
},
},
5: {
label: () => "下移",
disabled: (node, data) => {
let arr = getBrotherNodes(node);
let id = data.id;
let i = arr.findIndex((e) => e.id == id);
return arr.length - 1 == i;
},
},
};
const addWarnType = ref(false);
const addWarnTarget = ref(false);
const delWarnType = ref(false);
const isEditWarnType = ref(false);
const activeTree = ref({});
const Operation = (key, node, data) => {
activeTree.value = {
node,
own: data,
data: node.level == 1 ? data : node.parent.data,
};
let operation_handled_fn = {
1: async () => {
if (node.level == 1) {
addWarnType.value = true;
await nextTick();
state.form.name = data.label;
} else {
addWarnTarget.value = true;
await nextTick();
state.form_target.type = activeTree.value.data.id;
state.form_target.target = data.label;
}
isEditWarnType.value = true;
},
2: () => {
addWarnTarget.value = true;
isEditWarnType.value = false;
state.form_target.type = activeTree.value.data.id;
},
3: () => {
delWarnType.value = true;
},
4: () => {
let arr = getBrotherNodes(node);
let id = data.id;
let i = arr.findIndex((e) => e.id == id);
let prev = arr[i - 1];
},
5: () => {
let arr = getBrotherNodes(node);
let id = data.id;
let i = arr.findIndex((e) => e.id == id);
let next = arr[i + 1];
},
};
operation_handled_fn[key]();
};
const add_warn_type_form = ref(null);
const add_warn_target_form = ref(null);
const state = reactive({
form: {
name: "",
},
form_target: {
type: "",
target: "",
},
rules: {
name: [{ required: true, message: "请输入预警分类", trigger: "blur" }],
},
target_rules: {
type: [{ required: true, message: "请选择预警分类", trigger: "change" }],
target: [{ required: true, message: "请输入预警对象", trigger: "blur" }],
},
});
const Cancel = async () => {
add_warn_type_form.value.resetFields();
await nextTick();
addWarnType.value = false;
};
const Save = () => {
add_warn_type_form.value.validate((valid) => {
if (valid) {
console.log(state.form);
Cancel();
} else {
return false;
}
});
};
const CancelAddWarnTarget = async () => {
add_warn_target_form.value.resetFields();
await nextTick();
addWarnTarget.value = false;
};
const SaveAddWarnTarget = () => {
add_warn_target_form.value.validate((valid) => {
if (valid) {
console.log(state.form_target);
Cancel();
} else {
return false;
}
});
};
const CancelDel = () => {
delWarnType.value = false;
};
const ConfirmDel = () => {
console.log(activeTree.value.own);
CancelDel();
};
onBeforeMount(() => {
getSlideTree();
});
</script>
<style lang="scss" scoped>
.main-slide {
width: 320px;
height: 100%;
background-color: #fff;
box-shadow: 0 1px 4px 0 rgba(0, 7, 101, 0.15);
border-radius: 6px;
margin-right: 16px;
overflow: hidden;
.slide-title {
height: 40px;
background-color: #f7f7f9;
display: flex;
align-items: center;
padding-left: 16px;
}
.slide-search {
padding: 16px;
display: flex;
align-items: center;
:deep(.el-button) {
margin-left: 8px;
}
}
.slide-tree {
height: calc(100% - 108px);
:deep(.el-tree-node__content) {
height: 36px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
overflow: hidden;
&:hover {
.operation {
visibility: visible;
}
}
.operation {
display: flex;
visibility: hidden;
width: 36px;
height: 36px;
align-items: center;
justify-content: center;
box-shadow: -4px 0 4px 0px rgba(0, 7, 101, 0.15);
}
}
}
}
.add-warn-type-form {
padding-top: 20px;
}
</style>
<style lang="scss">
.tree-operation {
padding: 4px 0 !important;
min-width: 0 !important;
.operation-lists {
li {
line-height: 34px;
cursor: pointer;
transition: all 300ms;
.el-button {
width: 100%;
height: 34px;
justify-content: flex-start;
padding: 0 16px;
}
&:hover {
background-color: #f2f3f7;
.el-button {
&:not(.is-disabled) {
color: #3759be;
}
}
}
}
}
}
</style>
...@@ -87,6 +87,7 @@ ...@@ -87,6 +87,7 @@
:headers="headers" :headers="headers"
:rows="tableRows" :rows="tableRows"
@selectAc="selectRows" @selectAc="selectRows"
:selectable="selectable"
:isIndex="true" :isIndex="true"
:select="true" :select="true"
:stripe="true"> :stripe="true">
...@@ -398,7 +399,9 @@ const filterClear = () => { ...@@ -398,7 +399,9 @@ const filterClear = () => {
}; };
changePage(1); changePage(1);
}; // 重置筛选项 }; // 重置筛选项
const selectable = (row, index) => {
return row.state === 0;
};
const getTableRows = () => { const getTableRows = () => {
let params = { ...state.filter }; let params = { ...state.filter };
// axios // axios
......
<template>
<div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="main_container">
<div class="add-form-main bg-scroll">
<add-form ref="add_form"></add-form>
</div>
<div class="add-btns">
<el-button size="default" @click="Cancle">取消</el-button>
<el-button type="primary" size="default" @click="Save">保存</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import addForm from "../modules/add-form.vue";
const router = useRouter();
const Cancle = () => {
router.go(-1);
};
const add_form = ref(null);
const Save = async () => {
let res = await add_form.value.Submit();
if (!res) return;
console.log("res: ", res);
};
</script>
<style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px 16px;
min-height: 100%;
.main_container {
height: calc(100% - 46px);
padding: 0;
.add-form-main {
padding: 24px;
height: calc(100% - 68px);
}
.add-btns {
height: 68px;
display: flex;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #ddd;
padding: 0 24px;
}
}
}
</style>
<template> <template>
<div>规则详情</div> <div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="main_container bg-scroll">
<gap-title :hasLine="true" title="基本信息"></gap-title>
<div class="info">
<Info :labelData="labelData" :valueData="info">
<template #status="{ item, valueData }">
<span class="status-body">
<span class="status" :class="`status-${valueData.status}`"></span>
<span>{{ STATUS_OBJ[valueData[item.prop]] }}</span>
</span>
</template>
</Info>
</div>
<gap-title :hasLine="true" title="预警范围"></gap-title>
<div class="info">
<Info :labelData="warning_scope_label" :valueData="watning_scope_data"> </Info>
</div>
<gap-title :hasLine="true" title="预警规则"></gap-title>
<div class="info">
<bg-table border ref="ruletable" :headers="ruleHeaders" :rows="ruleRows" height="100%"> </bg-table>
</div>
<gap-title :hasLine="true" title="高级配置"> </gap-title>
<div class="info">
<Info :labelData="advanced_label" :valueData="advanced_data"> </Info>
</div>
<gap-title :hasLine="true" title="预警工单推送"> </gap-title>
<div class="info">
<Info :labelData="ticket_push_label" :valueData="ticket_push_data">
<template #notification_method="{ valueData }">
<span>{{ valueData.notification_method.map((e) => METHODS[e]).join("") }}</span>
</template>
</Info>
<div class="push-lists">
<bg-table border ref="pushtable" :headers="pushHeaders" :rows="pushRows" height="100%" :isIndex="true">
</bg-table>
</div>
</div>
</div>
</div>
</template> </template>
<script setup></script> <script setup>
import gapTitle from "@/components/gap-title.vue";
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import Info from "@/components/warn-detail/info.vue";
import { METHODS } from "@/components/manual-distribution/env.js";
const STATUS_OBJ = {
enabled: "启用",
disabled: "禁用",
};
const labelData = [
[
{
label: "预警规则名称",
prop: "warning_rule_name",
},
{
label: "启用状态",
prop: "status",
},
],
[
{
label: "预警对象",
prop: "warning_target",
},
{
label: "预警分类",
prop: "warning_type",
},
],
[
{
label: "预警指标",
prop: "warning_index",
},
{
label: "创建人",
prop: "create_by",
},
],
[
{
label: "创建时间",
prop: "create_time",
},
{
label: "更新时间",
prop: "update_time",
},
],
];
const info = {
warning_rule_name: "服务中断推送规则1",
status: "enabled",
warning_target: "容器云",
warning_type: "容器集群",
warning_index: "CPU使用率",
create_by: "admin",
create_time: "2020-01-01 00:00:00",
update_time: "2020-01-01 00:00:00",
};
const warning_scope_label = [
[
{
prop: "colony",
label: "集群",
},
],
[
{
prop: "core_com",
label: "核心组件",
},
],
];
const watning_scope_data = {
colony: "等于 default",
core_com: "等于 kube-apiserver/kube-apiserver2/kube-apiserver3",
};
const advanced_label = [
[
{
prop: "duration",
label: "持续时间",
},
],
[
{
prop: "inspection_cycle",
label: "检查周期",
},
],
];
const advanced_data = {
duration: "直接产生预警",
inspection_cycle: "1分钟",
};
const ticket_push_label = [
[
{
prop: "notification_method",
label: "预警通知方式",
},
],
[
{
prop: "push_num",
label: "消息推送次数",
},
],
[
{
prop: "push_frequency",
label: "消息推送频率",
},
],
];
const ticket_push_data = {
notification_method: ["1", "2"],
push_num: "10次",
push_frequency: "60分钟",
};
const ruleHeaders = [
{
prop: "warning_threshold",
label: "预警阈值",
},
{
prop: "risk_level",
label: "风险程度",
},
];
const ruleRows = [
{
warning_threshold: "12% - 50% ",
risk_level: "较大风险",
},
{
warning_threshold: "50% - 100% ",
risk_level: "重大风险",
},
];
const pushHeaders = [
{
prop: "warning_threshold",
label: "预警阈值",
},
{
prop: "risk_level",
label: "风险程度",
},
];
const pushRows = [
{
warning_threshold: "12% - 50% ",
risk_level: "较大风险",
},
{
warning_threshold: "50% - 100% ",
risk_level: "重大风险",
},
];
</script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px;
min-height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
.main_container {
height: 100%;
padding: 24px;
:deep(.gap-title) {
margin-bottom: 16px;
}
.info {
max-width: 1072px;
width: 100%;
padding: 0 8px 0;
&:not(:last-child) {
padding-bottom: 24px;
}
.push-lists {
margin-top: 16px;
}
}
}
}
.status {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
$statusObj: (
enabled: #48ad97,
disabled: #9e9e9e,
);
@each $status, $color in $statusObj {
&-#{$status} {
background-color: $color;
}
}
}
.status-body {
display: flex;
align-items: center;
}
</style>
<template>
<div class="detail_container">
<bg-breadcrumb></bg-breadcrumb>
<div class="main_container">
<div class="add-form-main bg-scroll">
<add-form ref="add_form" :row="infoData"></add-form>
</div>
<div class="add-btns">
<el-button size="default" @click="Cancle">取消</el-button>
<el-button type="primary" size="default" @click="Save">保存</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import bgBreadcrumb from "@/components/bg-breadcrumb.vue";
import addForm from "../modules/add-form.vue";
// const infoData = ref({
// id: 1,
// name: "11",
// type_key: "static",
// duration: 1,
// time: 10,
// inspection_cycle: 1,
// push_num: 1,
// push_frequency: 60,
// enabled: true,
// type_com_ref: {
// warn_type: "colony",
// warn_indicator: "1",
// warn_target: "1",
// warn_type_com: {
// value1: "22",
// value1_select: "3",
// value2: "33",
// value2_select: "2",
// risk_level: 1,
// },
// },
// manual_distribution_form: {
// method: ["1", "2"],
// lists: [
// {
// user_id: 2,
// user_name: 22,
// phone: 13000000002,
// },
// {
// user_id: 3,
// user_name: 33,
// phone: 13000000003,
// },
// ],
// },
// });
const infoData = ref({
name: "11",
type_key: "static",
duration: 1,
time: 10,
inspection_cycle: 1,
push_num: 1,
push_frequency: 60,
enabled: true,
type_com_ref: {
warn_type: "gateway",
warn_indicator: "1",
warn_target: "1",
warn_type_com: {
ruleRows: [
{
from: "22",
to: "33",
risk_level: 1,
},
{
from: "44",
to: "55",
risk_level: 2,
},
],
},
},
manual_distribution_form: {
method: ["1"],
lists: [
{
user_id: 1,
user_name: 11,
phone: 13000000001,
},
],
},
});
// const infoData = ref({
// id: 1,
// name: "11",
// type_key: "static",
// duration: 1,
// time: 10,
// inspection_cycle: 1,
// push_num: 1,
// push_frequency: 60,
// enabled: true,
// type_com_ref: {
// warn_type: "colony",
// warn_indicator: "1",
// warn_target: "1",
// warn_type_com: {
// value1: "22",
// value1_select: "3",
// value2: "33",
// value2_select: "2",
// risk_level: 1,
// },
// },
// manual_distribution_form: {
// method: ["1", "2"],
// lists: [
// {
// user_id: 2,
// user_name: 22,
// phone: 13000000002,
// },
// {
// user_id: 3,
// user_name: 33,
// phone: 13000000003,
// },
// ],
// },
// });
// const infoData = ref({
// name: "11",
// type_key: "custom",
// duration: 1,
// time: 10,
// inspection_cycle: 1,
// push_num: 1,
// push_frequency: 60,
// enabled: true,
// type_com_ref: {
// warn_target: "22",
// warn_type: "33",
// warn_indicator: "44",
// indicator_expression: "55\n66\n77",
// rule_type: "1",
// ruleRows: [
// {
// from: "88",
// to: "99",
// risk_level: 1,
// },
// ],
// },
// manual_distribution_form: {
// method: ["1", "2"],
// lists: [
// {
// user_id: 1,
// user_name: 11,
// phone: 13000000001,
// },
// {
// user_id: 2,
// user_name: 22,
// phone: 13000000002,
// },
// ],
// },
// });
const router = useRouter();
const Cancle = () => {
router.go(-1);
};
const add_form = ref(null);
const Save = async () => {
let res = await add_form.value.Submit();
if (!res) return;
console.log("res: ", res);
};
</script>
<style lang="scss" scoped>
.detail_container {
width: 100%;
height: calc(100vh - 56px);
padding: 0 24px 16px;
min-height: 100%;
.main_container {
height: calc(100% - 46px);
padding: 0;
.add-form-main {
padding: 24px;
height: calc(100% - 68px);
}
.add-btns {
height: 68px;
display: flex;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #ddd;
padding: 0 24px;
}
}
}
</style>
...@@ -69,6 +69,7 @@ ...@@ -69,6 +69,7 @@
:headers="headers" :headers="headers"
:rows="tableRows" :rows="tableRows"
@selectAc="selectRows" @selectAc="selectRows"
:selectable="selectable"
:isIndex="true" :isIndex="true"
:select="true" :select="true"
:stripe="true"> :stripe="true">
...@@ -243,7 +244,12 @@ const batchDelete = () => { ...@@ -243,7 +244,12 @@ const batchDelete = () => {
const goDetail = (row) => { const goDetail = (row) => {
console.log("去详情"); console.log("去详情");
router.push(`/forewarning/rule-set/detail?id=${row.id}`); router.push({
path: "/forewarning/rule-set/detail",
query: {
id: row.id,
},
});
}; // 查看详情 }; // 查看详情
const changeSearch = (val) => { const changeSearch = (val) => {
...@@ -267,6 +273,9 @@ const filterClear = () => { ...@@ -267,6 +273,9 @@ const filterClear = () => {
changePage(1); changePage(1);
}; // 重置筛选项 }; // 重置筛选项
const selectable = (row, index) => {
return row.state === 0;
};
const getTableRows = () => { const getTableRows = () => {
let params = { ...state.filter }; let params = { ...state.filter };
// axios // axios
...@@ -349,12 +358,19 @@ const stateChange = (row) => { ...@@ -349,12 +358,19 @@ const stateChange = (row) => {
const addRule = () => { const addRule = () => {
console.log("新增"); console.log("新增");
// router.push(`/xxx/xxx`); router.push({
path: `/forewarning/rule-set/add`,
});
}; // 新增规则 }; // 新增规则
const editRow = (row) => { const editRow = (row) => {
console.log("编辑"); console.log("编辑");
// router.push(`/xxx/xxx?id=${row.id}`); router.push({
path: `/forewarning/rule-set/edit`,
query: {
id: row.id,
},
});
}; // 编辑 }; // 编辑
const deleteRow = (row) => { const deleteRow = (row) => {
......
<template>
<div class="add-form">
<el-form :model="state.form" ref="form_ref" :rules="state.rules" label-width="110px">
<div class="add-form-item">
<el-form-item label="预警规则名称" prop="name">
<el-input v-model="state.form.name" :disabled="isEdit" placeholder="请输入预警规则名称"></el-input>
</el-form-item>
<el-form-item label="检测类型" prop="type">
<el-button-group>
<el-button
v-for="(value, key) in types"
:key="key"
:type="state.form.type_key == key ? 'primary' : ''"
size="small"
@click="changeType(key)"
:disabled="isEdit">
{{ value }}
</el-button>
</el-button-group>
</el-form-item>
</div>
<component
ref="type_com_ref"
:is="typeCom[state.form.type_key]"
:isEdit="isEdit"
:form="typrFormData"></component>
<gap-title :hasLine="true" title="高级配置"></gap-title>
<div class="add-form-item">
<div class="duration">
<el-form-item label="持续时间" prop="time">
<span>当预警持续</span>
<el-input v-model="state.form.time" placeholder="请输入持续时间" @input="inputNum"></el-input>
</el-form-item>
<el-form-item label="" prop="time" class="no-el-label">
<el-select v-model="state.form.unit" placeholder="请选择">
<el-option v-for="(value, key) in unitOptions" :key="key" :label="value.label" :value="key"> </el-option>
</el-select>
<span>时产生报警</span>
</el-form-item>
</div>
<el-form-item label="检查周期" prop="inspection_cycle">
<el-select style="flex: 1" v-model="state.form.inspection_cycle" placeholder="请选择">
<el-option v-for="item in inspectionCycleOptions" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
</div>
<gap-title :hasLine="true" title="预警工单推送"></gap-title>
<div class="add-form-item">
<el-form-item class="no-el-label" label="" prop="push_method">
<div style="width: 100%">
<ManualDistributionForm
ref="manual_distribution_form"
class="manual-distribution-form"
:noElLabel="false"
methodLabel="预警通知方式"
:history="history" />
</div>
</el-form-item>
<el-form-item label="消息推送次数">
<el-input style="flex: 1" v-model="state.form.push_num" placeholder="请输入消息推送次数" clearable>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="消息推送频率">
<el-input style="flex: 1" v-model="state.form.push_frequency" placeholder="请输入消息推送频率" clearable>
<template #append>分钟</template>
</el-input>
</el-form-item>
<el-form-item label="是否立即启用" prop="">
<el-switch
v-model="state.form.enabled"
inline-prompt
active-text="是"
inactive-text="否"
:active-value="true"
:inactive-value="false">
</el-switch>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script setup>
import { reactive, ref, shallowReactive, computed, nextTick, watch } from "vue";
import gapTitle from "@/components/gap-title.vue";
import ManualDistributionForm from "@/components/manual-distribution/form.vue";
import Static from "./static.vue";
import Custom from "./custom.vue";
import { MAX_DAY } from "@/components/env.js";
const props = defineProps({
row: {
type: Object,
default: null,
},
});
const isEdit = computed(() => !!props.row);
const history = computed(() => props.row?.manual_distribution_form || null);
const typrFormData = computed(() => props.row?.type_com_ref || null);
const manual_distribution_form = ref(null);
const typeCom = shallowReactive({
static: Static,
custom: Custom,
});
const unitOptions = {
s: { label: "", max: MAX_DAY * 24 * 3600 },
m: { label: "分钟", max: MAX_DAY * 24 * 60 },
h: { label: "小时", max: MAX_DAY * 24 },
};
const state = reactive({
form: {
name: "",
type_key: "static",
time: 10,
unit: "s",
inspection_cycle: 1,
push_num: 1,
push_frequency: 60,
enabled: true,
},
rules: {
name: [{ required: true, message: "请输入预警规则名称", trigger: "blur" }],
time: [{ required: true, message: "请输入持续时间", trigger: "blur" }],
},
});
const inputNum = () => {
state.form.time = state.form.time.replace(/[^\d]/g, "");
let time = +state.form.time;
let { max } = unitOptions[state.form.unit];
if (time > +max) {
state.form.time = max;
}
};
const types = {
static: "静态阈值",
custom: "自定义",
};
const changeType = async (key) => {
state.form.type_key = key;
form_ref.value.clearValidate();
};
const durationOptions = [
{
id: 1,
name: "直接产生预警",
},
{
id: 2,
name: "当预警持续",
},
];
const timeOptions = [10, 20, 60, 120, 180, 300];
const inspectionCycleOptions = ref([1, 3, 5, 10, 20]);
const form_ref = ref(null);
const type_com_ref = ref(null);
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
let type_com_ref_valid = await type_com_ref.value.Submit();
let manual_distribution_form_valid = await manual_distribution_form.value.Submit();
if (form_valid && type_com_ref_valid && manual_distribution_form_valid) {
return {
...state.form,
type_com_ref: type_com_ref.value?.form || {},
manual_distribution_form: manual_distribution_form.value?.form || {},
};
}
return;
};
watch(
() => props.row,
(n) => {
if (!n) return;
state.form.name = n.name;
state.form.type_key = n.type_key;
state.form.unit = n.unit;
state.form.time = n.time || 10;
state.form.inspection_cycle = n.inspection_cycle || 1;
state.form.push_num = n.push_num || 1;
state.form.push_frequency = n.push_frequency || 60;
state.form.enabled = n.enabled || false;
},
{ deep: true, immediate: true }
);
defineExpose({
Submit,
});
</script>
<style lang="scss" scoped>
.add-form {
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
padding-left: 8px;
.indicator-expression {
height: 300px;
width: 100%;
:deep(.vue-ace-editor) {
margin-top: 0;
}
}
.duration {
display: flex;
align-items: center;
gap: 8px;
:deep(.el-form-item__content) {
display: flex;
align-items: center;
gap: 8px;
> .el-input {
flex: 1;
width: 80px;
}
}
.no-el-label {
:deep(.el-form-item__content) {
.el-select {
width: 80px;
}
}
}
}
:deep(.el-switch__inner) {
.is-hide {
display: none;
}
}
:deep(.el-input-group__append, .el-input-group__prepend) {
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
.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;
}
}
}
</style>
<style lang="scss">
.el-form-item {
min-height: 36px;
label {
height: 36px;
line-height: 36px;
}
.el-input__inner {
min-height: 34px;
}
}
</style>
<template>
<div class="container-cluster-form">
<el-form :model="state.form" ref="form_ref" :rules="state.rules" label-width="110px">
<gap-title :hasLine="true" title="预警范围"></gap-title>
<div class="container-cluster-form-item warning-scope-form-item">
<el-form-item :label="typeToTextJson[props.type][0]" prop="value1">
<el-input v-model="state.form.value1" placeholder="请输入">
<template #prepend>
<el-select class="rule-select" v-model="state.form.value1_select" style="width: 114px">
<el-option v-for="(value, key) in selectRule" :key="key" :label="value" :value="key" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item :label="typeToTextJson[props.type][1]" prop="value2">
<el-input v-model="state.form.value2" placeholder="请输入">
<template #prepend>
<el-select class="rule-select" v-model="state.form.value2_select" style="width: 114px">
<el-option v-for="(value, key) in selectRule" :key="key" :label="value" :value="key" />
</el-select>
</template>
</el-input>
</el-form-item>
</div>
<gap-title :hasLine="true" title="预警规则"></gap-title>
<div class="container-cluster-form-item">
<el-form-item label="风险程度" prop="risk_level">
<el-select style="flex: 1" v-model="state.form.risk_level" placeholder="请选择" filterable>
<el-option v-for="item in riskLevelOptions" :key="item.id" :label="item.name" :value="item.id"> </el-option>
</el-select>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script setup>
import { reactive, ref, watch } from "vue";
import gapTitle from "@/components/gap-title.vue";
const props = defineProps({
type: {
type: String,
default: "",
},
form: {
type: Object,
default: null,
},
});
const typeToTextJson = {
colony: ["集群", "核心组件"],
node: ["集群", "节点"],
group: ["命名空间", "容器"],
};
const selectRule = ref({
1: "遍历",
2: "等于",
3: "不等于",
4: "正则匹配",
5: "正则不匹配",
});
const state = reactive({
form: {
value1: "",
value1_select: "1",
value2: "",
value2_select: "1",
risk_level: "",
},
rules: {
value1: [{ required: true, message: "请输入", trigger: "blur" }],
value2: [{ required: true, message: "请输入", trigger: "blur" }],
risk_level: [{ required: true, message: "请选择", trigger: "change" }],
},
});
const form_ref = ref(null);
watch(
() => props.type,
(n) => {
state.form.value1 = "";
state.form.value1_select = "1";
state.form.value2 = "";
state.form.value2_select = "1";
form_ref.value.clearValidate(["value1", "value2", "risk_level"]);
}
);
watch(
() => props.form,
(n) => {
if (!n) return;
state.form.value1 = n.value1;
state.form.value1_select = n.value1_select || "1";
state.form.value2 = n.value2;
state.form.value2_select = n.value2_select || "1";
state.form.risk_level = n.risk_level;
},
{
deep: true,
immediate: true,
}
);
const riskLevelOptions = ref([
{
id: 1,
name: "重大风险",
},
{
id: 2,
name: "较大风险",
},
{
id: 3,
name: "一般风险",
},
{
id: 4,
name: "低风险",
},
]);
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
return form_valid;
};
defineExpose({
Submit,
form: state.form,
form_ref,
});
</script>
<style lang="scss" scoped>
.container-cluster-form {
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
padding-left: 8px;
}
.warning-scope-form-item {
display: flex;
align-items: center;
:deep(.el-form-item) {
flex: 1;
.el-input-group__prepend {
border-radius: 4px !important;
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
overflow: hidden;
}
}
:deep(.el-input-group__prepend) {
background-color: #2b4695;
.el-input__wrapper {
box-shadow: 1px 0 0 0 #2b4695 !important;
&,
.el-input__inner {
color: #fff;
}
.el-input__wrapper {
color: #fff;
}
}
}
}
}
</style>
<template>
<div class="custom-form">
<el-form :model="state.form" ref="form_ref" :rules="state.rules" label-width="110px">
<div class="custom-form-item">
<el-form-item label="预警对象" prop="warn_target">
<el-input
v-model="state.form.warn_target"
placeholder="请输入"
:disabled="isEdit"
:maxlength="20"
show-word-limit
clearable
@change="changeWarnCustomTarget"></el-input>
</el-form-item>
<el-form-item label="预警分类" prop="warn_type">
<el-input
v-model="state.form.warn_type"
placeholder="请输入"
:disabled="isEdit"
clearable
:maxlength="20"
show-word-limit
@change="changeWarnCustomType"></el-input>
</el-form-item>
<el-form-item label="预警指标" prop="warn_indicator">
<el-input
v-model="state.form.warn_indicator"
placeholder="请输入"
:disabled="isEdit"
:maxlength="30"
show-word-limit
clearable
@change="changeWarnCustomType"></el-input>
</el-form-item>
</div>
<gap-title :hasLine="true" title="指标表达式"> </gap-title>
<div class="custom-form-item">
<el-form-item label="" prop="indicator_expression">
<div class="indicator-expression">
<bg-code-editor v-model="state.form.indicator_expression"></bg-code-editor>
</div>
</el-form-item>
</div>
<gap-title :hasLine="true" title="预警规则"></gap-title>
<div class="add-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-option v-for="(value, key) in ruleTypeOptions" :key="key" :label="value.label" :value="key">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="" v-if="state.form.ruleRows.length > 0 && state.form.rule_type != 'empty'">
<div class="rule-table" style="width: 100%">
<el-form
:model="state.form.ruleRows"
ref="table_form"
:rules="state.tableRules"
label-width="0"
style="width: 100%">
<el-table :data="state.form.ruleRows" stripe border>
<el-table-column
v-for="header in ruleHeaders"
:prop="header.prop"
:key="header.prop"
:label="header.label"
:width="header.width">
<template #default="{ $index }">
<div class="warning-threshold" v-if="header.prop == 'warning_threshold'">
<el-form-item :prop="`[${$index}].from`" :rules="state.tableRules.from" style="flex: 1">
<el-input
style="flex: 1"
v-model="state.form.ruleRows[$index].from"
placeholder="请输入"
@input="changeWarningThresholdFrom($index)">
<template #append>{{ unitMap }}</template>
</el-input>
</el-form-item>
<span class="to">-</span>
<el-form-item label="" :prop="`${$index}.to`" :rules="state.tableRules.to" style="flex: 1">
<el-input
style="flex: 1"
v-model="state.form.ruleRows[$index].to"
placeholder="请输入"
clearable
@input="changeWarningThresholdTo($index)">
<template #append>{{ unitMap }}</template>
</el-input>
</el-form-item>
</div>
<div v-else-if="header.prop == 'risk_level'">
<el-form-item label="" :prop="`[${$index}].risk_level`" :rules="state.tableRules.risk_level">
<el-select
style="flex: 1"
v-model="state.form.ruleRows[$index].risk_level"
placeholder="请选择">
<el-option
v-for="item in riskLevelOptions($index)"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</div>
</template>
</el-table-column>
<el-table-column prop="" label="操作" width="150px">
<template #default="{ $index }">
<div class="table-operation">
<el-button
link
type="primary"
@click="createRule($index)"
:disabled="state.form.ruleRows.length >= riskLevels.length">
新增
</el-button>
<span class="line"></span>
<el-button
link
type="primary"
@click="removeRule($index)"
:disabled="state.form.ruleRows.length == 1">
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script setup>
import { computed, reactive, ref, watch } from "vue";
import gapTitle from "@/components/gap-title.vue";
import { ElMessage } from "element-plus";
const props = defineProps({
form: {
type: Object,
default: null,
},
isEdit: {
type: Boolean,
default: false,
},
});
var validateWarnIndex = (rule, value, callback) => {
if (value === "") {
if (state.form.type_key == "static") {
callback(new Error("请选择"));
} else {
callback(new Error("请输入"));
}
} else {
callback();
}
};
const state = reactive({
form: {
warn_target: "",
warn_type: "",
warn_indicator: "",
indicator_expression: "",
rule_type: "empty",
ruleRows: [],
},
rules: {
warn_target: [{ required: true, message: "请输入", trigger: "blur" }],
warn_type: [{ required: true, message: "请输入", trigger: "blur" }],
warn_indicator: [{ validator: validateWarnIndex, trigger: "blur" }],
indicator_expression: [{ required: true, message: "请输入", trigger: "blur" }],
rule_type: [{ required: true, message: "请选择", trigger: "change" }],
},
tableRules: {
from: [{ required: true, message: "请输入", trigger: "blur" }],
to: [{ required: true, message: "请输入", trigger: "blur" }],
risk_level: [{ required: true, message: "请选择", trigger: "change" }],
},
});
const changeWarnCustomTarget = () => {};
const changeWarnCustomType = () => {};
const setLimits = (index) => {
let rows = [...state.form.ruleRows];
rows.splice(index, 1);
return (
rows.map((e) => {
return {
down: +e.from,
up: +e.to,
};
}) || []
);
};
const changeWarningThresholdFrom = (index) => {
let { down, up } = limit.value;
if (down === "") return;
let { from, to } = state.form.ruleRows[index];
if (+from > +up || (index == 0 && +from < +down) || +from > +to) {
state.form.ruleRows[index].from = "";
return;
}
let rows = setLimits(index);
if (rows.length == 0) return;
try {
rows.forEach((e) => {
if (+e.up >= +from > +e.down) {
throw "";
}
});
} catch (e) {
state.form.ruleRows[index].from = "";
}
};
const changeWarningThresholdTo = (index) => {
let { down, up } = limit.value;
if (up === "") return;
let { from, to } = state.form.ruleRows[index];
if ((index == 0 && +to > +up) || +to < +down || +from > +to) {
state.form.ruleRows[index].to = "";
return;
}
let rows = setLimits(index);
if (rows.length == 0) return;
try {
rows.forEach((e) => {
if (+e.up > +to > +e.down) {
throw "";
}
});
} catch (e) {
state.form.ruleRows[index].to = "";
}
};
const changeRuleType = () => {};
const form_ref = ref(null);
const table_form = 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.value.validate((res) => resolve(res));
});
return form_valid && table_form_valid;
};
const riskLevels = ref([
{
id: 1,
name: "重大风险",
},
{
id: 2,
name: "较大风险",
},
{
id: 3,
name: "一般风险",
},
{
id: 4,
name: "低风险",
},
]);
const riskLevelOptions = computed(() => {
return (index) => {
let risk_level = state.form.ruleRows[index].risk_level;
let rows = state.form.ruleRows.map((e) => e.risk_level);
if (risk_level) {
let i = rows.findIndex((e) => e == risk_level);
rows.splice(i, 1);
}
return riskLevels.value.filter((e) => !rows.includes(e.id));
};
});
const unitMap = computed(() => {
return ruleTypeOptions[state.form.rule_type].unit || "";
});
const limit = computed(() => {
return (
ruleTypeOptions[state.form.rule_type].limit || {
down: "",
up: "",
}
);
});
const ruleTypeOptions = {
empty: {
label: "",
},
1: {
label: "百分比范围",
unit: "%",
limit: {
down: 0,
up: 100,
},
},
2: {
label: "毫秒范围",
unit: "ms",
limit: {
down: 0,
up: "",
},
},
3: {
label: "秒范围",
unit: "s",
limit: {
down: 0,
up: "",
},
},
4: {
label: "个范围",
unit: "",
limit: {
down: 0,
up: "",
},
},
5: {
label: "温度范围",
unit: "",
limit: {
down: "",
up: "",
},
},
};
const ruleHeaders = [
{
prop: "warning_threshold",
label: "预警阈值",
width: "500px",
},
{
prop: "risk_level",
label: "风险程度",
},
];
const createRule = (index = -1) => {
state.form.ruleRows.splice(index + 1, 0, {
from: "",
to: "",
risk_level: "",
});
};
const removeRule = (index) => {
state.form.ruleRows.splice(index, 1);
};
watch(
() => props.form,
(f) => {
if (!f) {
createRule();
return;
}
state.form.warn_target = f.warn_target;
state.form.warn_type = f.warn_type;
state.form.warn_indicator = f.warn_indicator;
state.form.indicator_expression = f.indicator_expression;
state.form.rule_type = f.rule_type;
state.form.ruleRows =
f.ruleRows.map((e) => {
return {
from: e.from,
to: e.to,
risk_level: e.risk_level,
};
}) || [];
if (state.form.ruleRows.length == 0) {
createRule();
}
},
{
deep: true,
immediate: true,
}
);
defineExpose({
Submit,
form: state.form,
form_ref,
});
</script>
<style lang="scss" scoped>
.custom-form {
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
padding-left: 8px;
.indicator-expression {
height: 300px;
width: 100%;
:deep(.vue-ace-editor) {
margin-top: 0;
}
}
}
}
.warning-threshold {
display: flex;
align-items: center;
:deep(.el-input-group__append, .el-input-group__prepend) {
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.to {
margin: 0 16px;
}
.line {
width: 1px;
height: 14px;
background-color: #c1c7d7;
margin: 0 16px;
display: inline-block;
}
.rule-table {
:deep(.el-table thead th) {
background-color: #f5f6f9;
}
}
</style>
<template>
<div class="gateway-form">
<div class="gateway-form-item">
<el-form
:model="state.form.ruleRows"
ref="form_ref"
:rules="state.tableRules"
label-width="0"
style="width: 100%">
<el-table :data="state.form.ruleRows" stripe border>
<el-table-column
v-for="header in ruleHeaders"
:prop="header.prop"
:key="header.prop"
:label="header.label"
:width="header.width">
<template #default="{ $index }">
<div class="warning-threshold" v-if="header.prop == 'warning_threshold'">
<el-form-item :prop="`[${$index}].from`" :rules="state.tableRules.from" style="flex: 1">
<el-input
style="flex: 1"
v-model.number="state.form.ruleRows[$index].from"
placeholder="请输入"
@input="changeWarningThresholdFrom($index)">
<template v-if="state.form.rule_type != 'empty'" #append>ms</template>
</el-input>
</el-form-item>
<span class="to">-</span>
<el-form-item label="" :prop="`${$index}.to`" :rules="state.tableRules.to" style="flex: 1">
<el-input
style="flex: 1"
v-model.number="state.form.ruleRows[$index].to"
placeholder="请输入"
clearable
@input="changeWarningThresholdTo($index)">
<template v-if="state.form.rule_type != 'empty'" #append>ms</template>
</el-input>
</el-form-item>
</div>
<div v-else-if="header.prop == 'risk_level'">
<el-form-item label="" :prop="`[${$index}].risk_level`" :rules="state.tableRules.risk_level">
<el-select style="flex: 1" v-model="state.form.ruleRows[$index].risk_level" placeholder="请选择">
<el-option
v-for="item in riskLevelOptions($index)"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</div>
</template>
</el-table-column>
<el-table-column prop="" label="操作" width="150px">
<template #default="{ $index }">
<div class="table-operation">
<el-button
link
type="primary"
@click="createRule($index)"
:disabled="state.form.ruleRows.length >= riskLevels.length">
新增
</el-button>
<span class="line"></span>
<el-button link type="primary" @click="removeRule($index)" :disabled="state.form.ruleRows.length == 1">
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</div>
</template>
<script setup>
import { computed, reactive, ref, watch } from "vue";
import gapTitle from "@/components/gap-title.vue";
const props = defineProps({
form: {
type: Object,
default: null,
},
});
const state = reactive({
form: {
ruleRows: [],
},
tableRules: {
from: [{ required: true, message: "请输入", trigger: "blur" }],
to: [{ required: true, message: "请输入", trigger: "blur" }],
risk_level: [{ required: true, message: "请选择", trigger: "change" }],
},
});
const form_ref = ref(null);
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
return form_valid;
};
const riskLevels = ref([
{
id: 1,
name: "重大风险",
},
{
id: 2,
name: "较大风险",
},
{
id: 3,
name: "一般风险",
},
{
id: 4,
name: "低风险",
},
]);
const riskLevelOptions = computed(() => {
return (index) => {
let risk_level = state.form.ruleRows[index].risk_level;
let rows = state.form.ruleRows.map((e) => e.risk_level);
if (risk_level) {
let i = rows.findIndex((e) => e == risk_level);
rows.splice(i, 1);
}
return riskLevels.value.filter((e) => !rows.includes(e.id));
};
});
const ruleHeaders = [
{
prop: "warning_threshold",
label: "预警阈值",
width: "500px",
},
{
prop: "risk_level",
label: "风险程度",
},
];
const createRule = (index = -1) => {
state.form.ruleRows.splice(index + 1, 0, {
from: "",
to: "",
risk_level: "",
});
};
const setLimits = (index) => {
let rows = [...state.form.ruleRows];
rows.splice(index, 1);
return (
rows.map((e) => {
return {
down: +e.from,
up: +e.to,
};
}) || []
);
};
const changeWarningThresholdFrom = (index) => {
let { down, up } = limit.value;
if (down === "") return;
let { from, to } = state.form.ruleRows[index];
if (+from > +up || (index == 0 && +from < +down) || +from > +to) {
state.form.ruleRows[index].from = "";
return;
}
let rows = setLimits(index);
if (rows.length == 0) return;
try {
rows.forEach((e) => {
if (+e.up >= +from > +e.down) {
throw "";
}
});
} catch (e) {
state.form.ruleRows[index].from = "";
}
};
const changeWarningThresholdTo = (index) => {
let { down, up } = limit.value;
if (up === "") return;
let { from, to } = state.form.ruleRows[index];
if ((index == 0 && +to > +up) || +to < +down || +from > +to) {
state.form.ruleRows[index].to = "";
return;
}
let rows = setLimits(index);
if (rows.length == 0) return;
try {
rows.forEach((e) => {
if (+e.up > +to > +e.down) {
throw "";
}
});
} catch (e) {
state.form.ruleRows[index].to = "";
}
};
const removeRule = (index) => {
state.form.ruleRows.splice(index, 1);
};
watch(
() => props.form,
(f) => {
if (!f?.ruleRows) {
createRule();
return;
}
state.form.ruleRows =
f.ruleRows.map((e) => {
return {
from: e.from,
to: e.to,
risk_level: e.risk_level,
};
}) || [];
if (state.form.ruleRows.length == 0) {
createRule();
}
},
{
deep: true,
immediate: true,
}
);
defineExpose({
Submit,
form: state.form,
form_ref,
});
</script>
<style lang="scss" scoped>
.gateway-form {
margin-bottom: 16px;
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
padding-left: 110px;
:deep(.el-table thead th) {
background-color: #f5f6f9;
}
.indicator-expression {
height: 300px;
width: 100%;
:deep(.vue-ace-editor) {
margin-top: 0;
}
}
}
}
:deep(.el-form-item) {
margin-bottom: 0 !important;
}
.warning-threshold {
display: flex;
align-items: center;
:deep(.el-input-group__append, .el-input-group__prepend) {
border-radius: 4px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.to {
margin: 0 16px;
}
.line {
width: 1px;
height: 14px;
background-color: #c1c7d7;
margin: 0 16px;
display: inline-block;
}
.rule-table {
:deep(.el-table thead th) {
background-color: #f5f6f9;
}
}
</style>
<template>
<div class="static-form">
<el-form :model="state.form" ref="form_ref" :rules="state.rules" label-width="110px">
<div class="static-form-item">
<el-form-item label="预警对象/分类" prop="warn_type">
<el-cascader
:disabled="props.isEdit"
ref="cascader_ref"
style="flex: 1"
placeholder="请选择预警对象/分类"
:options="staticTypeOptions"
v-model="state.form.warn_type"
@change="changeWarnStaticType"
:props="cascaderProps">
</el-cascader>
</el-form-item>
<el-form-item label="预警指标" prop="warn_indicator">
<el-select
style="flex: 1"
:disabled="!formFormat.warn_type || props.isEdit"
v-model="state.form.warn_indicator"
placeholder="请选择预警指标">
<el-option v-for="(value, key) in warningIndexOptions" :key="key" :label="value" :value="key"> </el-option>
</el-select>
</el-form-item>
</div>
<div>
<div v-if="state.form.warning_scpoe_form.length > 0">
<gap-title :hasLine="true" title="预警范围"></gap-title>
<div class="static-form-item">
<div class="warning-scope-main">
<div class="warn-scpoe-form" v-for="(item, index) in state.form.warning_scpoe_form" :key="index">
<el-form-item :label="item.chinese_name">
<el-select
class="warn-scpoe-select"
v-model="item.select"
style="width: 114px"
@change="changeSelect(index, item)">
<el-option v-for="(value, key) in selectRule" :key="key" :label="value" :value="key" />
</el-select>
</el-form-item>
<el-form-item
label=""
class="no-el-label warn-scpoe-input-item"
prop="value"
:rules="[
{
validator: (rule, value, callback) => validateValue(rule, value, callback, item, index),
trigger: showSelect.includes(item.select) ? 'change' : 'blur',
},
]">
<el-input
class="warn-scpoe-input-value"
v-model="state.form.warning_scpoe_form[index].value"
placeholder="请输入"
v-if="!showSelect.includes(item.select)"
:disabled="item.select == 1">
</el-input>
<el-select
class="warn-scpoe-input-value"
v-else
v-model="item.value"
placeholder="请选择"
filterable
:loading="item.loading"
remote
:remote-method="(query) => remoteMethod(query, index)"
multiple>
<el-option v-for="item in item.options" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</div>
</div>
</div>
</div>
<gap-title :hasLine="true" title="预警规则"></gap-title>
<div class="static-form-item">
<div class="warning-rule-main">
<el-form-item label="风险程度" prop="risk_level" v-if="isEmpty">
<el-select style="flex: 1" v-model="state.form.risk_level" placeholder="请选择风险程度" filterable>
<el-option v-for="item in riskLevelOptions" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<Gateway v-else ref="warn_type_com" />
</div>
</div>
</div>
<!-- <component
ref="warn_type_com"
:is="warnTypeCom[formFormat.warn_type]"
:type="formFormat.warn_type"
:form="warn_type_com_form"
v-if="formFormat.warn_type"></component> -->
</el-form>
</div>
</template>
<script setup>
import { computed, nextTick, reactive, ref, shallowReactive, watch } from "vue";
import gapTitle from "@/components/gap-title.vue";
// import CommonCom from "./common-com.vue";
import Gateway from "./gateway.vue";
const showSelect = ["4", "5"];
const selectRule = ref({
1: "全部",
2: "等于",
3: "不等于",
4: "正则匹配",
5: "正则不匹配",
});
const riskLevelOptions = ref([
{
id: 1,
name: "重大风险",
},
{
id: 2,
name: "较大风险",
},
{
id: 3,
name: "一般风险",
},
{
id: 4,
name: "低风险",
},
]);
// const warnTypeCom = shallowReactive({
// colony: CommonCom,
// node: CommonCom,
// group: CommonCom,
// gateway: Gateway,
// });
const props = defineProps({
form: {
type: Object,
default: null,
},
isEdit: {
type: Boolean,
default: false,
},
});
const changeSelect = (index, item) => {
console.log("item.select: ", item.select);
if (showSelect.includes(item.select)) {
state.form.warning_scpoe_form[index].value = [];
} else {
state.form.warning_scpoe_form[index].value = "";
}
};
const validateValue = (rule, value, callback, item, index) => {
if (!item.is_required || item.select == 1) return callback();
if (item.value == "") {
let msg = showSelect.includes(item.select) ? `请选择${item.chinese_name}` : `请输入${item.chinese_name}`;
return callback(new Error(msg));
}
return callback();
};
const state = reactive({
form: {
warn_type: [],
warn_indicator: "",
risk_level: "",
warning_scpoe_form: [],
},
rules: {
warn_type: [{ type: "array", required: true, message: "请选择预警对象/分类", trigger: "change" }],
warn_indicator: [{ required: true, message: "请选择预警指标", trigger: "change" }],
risk_level: [{ required: true, message: "请选择风险程度", trigger: "change" }],
},
});
const module_data = {
id: "20da8f87-628a-4f0c-bd9c-0ad176d18d59",
class_id: 101,
metric_name: "xx请求次数告警",
expr: 'shttp_requests_total{method="GET",$pod$}',
duration: 5,
duration_unit: "m",
check_period: 3,
is_enabled: 1,
alert_rule_type: "9f1e6170-65e8-4e14-9c17-6a7b87a900a7",
created_by: "",
created_at: "2023-06-28 17:28:29",
updated_by: "",
updated_at: "2023-06-28 17:54:33",
alert_range: [
{
variable_name: "$pod$",
metric_name: "shttp_requests_total",
metric_label: "pod",
chinese_name: "demoString",
is_required: true,
is_linked: true,
},
{
variable_name: "$image$",
metric_name: "shttp_requests_total",
metric_label: "image",
chinese_name: "镜像",
is_required: true,
is_linked: true,
},
],
};
const isEmpty = computed(() => {
return false;
// return module_data.alert_rule_type == "9f1e6170-65e8-4e14-9c17-6a7b87a900a7";
});
state.form.warning_scpoe_form = module_data.alert_range.map((e) => {
return {
...e,
value: "",
select: "1",
options: [],
loading: false,
};
});
watch(
() => state.form,
(n) => {
console.log(n);
},
{
deep: true,
}
);
const remoteMethod = (query, index) => {
console.log("query, index: ", query, index);
if (!query) {
state.form.warning_scpoe_form[index].options = [];
return;
}
state.form.warning_scpoe_form[index].loading = true;
setTimeout(() => {
state.form.warning_scpoe_form[index].loading = false;
state.form.warning_scpoe_form[index].options = [
{
id: 1,
name: 111,
},
{
id: 2,
name: 222,
},
];
}, 1000);
};
const warn_type_com_form = computed(() => props.form?.warn_type_com || null);
watch(
() => props.form,
(n) => {
if (!n) return;
state.form.warn_type = n.warn_target && n.warn_type ? [n.warn_target, n.warn_type] : [];
state.form.warn_indicator = n.warn_indicator;
},
{ deep: true, immediate: true }
);
const cascaderProps = {
value: "id",
label: "label",
};
const staticTypeOptions = [
{
id: "1",
label: "容器云平台",
children: [
{
id: "colony",
label: "容器集群",
options: {
1: "服务中断",
2: "CPU使用率",
3: "内存使用率",
4: "磁盘使用率",
},
},
{
id: "node",
label: "容器节点",
options: {
1: "服务中断",
2: "CPU使用率",
3: "内存使用率",
4: "磁盘使用率",
},
},
{
id: "group",
label: "容器组",
options: {
1: "服务中断",
2: "CPU使用率",
3: "内存使用率",
},
},
{
id: "gateway",
label: "网关",
options: {
1: "平均响应时长(入口网关)",
},
},
],
},
];
const formFormat = computed(() => {
let [warn_target = "", warn_type = ""] = state.form.warn_type || [];
let obj = {
...state.form,
warn_target,
warn_type,
isEmpty: isEmpty.value,
};
if (isEmpty.value) {
obj.warn_type_com = warn_type_com.value?.form || {};
}
return obj;
});
const warningIndexOptions = ref({});
const cascader_ref = ref(null);
const changeWarnStaticType = async () => {
const { data } = cascader_ref.value.getCheckedNodes()[0];
warningIndexOptions.value = data.options;
let keys = Object.keys(warningIndexOptions.value);
state.form.warn_indicator = keys[0];
setTimeout(() => {
form_ref.value.clearValidate(["warn_indicator"]);
});
};
const changeRuleType = () => {};
const form_ref = ref(null);
const warn_type_com = ref(null);
const Submit = async () => {
let form_valid = await new Promise((resolve, reject) => {
form_ref.value.validate((res) => resolve(res));
});
if (isEmpty.value) return form_valid;
let warn_type_com_valid = (await warn_type_com.value?.Submit()) || true;
return form_valid && warn_type_com_valid;
};
const ruleHeaders = [
{
prop: "warning_threshold",
label: "预警阈值",
width: "500px",
},
{
prop: "risk_level",
label: "风险程度",
},
];
defineExpose({
Submit,
form: formFormat,
form_ref,
});
</script>
<style lang="scss" scoped>
.static-form {
:deep(.gap-title) {
margin-bottom: 16px;
}
&-item {
max-width: 1080px;
width: 100%;
padding-left: 8px;
.warning-scope-main {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 16px;
:deep(.el-input-group__prepend) {
border-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.warn-scpoe-form {
width: 100%;
display: flex;
align-items: center;
.warn-scpoe-select {
width: 114px !important;
:deep(.el-input__wrapper) {
border-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.warn-scpoe-input-item {
flex: 1;
.warn-scpoe-input-value {
flex: 1;
:deep(.el-input__wrapper) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
}
}
}
}
.no-el-label {
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
}
</style>
...@@ -63,6 +63,7 @@ const Distribute = async () => { ...@@ -63,6 +63,7 @@ const Distribute = async () => {
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
padding: 0 24px;
} }
} }
} }
......
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