console/web/src/pages/Alerting/Rule/components/RuleDetail.jsx

438 lines
12 KiB
JavaScript

import {
Button,
message,
Divider,
Typography,
Icon,
Tooltip,
Tag,
Card,
Empty,
Tabs,
} from "antd";
import Link from "umi/link";
import { formatMessage } from "umi/locale";
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
import { useHistory } from "react-router-dom";
import request from "@/utils/request";
import { formatter, getFormatter } from "@/utils/format";
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
import { MonitorDatePicker } from "@/components/infini/MonitorDatePicker";
import RuleCard from "./RuleCard";
import RuleRecords from "../../Message/components/RuleRecords";
import RuleRecordChart from "./RuleRecordChart";
import { Route } from "umi";
import { JsonParam, QueryParamProvider, useQueryParam } from "use-query-params";
import { formatUtcTimeToLocal, generateId } from "@/utils/utils";
import { PriorityIconText } from "../../components/Statistic";
import { MessageStautsColor } from "../../utils/constants";
import ExternalLink from "@/components/Icons/ExternalLink";
import NotificationCard from "./NotificationCard";
import Sum from "@/components/Icons/Sum";
import WidgetLoader, {
WidgetRender,
} from "@/pages/DataManagement/View/WidgetLoader";
import MessageRecord from "./MessageRecord";
import { hasAuthority } from "@/utils/authority";
import DatePicker from "@/common/src/DatePicker";
import { getLocale } from "umi/locale";
import { getTimezone } from "@/utils/utils";
import moment from "moment";
const { Title } = Typography;
export const buildWidgetByRule = (rule, queries, created, updated) => {
if (!rule) return;
const { metrics = {} } = rule;
const { format_type = "num" } = metrics;
const formatMapping = {
num: {
type: "number",
pattern: "0.00a",
},
bytes: {
type: "bytes",
pattern: "0.00b",
},
ratio: {
type: "percent",
pattern: "0.00%",
},
};
let query;
try {
query = JSON.stringify(queries.raw_filter);
} catch (error) {}
const number = parseInt(metrics.bucket_size);
const unit = metrics.bucket_size?.replace(`${number}`, '')
let bucketSize = metrics.bucket_size
if (unit) {
const duration = moment(updated).valueOf() - moment(created).valueOf()
const ms = moment.duration(number, unit).asMilliseconds()
if (duration <= 2 * ms) {
bucketSize = `${ms / 1000 / 2}s`
}
}
const config = {
bucket_size: bucketSize,
format: formatMapping[format_type],
group_labels: metrics.bucket_label ? [metrics.bucket_label] : undefined,
series: [
{
metric: {
formula: metrics.formula,
items: metrics.items,
groups: metrics.groups,
sort: [
{
direction: "desc",
key: "_count",
},
],
},
queries: {
cluster_id: queries.cluster_id,
indices: queries.indices,
time_field: queries.time_field,
dsl: query,
},
type: "line",
},
],
};
return {
id: generateId(20),
...config,
};
};
const RuleDetail = (props) => {
const ruleID = props?.ruleID;
if (!ruleID) {
return null;
}
const [param, setParam] = useQueryParam("_g", JsonParam);
const history = useHistory();
const [state, setState] = React.useState({
spinning: false,
timeRange: {
min: param?.timeRange?.min || "now-7d",
max: param?.timeRange?.max || "now",
timeFormatter: formatter.dates(1),
},
});
const ruleCardWrap = useRef();
const [ruleCardHeight, setRuleCardHeight] = React.useState(0);
const [refresh, setRefresh] = useState({ isRefreshPaused: true });
const [timeZone, setTimeZone] = useState(() => getTimezone());
useMemo(() => {
setParam({ ...param, timeRange: state.timeRange });
}, [state.timeRange]);
const handleTimeChange = ({ start, end, refresh }) => {
setState({
...state,
spinning: true,
timeRange: {
min: start,
max: end,
timeFormatter: formatter.dates(1),
},
refresh: refresh || state.refresh
});
};
const [ruleDetail, setRuleDetail] = useState({});
const fetcDetail = (id) => {
const fetchData = async () => {
const res = await request(`/alerting/rule/${id}/info`, {
method: "GET",
});
if (res) {
setRuleDetail(res);
}
};
fetchData();
};
const onIgnoreClick = useCallback(async () => {
const res = await request(`alerting/message/_ignore`, {
method: "POST",
body: { ids: [ruleID], user: "" },
});
if (res && res.result == "updated") {
message.success(
formatMessage({
id: "app.message.ignored.success",
})
);
fetcDetail(ruleID);
} else {
console.log("Ignored failed,", res);
message.success(
formatMessage({
id: "app.message.ignored.failed",
})
);
}
}, [ruleID]);
useEffect(() => {
fetcDetail(ruleID);
//Delay update RuleCard componment height
setTimeout(() => {
setRuleCardHeight(ruleCardWrap.current?.clientHeight);
}, 2000);
}, []);
const widget = useMemo(() => {
if (!ruleDetail || !ruleDetail.rule_name) return;
return buildWidgetByRule(ruleDetail, {
cluster_id: ruleDetail.resource_id,
indices: ruleDetail.resource_objects,
time_field: ruleDetail.resource_time_field,
raw_filter: ruleDetail.resource_raw_filter,
}, ruleDetail?.created, ruleDetail?.updated);
}, [ruleDetail]);
return (
<div>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 10,
}}
>
<Title level={4}>{ruleDetail?.rule_name}</Title>
<div>
{hasAuthority("alerting.rule:all") ? (
<Link to={`/alerting/rule/edit/${ruleID}`}>
<Button type="primary">
{formatMessage({ id: "form.button.edit" })}
</Button>
</Link>
) : null}
<Button
style={{ marginLeft: 20 }}
type="primary"
onClick={() => {
history.goBack();
}}
>
{formatMessage({ id: "form.button.goback" })}
</Button>
</div>
</div>
<div>
<div style={{ color: "rgb(153, 153, 153)" }}>
{formatMessage(
{ id: "alert.rule.detail.title.changed_desc" },
{
updated: formatUtcTimeToLocal(ruleDetail?.updated),
created: formatUtcTimeToLocal(ruleDetail?.created),
user: ruleDetail?.creator?.name,
}
)}
</div>
<div style={{ margin: "10px 0" }}>
{(ruleDetail?.tags || []).map((tag) => {
return (
<Tag
key={tag}
style={{
color: "rgb(0, 127, 255)",
border: "none",
background: "rgba(173, 173, 173, 0.15)",
}}
>
{tag}
</Tag>
);
})}
</div>
{ruleDetail?.alerting_message ? (
<Link
to={`/alerting/message/${ruleDetail?.alerting_message.id}`}
style={{
display: "block",
background: "rgba(255, 0, 0, 0.1)",
color: "rgb(102, 102, 102)",
fontSize: 12,
padding: 5,
marginBottom: 15,
}}
>
<Tag
style={{
backgroundColor: MessageStautsColor["alerting"],
color: "#fff",
border: "none",
}}
>
Alerting
</Tag>
<PriorityIconText
priority={ruleDetail?.alerting_message?.priority}
/>{" "}
| {ruleDetail?.alerting_message?.title}
<Icon
component={ExternalLink}
style={{
fontSize: "14px",
color: "rgb(0, 127, 255)",
marginLeft: 10,
}}
/>
</Link>
) : null}
</div>
<div style={{ display: "flex", gap: 15, marginBottom: 20 }}>
<div style={{ flex: "1 1 50%" }} ref={ruleCardWrap}>
<RuleCard ruleID={ruleID} data={ruleDetail} />
</div>
<div style={{ flex: "1 1 50%" }}>
<Card
size="small"
title={formatMessage({
id: "alert.message.detail.title.notification",
})}
bodyStyle={{
height: ruleCardHeight > 0 ? ruleCardHeight - 38 : 240,
minHeight: 240,
overflowY: "scroll",
overflowX: "hidden",
}}
>
<NotificationCard
notificationConfig={ruleDetail?.notification_config}
recoverNotificationConfig={
ruleDetail.recovery_notification_config
}
/>
</Card>
</div>
</div>
{/* <Title level={4} style={{ marginTop: 20 }}>
{formatMessage({ id: "alert.message.detail.alert_metric_status" })}
</Title> */}
<div style={{ marginBottom: 10, display: "flex", gap: 8 }}>
<div style={{ flexGrow: 0, minWidth: 400 }}>
<DatePicker
locale={getLocale()}
start={state.timeRange.min}
end={state.timeRange.max}
onRangeChange={handleTimeChange}
{...refresh}
onRefreshChange={setRefresh}
onRefresh={({ start, end}) => handleTimeChange({ start, end, refresh: new Date().valueOf()})}
timeZone={timeZone}
onTimeZoneChange={setTimeZone}
recentlyUsedRangesKey={"rule-detail"}
/>
</div>
</div>
<div style={{ display: "flex", gap: 15, marginBottom: 20 }}>
<div style={{ flex: "1 1 50%" }}>
<Card
size={"small"}
title={
<>
{formatMessage({ id: "alert.rule.detail.title.rule_preview" })}
<Tooltip title={ruleDetail?.expression}>
<Icon
component={Sum}
style={{
color: "rgb(0, 127, 255)",
backgroundColor: "#efefef",
marginLeft: 5,
}}
/>
</Tooltip>
</>
}
bodyStyle={{ height: 250, padding: 1 }}
>
{ruleDetail.rule_name ? (
<WidgetRender
widget={widget}
range={{
from: state.timeRange.min,
to: state.timeRange.max,
}}
refresh={state.refresh}
/>
) : (
<Empty />
)}
</Card>
</div>
<div style={{ flex: "1 1 50%" }}>
<Card
size="small"
title={formatMessage({
id: "alert.rule.detail.title.alert_heatmap",
})}
bodyStyle={{ height: 250, padding: 1 }}
>
<WidgetLoader
id="cji1sc28go5i051pl1i0"
range={{
from: state.timeRange.min,
to: state.timeRange.max,
}}
queryParams={{ rule_id: ruleID }}
refresh={state.refresh}
/>
</Card>
</div>
</div>
{/* {ruleDetail.resource_id ?
<RuleRecordChart
ruleID={ruleID}
timeRange={state.timeRange}
conditions={ruleDetail?.conditions}
clusterID ={ruleDetail?.resource_id}
/>:null} */}
{/* <Title level={4} style={{ marginTop: 20 }}>
{formatMessage({ id: "alert.message.detail.execution_record" })}
</Title> */}
<Tabs>
<Tabs.TabPane
key="alerts"
tab={formatMessage({ id: "alert.rule.detail.title.alert_event" })}
>
<MessageRecord ruleID={ruleID} timeRange={state.timeRange} refresh={state.refresh}/>
</Tabs.TabPane>
<Tabs.TabPane
key="history"
tab={formatMessage({ id: "alert.rule.detail.title.alert_history" })}
>
<RuleRecords
ruleID={ruleID}
timeRange={state.timeRange}
showAertMetric={true}
refresh={state.refresh}
/>
</Tabs.TabPane>
</Tabs>
</div>
);
};
export default (props) => {
return (
<QueryParamProvider ReactRouterRoute={Route}>
<RuleDetail {...props} />
</QueryParamProvider>
);
};