Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/TimeRangePicker/AutoRefresh/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface IProps {

function Refresh(props: IProps, ref) {
const intervalSecondsCache = props.localKey ? _.toNumber(window.localStorage.getItem(props.localKey)) : 0;
const [intervalSeconds, setIntervalSeconds] = useState(props.intervalSeconds || intervalSecondsCache);
const [intervalSeconds, setIntervalSeconds] = useState(props.intervalSeconds ?? intervalSecondsCache);
const [visible, setVisible] = useState(false);
const intervalRef = useRef<ReturnType<typeof setTimeout> | undefined>();
const removeRef = useRef(false);
Expand Down Expand Up @@ -86,7 +86,7 @@ function Refresh(props: IProps, ref) {
}, []);

useEffect(() => {
setIntervalSeconds(props.intervalSeconds || intervalSecondsCache);
setIntervalSeconds(props.intervalSeconds ?? intervalSecondsCache);
}, [props.intervalSeconds]);

useImperativeHandle(ref, () => ({
Expand Down
179 changes: 112 additions & 67 deletions src/pages/alertCurEvent/pages/List/AlertTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface IProps {
setRefreshFlag: (refreshFlag: string) => void;
tagDisplayMode: AlertEventTagsDisplayMode;
alertEscalationEnable: boolean;
isFullscreen: boolean;
}

function formatDuration(ms: number) {
Expand All @@ -59,7 +60,7 @@ function formatDuration(ms: number) {
}

export default function AlertTable(props: IProps) {
const { filter, setFilter, selectedRowKeys, setSelectedRowKeys, params, setRefreshFlag, tagDisplayMode, alertEscalationEnable } = props;
const { filter, setFilter, selectedRowKeys, setSelectedRowKeys, params, setRefreshFlag, tagDisplayMode, alertEscalationEnable, isFullscreen } = props;
const history = useHistory();
const { t } = useTranslation(NS);
const { datasourceList } = useContext(CommonStateContext);
Expand All @@ -73,6 +74,18 @@ export default function AlertTable(props: IProps) {
const lastInitiatedViewIdRef = useRef<number | null>(null);
const tableContainerRef = useRef<HTMLDivElement>(null);
const [tableHeaderHeight, setTableHeaderHeight] = useState<number>();
const [newFullscreenEventKeys, setNewFullscreenEventKeys] = useState<Set<string>>(new Set());
const previousFullscreenPageRef = useRef<{ queryKey: string; eventKeys: Set<string> }>();
// TimeRangePicker 会在刷新时向 range 写入 refreshFlag;它不参与接口筛选,不能让它重置新增事件的对比基准。
const fullscreenQueryKey = JSON.stringify({
...params,
range: params.range ? _.omit(params.range, ['refreshFlag']) : params.range,
});

useEffect(() => {
previousFullscreenPageRef.current = undefined;
setNewFullscreenEventKeys(new Set());
}, [fullscreenQueryKey, isFullscreen]);

useEffect(() => {
const parsed = queryString.parse(location.search);
Expand Down Expand Up @@ -236,9 +249,13 @@ export default function AlertTable(props: IProps) {
} as any);
}

const getEventIdentity = (event: { hash: string }) => event.hash;

const fetchData = ({ current, pageSize }) => {
const requestQueryKey = fullscreenQueryKey;
const isFullscreenRequest = isFullscreen;
const requestParams: any = {
p: current,
p: isFullscreen ? 1 : current,
limit: pageSize,
my_groups: String(params.my_groups) === 'true',
..._.omit(params, ['range', 'my_groups']),
Expand All @@ -250,16 +267,29 @@ export default function AlertTable(props: IProps) {
requestParams.etime = moment(parsedRange.end).unix();
}
return getEvents(requestParams).then((res) => {
const list = res.dat.list ?? [];
return {
total: res.dat.total,
list: res.dat.list,
list,
requestQueryKey,
isFullscreenRequest,
};
});
};
const { tableProps } = useAntdTable(fetchData, {
refreshDeps: [JSON.stringify(params), props.refreshFlag],
refreshDeps: [fullscreenQueryKey, props.refreshFlag, isFullscreen],
defaultPageSize: 30,
debounceWait: 500,
onSuccess: (data: { list: Array<{ hash: string }>; requestQueryKey: string; isFullscreenRequest: boolean }) => {
if (!data.isFullscreenRequest) return;

const eventKeys = new Set(data.list.map(getEventIdentity));
const previousPage = previousFullscreenPageRef.current;
const nextNewEventKeys = previousPage?.queryKey === data.requestQueryKey ? new Set([...eventKeys].filter((key) => !previousPage.eventKeys.has(key))) : new Set<string>();

previousFullscreenPageRef.current = { queryKey: data.requestQueryKey, eventKeys };
setNewFullscreenEventKeys(nextNewEventKeys);
},
});

const pagination = usePagination({ PAGESIZE_KEY: EVENTS_TABLE_PAGESIZE_CACHE_KEY });
Expand Down Expand Up @@ -293,71 +323,86 @@ export default function AlertTable(props: IProps) {
rowKey={(record) => record.id}
columns={columns}
{...tableProps}
rowClassName={(record: { severity: number; is_recovered: number }) => {
return SEVERITY_COLORS[record.is_recovered ? 3 : record.severity - 1] + '-left-border';
rowClassName={(record: { hash: string; severity: number; is_recovered: number }) => {
const severityClassName = SEVERITY_COLORS[record.is_recovered ? 3 : record.severity - 1] + '-left-border';
const shouldHighlight = isFullscreen && newFullscreenEventKeys.has(getEventIdentity(record));
// 复用主题 violet-3(--fc-violet-3),明暗模式各自取值,避免硬编码色值。
return shouldHighlight ? `${severityClassName} children:!bg-violet-300` : severityClassName;
}}
rowSelection={{
selectedRowKeys: selectedRowKeys,
onChange(selectedRowKeys: number[]) {
setSelectedRowKeys(selectedRowKeys);
},
}}
pagination={{
...pagination,
...tableProps.pagination,
pageSizeOptions: ['30', '100', '200', '500'],
}}
rowActions={(record) => ({
inline: _.compact([
IS_PLUS && alertEscalationEnable
? {
key: 'ack',
icon: record.status === 0 ? 'claim' : 'unclaim',
text: record.status === 0 ? t('claim') : t('unclaim'),
onClick: () => {
ackEvents([record.id], record.status === 0 ? 'ack' : 'unack').then(() => {
setRefreshFlag(_.uniqueId('refresh_'));
});
},
}
: undefined,
!_.includes(['firemap', 'northstar'], record?.rule_prod)
? {
key: 'shield',
icon: 'shield',
text: t('shield'),
onClick: () => {
history.push({
pathname: '/alert-mutes/add',
search: queryString.stringify({
busiGroup: record.group_id,
prod: record.rule_prod,
cate: record.cate,
datasource_ids: [record.datasource_id],
tags: record.tags,
}),
});
},
}
: undefined,
{
key: 'delete',
icon: 'delete',
text: t('common:btn.delete'),
danger: true,
onClick: () =>
deleteAlertEventsModal(
[record.id],
() => {
setSelectedRowKeys(selectedRowKeys.filter((key) => key !== record.id));
setRefreshFlag(_.uniqueId('refresh_'));
rowSelection={
isFullscreen
? undefined
: {
selectedRowKeys: selectedRowKeys,
onChange(selectedRowKeys: number[]) {
setSelectedRowKeys(selectedRowKeys);
},
}
}
pagination={
isFullscreen
? false
: {
...pagination,
...tableProps.pagination,
pageSizeOptions: ['30', '100', '200', '500'],
}
}
rowActions={
isFullscreen
? undefined
: (record) => ({
inline: _.compact([
IS_PLUS && alertEscalationEnable
? {
key: 'ack',
icon: record.status === 0 ? 'claim' : 'unclaim',
text: record.status === 0 ? t('claim') : t('unclaim'),
onClick: () => {
ackEvents([record.id], record.status === 0 ? 'ack' : 'unack').then(() => {
setRefreshFlag(_.uniqueId('refresh_'));
});
},
}
: undefined,
!_.includes(['firemap', 'northstar'], record?.rule_prod)
? {
key: 'shield',
icon: 'shield',
text: t('shield'),
onClick: () => {
history.push({
pathname: '/alert-mutes/add',
search: queryString.stringify({
busiGroup: record.group_id,
prod: record.rule_prod,
cate: record.cate,
datasource_ids: [record.datasource_id],
tags: record.tags,
}),
});
},
}
: undefined,
{
key: 'delete',
icon: 'delete',
text: t('common:btn.delete'),
danger: true,
onClick: () =>
deleteAlertEventsModal(
[record.id],
() => {
setSelectedRowKeys(selectedRowKeys.filter((key) => key !== record.id));
setRefreshFlag(_.uniqueId('refresh_'));
},
t,
),
},
t,
),
},
]) as any,
})}
actionColumn={{ title: t('common:table.operations'), width: 100 }}
]) as any,
})
}
actionColumn={isFullscreen ? undefined : { title: t('common:table.operations'), width: 100 }}
/>
<EventDetailDrawer
showAckBtn
Expand Down
68 changes: 68 additions & 0 deletions src/pages/alertCurEvent/pages/List/FullscreenList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useEffect, useRef, useState } from 'react';
import { Button, ConfigProvider, Space, Tooltip } from 'antd';
import { FullscreenExitOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';

import { TimeRangePickerWithRefresh } from '@/components/TimeRangePicker';
import { IRawTimeRange } from '@/components/TimeRangePicker';

import { NS, TIME_RANGE_CACHE_KEY } from '../../constants';

interface IProps {
title: string;
children: React.ReactNode;
range?: IRawTimeRange;
onRangeChange: (range?: IRawTimeRange) => void;
onRefresh: () => void;
onExit: () => void;
}

export default function FullscreenList({ title, children, range, onRangeChange, onRefresh, onExit }: IProps) {
const { t } = useTranslation(NS);
const [refreshInterval, setRefreshInterval] = useState(30);
const containerRef = useRef<HTMLDivElement>(null);

// 退出全屏时把自动刷新缓存置为 Off。该 key 与常规列表的 TimeRangePickerWithRefresh 共享,
// 因此离开全屏大屏后常规列表也会停止轮询;AutoRefresh 自身的卸载清理已停掉全屏内的定时器。
useEffect(() => {
return () => {
localStorage.setItem(`${TIME_RANGE_CACHE_KEY}_refresh`, '0');
};
}, []);
Comment on lines +27 to +31

return (
<ConfigProvider getPopupContainer={() => containerRef.current ?? document.body}>
<div ref={containerRef} className='flex h-full flex-col bg-fc-100 p-4'>
<div className='mb-4 flex shrink-0 items-center justify-between border-0 border-b border-solid border-[var(--fc-border-color)] bg-fc-100 pb-4'>
<div className='flex items-center text-title'>
<span className='text-[18px] font-semibold leading-7'>{title}</span>
<span className='ml-3 inline-flex items-center gap-2 text-[14px] font-medium leading-[22px] text-green-900'>
<span className='relative inline-flex h-3.5 w-3.5 items-center justify-center' aria-hidden>
<span className='absolute h-2.5 w-2.5 rounded-full border border-green-900 motion-reduce:animate-none animate-[ping_2s_ease-out_infinite]' />
<span className='absolute h-2.5 w-2.5 rounded-full border border-green-900 motion-reduce:animate-none animate-[ping_2s_ease-out_infinite] [animation-delay:0.8s]' />
<span className='relative z-[1] h-2 w-2 rounded-full bg-green-900' />
</span>
<span>LIVE</span>
</span>
</div>
<Space>
<TimeRangePickerWithRefresh
allowClear
value={range}
onChange={onRangeChange}
onRefresh={onRefresh}
intervalSeconds={refreshInterval}
onIntervalSecondsChange={setRefreshInterval}
localKey={TIME_RANGE_CACHE_KEY}
dateFormat='YYYY-MM-DD HH:mm:ss'
/>
<Tooltip title={t('dashboard:exit_full_screen')}>
<Button icon={<FullscreenExitOutlined />} onClick={onExit} />
</Tooltip>
</Space>
</div>
<div className='h-full min-h-0 flex-1'>{children}</div>
</div>
</ConfigProvider>
);
}
Loading