782 lines
22 KiB
JavaScript
782 lines
22 KiB
JavaScript
import PageHeaderWrapper from "@/components/PageHeaderWrapper";
|
|
import {
|
|
Card,
|
|
Table,
|
|
Popconfirm,
|
|
Divider,
|
|
Form,
|
|
Row,
|
|
Col,
|
|
Button,
|
|
Select,
|
|
Input,
|
|
message,
|
|
Tag,
|
|
Drawer,
|
|
Icon,
|
|
Modal,
|
|
Dropdown,
|
|
Menu,
|
|
Tooltip,
|
|
} from "antd";
|
|
import { formatMessage } from "umi/locale";
|
|
import useFetch from "@/lib/hooks/use_fetch";
|
|
import { ESPrefix } from "@/services/common";
|
|
import { useGlobalClusters } from "@/layouts/GlobalContext";
|
|
import router from "umi/router";
|
|
import Link from "umi/link";
|
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
import request from "@/utils/request";
|
|
import moment from "moment";
|
|
import { formatter } from "@/lib/format";
|
|
import { formatESSearchResult } from "@/lib/elasticsearch/util";
|
|
import { filterSearchValue, sorter, formatUtcTimeToLocal } from "@/utils/utils";
|
|
import { HealthStatusView } from "@/components/infini/health_status_view";
|
|
import { Route } from "umi";
|
|
import { JsonParam, QueryParamProvider, useQueryParam } from "use-query-params";
|
|
import { MonitorDatePicker } from "@/components/infini/MonitorDatePicker";
|
|
import { calculateBounds } from "@/components/vendor/data/common/query/timefilter";
|
|
import "./Index.scss";
|
|
import { cloneDeep, endsWith } from "lodash";
|
|
import MessageDetail from "./components/MessageDetail";
|
|
import {
|
|
PriorityColor,
|
|
MessageStautsColor,
|
|
PriorityToIconType,
|
|
} from "../utils/constants";
|
|
import IgnoreModal from "./components/IgnoreModal";
|
|
import { hasAuthority } from "@/utils/authority";
|
|
import ClusterName from "@/pages/System/Cluster/components/ClusterName";
|
|
import Statistic, { PriorityIconText } from "../components/Statistic";
|
|
import Mute from "@/components/Icons/Mute";
|
|
import EventMessageStatus from "./components/EventMessageStatus";
|
|
import WidgetLoader from "@/pages/DataManagement/View/WidgetLoader";
|
|
|
|
import DatePicker from "@/common/src/DatePicker";
|
|
import { getTimezone } from "@/utils/utils";
|
|
import { getLocale } from "umi/locale";
|
|
|
|
const { Search } = Input;
|
|
const { Option } = Select;
|
|
|
|
const Index = (props) => {
|
|
const [param, setParam] = useQueryParam("_g", JsonParam);
|
|
const [searchValue, setSearchValue] = React.useState("");
|
|
const [messageDetail, setMessageDetail] = useState({});
|
|
const [loading, setLoading] = React.useState(false);
|
|
const [dataSource, setDataSource] = useState({ data: [], total: 0 });
|
|
const [stats, setStats] = useState({});
|
|
const [state, setState] = useState({
|
|
categories: [],
|
|
tags: [],
|
|
});
|
|
|
|
const [refresh, setRefresh] = useState({ isRefreshPaused: false });
|
|
const [timeZone, setTimeZone] = useState(() => getTimezone());
|
|
|
|
const initialQueryParams = {
|
|
from: 0,
|
|
size: 10,
|
|
// status: "alerting",
|
|
start_time: "now-7d",
|
|
end_time: "now",
|
|
...param,
|
|
};
|
|
|
|
const alertReducer = (queryParams, action) => {
|
|
switch (action.type) {
|
|
case "priority":
|
|
return {
|
|
...queryParams,
|
|
priority: action.value,
|
|
};
|
|
case "status":
|
|
return {
|
|
...queryParams,
|
|
status: action.value,
|
|
};
|
|
case "timeChange":
|
|
return {
|
|
...queryParams,
|
|
...action.value,
|
|
};
|
|
case "pagination":
|
|
return {
|
|
...queryParams,
|
|
from: (action.value - 1) * queryParams.size,
|
|
};
|
|
case "pageSizeChange":
|
|
return {
|
|
...queryParams,
|
|
size: action.value,
|
|
};
|
|
case "refresh":
|
|
return {
|
|
...queryParams,
|
|
_t: new Date().getTime(),
|
|
};
|
|
case "category":
|
|
return {
|
|
...queryParams,
|
|
category: action.value,
|
|
};
|
|
case "tags":
|
|
return {
|
|
...queryParams,
|
|
tags: action.value,
|
|
};
|
|
}
|
|
return queryParams;
|
|
};
|
|
const [queryParams, dispatch] = React.useReducer(
|
|
alertReducer,
|
|
initialQueryParams
|
|
);
|
|
|
|
const onRefreshClick = () => {
|
|
dispatch({ type: "refresh" });
|
|
};
|
|
|
|
const ignoreTextInput = useRef(null);
|
|
const [ignoreState, setIgnoreState] = useState({
|
|
items: [],
|
|
visible: false,
|
|
hasError: false,
|
|
});
|
|
const onIgnoreCancel = () => {
|
|
setIgnoreState({ ...ignoreState, visible: false, hasError: false });
|
|
};
|
|
const onIgnoreOk = async () => {
|
|
const ignoredReason = ignoreTextInput.current.state.value;
|
|
if (!ignoredReason || ignoredReason.length == 0) {
|
|
ignoreTextInput.current.focus();
|
|
setIgnoreState({ ...ignoreState, hasError: true });
|
|
return;
|
|
} else {
|
|
const res = await request(`alerting/message/_ignore`, {
|
|
method: "POST",
|
|
body: {
|
|
messages: ignoreState.items,
|
|
ignored_reason: ignoredReason,
|
|
},
|
|
});
|
|
if (res && res.result == "updated") {
|
|
message.success(
|
|
formatMessage({
|
|
id: "app.message.ignored.success",
|
|
})
|
|
);
|
|
|
|
setIgnoreState({ ...ignoreState, visible: false });
|
|
setTimeout(() => {
|
|
setMessageDetail({});
|
|
onRefreshClick();
|
|
}, 1000);
|
|
} else {
|
|
console.log("Ignored failed,", res);
|
|
message.error(
|
|
formatMessage({
|
|
id: "app.message.ignored.failed",
|
|
})
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
const showIgnoreConfirm = (items) => {
|
|
setIgnoreState({ ...ignoreState, items: items, visible: true });
|
|
};
|
|
const clusterM = useGlobalClusters();
|
|
const generateMenu = (onMenuClick, status) => {
|
|
return (
|
|
<Menu onClick={onMenuClick}>
|
|
<Menu.Item
|
|
key="ignore"
|
|
disabled={!status || status == "alerting" ? false : true}
|
|
>
|
|
<Icon component={Mute} style={{ color: "rgb(187, 187, 187)" }} />
|
|
<span style={{ color: "rgba(102,102,102,1)" }}>
|
|
{formatMessage({ id: "form.button.ignore" })}
|
|
</span>
|
|
</Menu.Item>
|
|
<Menu.Item
|
|
key="reset"
|
|
disabled={!status || status == "ignored" ? false : true}
|
|
>
|
|
<Icon type="reload" style={{ color: "rgb(187, 187, 187)" }} />
|
|
<span style={{ color: "rgba(102,102,102,1)" }}>
|
|
{formatMessage({ id: "form.button.reset" })}
|
|
</span>
|
|
</Menu.Item>
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
const columns = [
|
|
// {
|
|
// title: formatMessage({ id: "alert.rule.table.columnns.cluster" }),
|
|
// dataIndex: "resource_name",
|
|
// render: (val, record) => {
|
|
// return (
|
|
// <ClusterName
|
|
// name={val}
|
|
// distribution={clusterM[record.resource_id]?.distribution}
|
|
// id={record.resource_id}
|
|
// />
|
|
// );
|
|
// },
|
|
// },
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.priority" }),
|
|
dataIndex: "priority",
|
|
render: (text, record) => {
|
|
const Com = PriorityToIconType[text];
|
|
if (!Com) {
|
|
return text;
|
|
}
|
|
return <PriorityIconText priority={text} />;
|
|
},
|
|
},
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.title" }),
|
|
dataIndex: "title",
|
|
render: (text, record) => {
|
|
return (
|
|
<a
|
|
style={{
|
|
maxWidth: 360,
|
|
whiteSpace: "nowrap",
|
|
textOverflow: "ellipsis",
|
|
display: "block",
|
|
overflow: "hidden",
|
|
}}
|
|
onClick={() => setMessageDetail(record)}
|
|
>
|
|
{text}
|
|
</a>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.category" }),
|
|
dataIndex: "category",
|
|
width: 80,
|
|
render: (val, record) => {
|
|
return <Tag style={{ color: "rgb(0, 127, 255)" }}>{val}</Tag>;
|
|
},
|
|
},
|
|
// {
|
|
// title: formatMessage({ id: "alert.message.table.tags" }),
|
|
// dataIndex: "tags",
|
|
// render: (val, record)=>{
|
|
// return (val || []).map(item=>{
|
|
// return <Tag style={{color:"rgb(0, 127, 255)"}}>{item}</Tag>
|
|
// })
|
|
// }
|
|
// },
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.created" }),
|
|
dataIndex: "created",
|
|
width: 180,
|
|
render: (text, record) => (
|
|
<span title={text}>{formatUtcTimeToLocal(text)}</span>
|
|
),
|
|
},
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.duration" }),
|
|
dataIndex: "duration",
|
|
render: (text, record) => moment.duration(text).humanize(),
|
|
},
|
|
{
|
|
title: formatMessage({ id: "alert.message.table.status" }),
|
|
dataIndex: "status",
|
|
width: 80,
|
|
render: (text, record) => {
|
|
return <EventMessageStatus record={record} />;
|
|
// <HealthStatusView status={MessageStautsColor[text]} label={text} />
|
|
},
|
|
},
|
|
{
|
|
title: formatMessage({ id: "table.field.actions" }),
|
|
align: "center",
|
|
render: (text, record) => {
|
|
if (!hasAuthority("alerting.message:all")) {
|
|
return null;
|
|
}
|
|
const onSingleMenuClick = ({ key }) => {
|
|
switch (key) {
|
|
case "ignore":
|
|
showIgnoreConfirm([{ id: record.id, rule_id: record.rule_id }]);
|
|
break;
|
|
case "reset":
|
|
resetMessage([
|
|
{ id: record.id, rule_id: record.rule_id, is_reset: true },
|
|
]);
|
|
break;
|
|
}
|
|
};
|
|
const menu = generateMenu(onSingleMenuClick, record.status);
|
|
return (
|
|
<div>
|
|
<Dropdown overlay={menu}>
|
|
<a style={{ fontSize: "1.4em" }}>
|
|
<Icon type="ellipsis" />
|
|
</a>
|
|
</Dropdown>
|
|
|
|
{/*<div style={{ whiteSpace: 'nowrap'}}>
|
|
<a onClick={() => setMessageDetail(record)}>
|
|
{formatMessage({ id: "form.button.detail" })}
|
|
</a>
|
|
{hasAuthority("alerting.message:all") ? (
|
|
<>
|
|
<Divider type="vertical" />
|
|
<a
|
|
disabled={record?.status == "alerting" ? "" : "disabled"}
|
|
onClick={() => {
|
|
showIgnoreConfirm([
|
|
{ id: record.id, rule_id: record.rule_id },
|
|
]);
|
|
}}
|
|
>
|
|
{formatMessage({ id: "form.button.ignore" })}
|
|
</a>
|
|
</>
|
|
) : null} */}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
const onTimeChange = ({ start, end, refresh }) => {
|
|
dispatch({
|
|
type: "timeChange",
|
|
value: { start_time: start, end_time: end, refresh },
|
|
});
|
|
};
|
|
|
|
const fetchMessages = (queryParams) => {
|
|
setLoading(true);
|
|
let params = queryParams;
|
|
if (queryParams?.start_time && queryParams.end_time) {
|
|
const bounds = calculateBounds({
|
|
from: queryParams?.start_time,
|
|
to: queryParams.end_time,
|
|
});
|
|
params = {
|
|
...queryParams,
|
|
min: bounds.min.valueOf(),
|
|
max: bounds.max.valueOf(),
|
|
};
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
let url = `/alerting/message/_search`;
|
|
const res = await request(url, {
|
|
method: "GET",
|
|
queryParams: params,
|
|
});
|
|
if (res && !res.error) {
|
|
let { data, total, aggregations } = formatESSearchResult(res);
|
|
setDataSource({ data, total, aggregations });
|
|
}
|
|
setLoading(false);
|
|
};
|
|
fetchData();
|
|
};
|
|
|
|
const fetchMessageStats = () => {
|
|
const fetchData = async () => {
|
|
let url = `/alerting/message/_stats`;
|
|
const res = await request(url, {
|
|
method: "GET",
|
|
});
|
|
if (res && res.alert && res.alert.current && !res.error) {
|
|
setStats(res.alert.current);
|
|
setState({
|
|
categories: res.categories || [],
|
|
tags: res.tags || [],
|
|
});
|
|
}
|
|
};
|
|
fetchData();
|
|
};
|
|
|
|
useEffect(() => {
|
|
setParam({ ...param, ...queryParams });
|
|
fetchMessages(queryParams);
|
|
|
|
fetchMessageStats(queryParams);
|
|
}, [queryParams]);
|
|
|
|
const [selectedRows, setSelectedRows] = useState({
|
|
rowKeys: [],
|
|
rows: [],
|
|
});
|
|
const onSelectChange = (selectedRowKeys, selectedRows) => {
|
|
let rows = selectedRows.map((item) => {
|
|
return {
|
|
id: item.id,
|
|
rule_id: item.rule_id,
|
|
};
|
|
});
|
|
setSelectedRows({ rowKeys: selectedRowKeys, rows: rows });
|
|
};
|
|
|
|
const rowSelection = {
|
|
selectedRowKeys: selectedRows.rowKeys,
|
|
onChange: onSelectChange,
|
|
};
|
|
|
|
const onPriorityFilter = (value) => {
|
|
dispatch({ type: "priority", value: value });
|
|
dispatch({ type: "status", value: "alerting" });
|
|
};
|
|
const onStatusFilter = (value) => {
|
|
dispatch({ type: "status", value: value });
|
|
dispatch({ type: "priority", value: undefined });
|
|
};
|
|
|
|
const DrawerTitle = ({ id, ruleID, title, status }) => {
|
|
return (
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
wordBreak: "break-all",
|
|
gap: 10,
|
|
}}
|
|
>
|
|
<div>{title}</div>
|
|
<div style={{ paddingRight: 30 }}>
|
|
<Link to={`/alerting/message/${id}`}>
|
|
<Button type="primary">
|
|
{formatMessage({ id: "form.button.detail" })}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
const resetMessage = async (items) => {
|
|
const res = await request(`alerting/message/_ignore`, {
|
|
method: "POST",
|
|
body: {
|
|
messages: items,
|
|
is_reset: true,
|
|
},
|
|
});
|
|
if (res && res.result == "updated") {
|
|
message.success("Reset succeeded");
|
|
setTimeout(() => {
|
|
onRefreshClick();
|
|
}, 1000);
|
|
}
|
|
};
|
|
const onBatchMenuClick = ({ key }) => {
|
|
if (selectedRows.rowKeys.length == 0) {
|
|
message.warn(
|
|
formatMessage({
|
|
id: "app.message.warning.table.select-row",
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
switch (key) {
|
|
case "ignore":
|
|
showIgnoreConfirm(selectedRows.rows);
|
|
break;
|
|
case "reset":
|
|
resetMessage(selectedRows.rows);
|
|
break;
|
|
}
|
|
};
|
|
|
|
const batchMenu = generateMenu(onBatchMenuClick);
|
|
|
|
const widgetQueryParams = useMemo(() => {
|
|
const newQueryParams = cloneDeep(queryParams);
|
|
delete newQueryParams.from;
|
|
delete newQueryParams.size;
|
|
delete newQueryParams._t;
|
|
delete newQueryParams.start_time;
|
|
delete newQueryParams.end_time;
|
|
delete newQueryParams.refresh;
|
|
return newQueryParams;
|
|
}, [JSON.stringify(queryParams)]);
|
|
|
|
const { minUpdated, maxUpdated } = useMemo(() => {
|
|
if (!dataSource.aggregations) {
|
|
return { minUpdated: "", maxUpdated: "" };
|
|
}
|
|
return {
|
|
minUpdated: moment(dataSource.aggregations.min_updated?.value).tz(getTimezone()).utc().format(),
|
|
maxUpdated: moment(dataSource.aggregations.max_updated?.value).tz(getTimezone()).utc().format(),
|
|
};
|
|
}, [dataSource.aggregations]);
|
|
|
|
const filterPriorityAndStatus = (params) => {
|
|
dispatch({
|
|
type: "timeChange",
|
|
value: {
|
|
start_time: "",
|
|
end_time: "",
|
|
},
|
|
});
|
|
if (params.type === "priority") {
|
|
dispatch({
|
|
type: "status",
|
|
value: "alerting",
|
|
});
|
|
} else {
|
|
dispatch({
|
|
type: "priority",
|
|
value: undefined,
|
|
});
|
|
}
|
|
dispatch(params);
|
|
};
|
|
|
|
return (
|
|
<PageHeaderWrapper>
|
|
<Card>
|
|
<div style={{ display: "flex", gap: "10px", marginBottom: 20 }}>
|
|
<div
|
|
style={{
|
|
height: 180,
|
|
border: "1px solid rgb(235, 235, 235)",
|
|
width: "calc(50% - 5px)",
|
|
}}
|
|
>
|
|
<Statistic stats={stats} dispatch={filterPriorityAndStatus} />
|
|
</div>
|
|
<div style={{ width: "calc(50% - 5px)" }} className="alert-heatmap">
|
|
<Card
|
|
size="small"
|
|
title={formatMessage({
|
|
id: "alert.rule.detail.title.alert_heatmap",
|
|
})}
|
|
>
|
|
<WidgetLoader
|
|
id="cji1sc28go5i051pl1i0"
|
|
range={{
|
|
from: "now-6M",
|
|
to: "now",
|
|
}}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
marginBottom: 15,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexFlow: "column",
|
|
gap: 10,
|
|
width: 800,
|
|
}}
|
|
>
|
|
<div className="filters">
|
|
<Select
|
|
allowClear
|
|
showSearch
|
|
style={{ width: 120 }}
|
|
placeholder={"category"}
|
|
defaultValue={queryParams?.category}
|
|
value={queryParams?.category}
|
|
onChange={(value) => {
|
|
dispatch({ type: "category", value: value });
|
|
}}
|
|
>
|
|
{state.categories.map((item) => {
|
|
return (
|
|
<Option key={item} value={item}>
|
|
{item}
|
|
</Option>
|
|
);
|
|
})}
|
|
</Select>
|
|
<Select
|
|
allowClear
|
|
showSearch
|
|
style={{ width: 120 }}
|
|
placeholder={"tags"}
|
|
defaultValue={queryParams?.tags}
|
|
value={queryParams?.tags}
|
|
onChange={(value) => {
|
|
dispatch({ type: "tags", value: value });
|
|
}}
|
|
>
|
|
{state.tags.map((item) => {
|
|
return (
|
|
<Option key={item} value={item}>
|
|
{item}
|
|
</Option>
|
|
);
|
|
})}
|
|
</Select>
|
|
<Select
|
|
allowClear
|
|
showSearch
|
|
style={{ width: 150 }}
|
|
placeholder={"priority"}
|
|
defaultValue={queryParams?.priority}
|
|
value={queryParams?.priority}
|
|
onChange={(value) => {
|
|
dispatch({ type: "priority", value: value });
|
|
}}
|
|
>
|
|
{Object.keys(PriorityColor).map((item) => {
|
|
return (
|
|
<Option key={item} value={item}>
|
|
<PriorityIconText priority={item} />
|
|
</Option>
|
|
);
|
|
})}
|
|
</Select>
|
|
<Select
|
|
allowClear
|
|
showSearch
|
|
style={{ width: 150 }}
|
|
placeholder={"status"}
|
|
value={queryParams?.status}
|
|
onChange={(value) => {
|
|
dispatch({ type: "status", value: value });
|
|
}}
|
|
>
|
|
<Option value="alerting">alerting</Option>
|
|
<Option value="ignored">ignored</Option>
|
|
<Option value="recovered">recovered</Option>
|
|
</Select>
|
|
|
|
<div style={{ flexGrow: 0 }}>
|
|
<DatePicker
|
|
locale={getLocale()}
|
|
start={param?.start_time}
|
|
end={param?.end_time}
|
|
onRangeChange={onTimeChange}
|
|
{...refresh}
|
|
onRefreshChange={setRefresh}
|
|
onRefresh={(value) => onTimeChange({ ...(value || {}), refresh: new Date().valueOf()})}
|
|
timeZone={timeZone}
|
|
onTimeZoneChange={setTimeZone}
|
|
recentlyUsedRangesKey={'alerting-message'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 10,
|
|
}}
|
|
>
|
|
<Button
|
|
icon="redo"
|
|
onClick={() => {
|
|
onRefreshClick();
|
|
}}
|
|
>
|
|
{formatMessage({ id: "form.button.refresh" })}
|
|
</Button>
|
|
{hasAuthority("alerting.message:all") ? (
|
|
<Dropdown overlay={batchMenu}>
|
|
<Button type="primary">
|
|
{formatMessage({ id: "form.button.batch_actions" })}{" "}
|
|
<Icon type="down" />
|
|
</Button>
|
|
</Dropdown>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
width: "100%",
|
|
height: 140,
|
|
border: "1px solid rgb(232, 232, 232)",
|
|
borderRadius: 2,
|
|
marginBottom: 15,
|
|
}}
|
|
>
|
|
<WidgetLoader
|
|
id="cji1ttq8go5i051pl1t0"
|
|
range={{
|
|
from: minUpdated,
|
|
to: maxUpdated,
|
|
}}
|
|
queryParams={widgetQueryParams}
|
|
refresh={queryParams?.refresh}
|
|
/>
|
|
</div>
|
|
<Table
|
|
size={"small"}
|
|
loading={loading}
|
|
bordered={false}
|
|
dataSource={dataSource?.data}
|
|
rowKey={"id"}
|
|
pagination={{
|
|
size: "small",
|
|
pageSize: queryParams.size,
|
|
total: dataSource?.total?.value || dataSource?.total,
|
|
onChange: (page) => {
|
|
dispatch({ type: "pagination", value: page });
|
|
},
|
|
showSizeChanger: true,
|
|
onShowSizeChange: (_, size) => {
|
|
dispatch({ type: "pageSizeChange", value: size });
|
|
},
|
|
showTotal: (total, range) =>
|
|
`${range[0]}-${range[1]} of ${total} items`,
|
|
}}
|
|
columns={columns}
|
|
rowSelection={rowSelection}
|
|
/>
|
|
</Card>
|
|
<Drawer
|
|
title={
|
|
<DrawerTitle
|
|
id={messageDetail.id}
|
|
ruleID={messageDetail.rule_id}
|
|
title={messageDetail.title}
|
|
status={messageDetail.status}
|
|
/>
|
|
}
|
|
width={800}
|
|
placement="right"
|
|
closable={true}
|
|
onClose={() => setMessageDetail({})}
|
|
visible={!!messageDetail.id}
|
|
destroyOnClose={true}
|
|
>
|
|
<MessageDetail messageID={messageDetail.id} />
|
|
</Drawer>
|
|
|
|
<IgnoreModal
|
|
ref={ignoreTextInput}
|
|
ignoreState={ignoreState}
|
|
onCancel={onIgnoreCancel}
|
|
onOk={onIgnoreOk}
|
|
/>
|
|
</PageHeaderWrapper>
|
|
);
|
|
};
|
|
|
|
export default (props) => {
|
|
return (
|
|
<QueryParamProvider ReactRouterRoute={Route}>
|
|
<Index {...props} />
|
|
</QueryParamProvider>
|
|
);
|
|
};
|