diff --git a/scripts/generate_antd_dark_less.js b/scripts/generate_antd_dark_less.js index 26bff30cd..4f63c5627 100644 --- a/scripts/generate_antd_dark_less.js +++ b/scripts/generate_antd_dark_less.js @@ -50,10 +50,10 @@ function saveLess(filePath, filename, callback) { 'table-header-bg': 'var(--fc-fill-2-5)', 'table-header-color': 'var(--fc-text-3)', 'table-header-sort-bg': 'var(--fc-fill-2-5)', - 'table-body-sort-bg': 'var(--fc-fill-2-5)', + 'table-body-sort-bg': 'var(--fc-fill-2)', 'table-row-hover-bg': 'rgb(var(--fc-fill-5-rgb) / 0.2)', 'table-selected-row-color': 'inherit', - // Keep Less color functions compile-safe; runtime CSS vars are patched in theme/default.less. + // AntD calls color functions on selected/border tokens; patch runtime CSS vars in theme/default.less. 'table-selected-row-bg': 'rgba(228, 228, 231, 0.15)', 'table-body-selected-sort-bg': '@table-selected-row-bg', 'table-selected-row-hover-bg': 'rgba(228, 228, 231, 0.25)', @@ -73,8 +73,8 @@ function saveLess(filePath, filename, callback) { 'table-font-size-md': '14px', 'table-font-size-sm': '@table-font-size', 'table-header-cell-split-color': 'var(--fc-border-color)', - 'table-header-sort-active-bg': 'rgb(var(--fc-fill-5-rgb) / 0.4)', - 'table-fixed-header-sort-active-bg': 'var(--fc-fill-3)', + 'table-header-sort-active-bg': 'var(--fc-fill-2-5)', + 'table-fixed-header-sort-active-bg': 'var(--fc-fill-2-5)', 'border-radius-base': '8px', 'border-radius-sm': '4px', 'checkbox-border-radius': '2px', diff --git a/scripts/generate_antd_gold_less.js b/scripts/generate_antd_gold_less.js index 79b1ddd66..b3c88f02e 100644 --- a/scripts/generate_antd_gold_less.js +++ b/scripts/generate_antd_gold_less.js @@ -52,10 +52,10 @@ function saveLess(filePath, filename, callback) { 'table-header-bg': 'var(--fc-fill-2-5)', 'table-header-color': 'var(--fc-text-3)', 'table-header-sort-bg': 'var(--fc-fill-2-5)', - 'table-body-sort-bg': 'var(--fc-fill-2-5)', + 'table-body-sort-bg': 'var(--fc-fill-2)', 'table-row-hover-bg': 'rgb(var(--fc-fill-5-rgb) / 0.2)', 'table-selected-row-color': 'inherit', - // Keep Less color functions compile-safe; runtime CSS vars are patched in theme/default.less. + // AntD calls color functions on selected/border tokens; patch runtime CSS vars in theme/default.less. 'table-selected-row-bg': 'rgba(228, 228, 231, 0.15)', 'table-body-selected-sort-bg': '@table-selected-row-bg', 'table-selected-row-hover-bg': 'rgba(228, 228, 231, 0.25)', @@ -75,8 +75,8 @@ function saveLess(filePath, filename, callback) { 'table-font-size-md': '14px', 'table-font-size-sm': '@table-font-size', 'table-header-cell-split-color': 'var(--fc-border-color)', - 'table-header-sort-active-bg': 'rgb(var(--fc-fill-5-rgb) / 0.4)', - 'table-fixed-header-sort-active-bg': 'var(--fc-fill-3)', + 'table-header-sort-active-bg': 'var(--fc-fill-2-5)', + 'table-fixed-header-sort-active-bg': 'var(--fc-fill-2-5)', 'border-radius-base': '8px', 'border-radius-sm': '4px', 'checkbox-border-radius': '2px', diff --git a/src/App.less b/src/App.less index 11813ab0b..8eba4dc16 100644 --- a/src/App.less +++ b/src/App.less @@ -381,23 +381,65 @@ input::placeholder { } } -// antd表格排序icon特殊处理 -.ant-table-column-has-sorters .ant-table-column-sorter-inner { +.ant-table-thead > tr > th { + .ant-table-column-sorters { + justify-content: flex-start; + gap: 4px; + min-width: 0; + } + + .ant-table-column-title { + flex: 0 1 auto; + min-width: 0; + } + + .ant-table-column-sorter { + flex: none; + margin-left: 0; + } +} + +// 2026-05 table 设计规范:排序 icon 替换为线性箭头,hover 横向并排 ↑↓,已排序只显示当前方向 +.ant-table-column-sorter { + .ant-table-column-sorter-inner { + flex-direction: row; + align-items: center; + gap: 2px; + } + .ant-table-column-sorter-up, .ant-table-column-sorter-down { - transition: all 0.2s; + width: 12px; + height: 12px; + background-color: currentColor; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + + svg { + display: none; + } } - &:has(.ant-table-column-sorter-up.active) { - .ant-table-column-sorter-down { - opacity: 0; - } + .ant-table-column-sorter-up { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='64 64 896 896'%3E%3Cpath d='M868 545.5L536.1 163a31.96 31.96 0 00-48.3 0L156 545.5a7.97 7.97 0 006 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='64 64 896 896'%3E%3Cpath d='M868 545.5L536.1 163a31.96 31.96 0 00-48.3 0L156 545.5a7.97 7.97 0 006 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z'/%3E%3C/svg%3E"); } - &:has(.ant-table-column-sorter-down.active) { - .ant-table-column-sorter-up { - opacity: 0; - } + .ant-table-column-sorter-down { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='64 64 896 896'%3E%3Cpath d='M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0048.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='64 64 896 896'%3E%3Cpath d='M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0048.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z'/%3E%3C/svg%3E"); + } +} + +// 已排序时,非活跃方向的箭头隐藏不占空间 +.ant-table-column-has-sorters .ant-table-column-sorter-inner { + &:has(.ant-table-column-sorter-up.active) .ant-table-column-sorter-down, + &:has(.ant-table-column-sorter-down.active) .ant-table-column-sorter-up { + display: none; } } diff --git a/src/components/TableActionDropdown/index.tsx b/src/components/TableActionDropdown/index.tsx new file mode 100644 index 000000000..a0c269dcc --- /dev/null +++ b/src/components/TableActionDropdown/index.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Button } from 'antd'; +import type { ButtonProps } from 'antd/lib/button'; +import { + CheckCircle, + Copy, + ExternalLink, + Eye, + Link as LinkIcon, + MoreVertical, + Network, + Pencil, + Play, + Search, + Settings, + ShieldCheck, + Sparkles, + Trash2, +} from 'lucide-react'; +import classNames from 'classnames'; +import { Link, LinkProps } from 'react-router-dom'; + +const tableActionIconMap = { + default: CheckCircle, + edit: Pencil, + view: Eye, + settings: Settings, + access: Network, + permission: ShieldCheck, + copy: Copy, + delete: Trash2, + run: Play, + search: Search, + open: ExternalLink, + link: LinkIcon, + ai: Sparkles, +}; + +export type TableActionIconName = keyof typeof tableActionIconMap; + +export function TableActionIcon({ name }: { name: TableActionIconName }) { + const Icon = tableActionIconMap[name]; + return ; +} + +interface TableActionButtonProps extends Omit { + actionIcon?: TableActionIconName; + icon?: React.ReactNode; +} + +export function TableActionButton({ actionIcon, icon, className, type = 'link', ...rest }: TableActionButtonProps) { + return ( + + )} + {(IS_PLUS || !_.includes(['firemap', 'northstar'], record?.rule_prod)) && } - + } > - - {record.cate === 'prometheus' && anomalyEnabled === true && ( -
- {t('brain_result_btn')} -
- )} - + onCancel() {}, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, diff --git a/src/pages/alertRules/List/constants.ts b/src/pages/alertRules/List/constants.ts index dac1b27d8..24196474d 100644 --- a/src/pages/alertRules/List/constants.ts +++ b/src/pages/alertRules/List/constants.ts @@ -24,16 +24,6 @@ export const defaultColumnsConfigs = [ i18nKey: 'table.status', visible: true, }, - { - name: 'cate', - i18nKey: 'table.cate', - visible: true, - }, - { - name: 'datasource_ids', - i18nKey: 'table.datasource_ids', - visible: false, - }, { name: 'name', i18nKey: 'table.name', @@ -44,6 +34,11 @@ export const defaultColumnsConfigs = [ i18nKey: 'table.severity', visible: false, }, + { + name: 'datasource_ids', + i18nKey: 'table.datasource_ids', + visible: false, + }, { name: 'append_tags', i18nKey: 'table.append_tags', @@ -69,11 +64,6 @@ export const defaultColumnsConfigs = [ i18nKey: 'common:table.username', visible: true, }, - { - name: 'update_by_nickname', - i18nKey: 'common:table.nickname', - visible: true, - }, { name: 'disabled', i18nKey: 'table.disabled', diff --git a/src/pages/alertRules/List/index.tsx b/src/pages/alertRules/List/index.tsx index 245500e5f..45a7c4ef9 100644 --- a/src/pages/alertRules/List/index.tsx +++ b/src/pages/alertRules/List/index.tsx @@ -95,7 +95,7 @@ export default function List(props: ListProps) { }, [gids, refreshFlag]); return ( -
+
{ prod: string; severities: number[]; update_at: number; - update_by: number; + update_by: string; + update_by_nickname?: string; cur_event_count: number; } diff --git a/src/pages/builtInComponents/AlertRules/index.tsx b/src/pages/builtInComponents/AlertRules/index.tsx index c2fb266a1..c67cce56f 100644 --- a/src/pages/builtInComponents/AlertRules/index.tsx +++ b/src/pages/builtInComponents/AlertRules/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useContext, useEffect } from 'react'; import _ from 'lodash'; import { Table, Space, Button, Input, Select, Dropdown, Menu, Modal, Tag, message } from 'antd'; -import { SearchOutlined, MoreOutlined } from '@ant-design/icons'; +import { SearchOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { useDebounceEffect } from 'ahooks'; @@ -10,6 +10,8 @@ import Export from '@/pages/dashboard/List/Export'; import AuthorizationWrapper from '@/components/AuthorizationWrapper'; import { CommonStateContext } from '@/App'; import { HelpLink } from '@/components/pageLayout'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; +import Tags from '@/components/TableTags/Tags'; import { RuleType } from './types'; import Import from './Import'; import { getPayloads, deletePayloads, getCates } from '../services'; @@ -205,29 +207,17 @@ export default function index(props: Props) { render: (val) => { const tags = _.compact(_.split(val, ' ')); return ( - - {_.map(tags, (tag, idx) => { - return ( - { - const queryItem = _.compact(_.split(filter.query, ' ')); - if (_.includes(queryItem, tag)) return; - setFilter((filter) => { - return { - ...filter, - query: filter.query ? filter.query + ' ' + tag : tag, - }; - }); - }} - > - {tag} - - ); - })} - + { + const queryItem = _.compact(_.split(filter.query, ' ')); + if (_.includes(queryItem, tag)) return; + setFilter((filter) => ({ + ...filter, + query: filter.query ? filter.query + ' ' + tag : tag, + })); + }} + /> ); }, }, @@ -245,14 +235,19 @@ export default function index(props: Props) { }, { title: t('common:table.operations'), - width: 100, + width: 64, + fixed: 'right' as const, render: (record) => { return ( - { Import({ data: formatBeautifyJson(record.content), @@ -264,10 +259,11 @@ export default function index(props: Props) { }} > {t('import_to_buisGroup')} - + - { Export({ data: formatBeautifyJson(record.content, 'array'), @@ -275,12 +271,13 @@ export default function index(props: Props) { }} > {t('common:btn.export')} - + {record.updated_by !== 'system' && ( - { PayloadFormModal({ darkMode, @@ -297,38 +294,40 @@ export default function index(props: Props) { }} > {t('common:btn.edit')} - + )} {record.updated_by !== 'system' && ( - - - + <> + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deletePayloads([record.id]).then(() => { + fetchData(); + fetchCates(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + )} } > - - + <> + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deletePayloads([record.id]).then(() => { + fetchData(); + fetchCates(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + )} } > - - + <> + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deletePayloads([record.id]).then(() => { + fetchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + )} } > - - + <> + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deleteMetrics([record.id]).then(() => { + message.success(t('common:success.delete')); + setRefreshFlag(_.uniqueId('refreshFlag_')); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + )} } > - - - )} - {gids && gids !== '-1' && ( - - - - )} + }, + { + title: t('common:table.operations'), + width: 64, + fixed: 'right' as const, + render: (text: string, record: DashboardType) => { + return ( + + {gids !== '-1' && ( - + {t('common:btn.clone')} + - {gids !== '-1' && ( + )} + + { + const exportData = await getDashboard(record.id); + Export({ + data: exportDataStringify(exportData), + }); + }} + > + {t('common:btn.export')} + + + {gids !== '-1' && ( + <> + - + - )} - - } - > - - )} - - {record.plugin_type === 'cloudwatch' && ( - + }} + > + {record.status === 'enabled' ? t('disable') : t('enable')} + + + {record.plugin_type === 'cloudwatch' && ( + + + + )} + {record.status === 'disabled' && ( + <> + - + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deleteDataSourceById(record.id).then(() => { + message.success(t('common:success.delete')); + setRefresh((oldVal) => !oldVal); + }); + }, + }); + }} + > + {t('common:btn.delete')} + - - } - > - + )} + - + } > - + )} + - + } > - - {t('executions.title')} - + + + { + setEventPipelineDrawerState({ + visible: true, + action: 'clone', + data: _.omit(item, 'id'), + }); + }} + > + {t('common:btn.clone')} + + + + { + setEventPipelineDrawerState({ + visible: true, + action: 'edit', + id: item.id, + }); + }} + > + {t('common:btn.edit')} + + + + + {t('executions.title')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteItems([item.id]).then(() => { + featchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, diff --git a/src/pages/historyEvents/ListNG/index.tsx b/src/pages/historyEvents/ListNG/index.tsx index fba35297d..ca0c396f5 100644 --- a/src/pages/historyEvents/ListNG/index.tsx +++ b/src/pages/historyEvents/ListNG/index.tsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from 'react'; -import { SearchOutlined, MoreOutlined } from '@ant-design/icons'; +import { SearchOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import moment from 'moment'; import _ from 'lodash'; @@ -21,6 +21,7 @@ import EventDetailDrawer from '@/pages/alertCurEvent/pages/List/EventDetailDrawe import usePagination from '@/components/usePagination'; import { getEventById } from '@/pages/alertCurEvent/services'; import deleteAlertEventsModal from '@/pages/alertCurEvent/utils/deleteAlertEventsModal'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; import exportEvents, { downloadFile } from '../exportEvents'; import { SeverityColor } from '../../event'; @@ -184,14 +185,14 @@ const Event = (props: Props) => { { title: t('common:table.operations'), fixed: 'right' as const, + width: 64, render(record) { return ( -
+
{IS_PLUS && ( @@ -206,10 +207,8 @@ const Event = (props: Props) => { )} {!_.includes(['firemap', 'northstar'], record?.rule_prod) && ( - + )} {!hideDeleteEventButton && ( - - - + <> + + + + deleteAlertEventsModal( + [record.id], + () => { + setRefreshFlag(_.uniqueId('refresh_')); + }, + t, + ) + } + > + {t('common:btn.delete')} + + + )} } > -
); diff --git a/src/pages/hosts/pages/List/List.tsx b/src/pages/hosts/pages/List/List.tsx index 56bb3683a..62959c9a5 100644 --- a/src/pages/hosts/pages/List/List.tsx +++ b/src/pages/hosts/pages/List/List.tsx @@ -14,6 +14,7 @@ import { copy2ClipBoard } from '@/utils'; import getTextWidth from '@/utils/getTextWidth'; import usePagination from '@/components/usePagination'; import DocumentDrawer from '@/components/DocumentDrawer'; +import Tags from '@/components/TableTags/Tags'; import HostsSelect from '@/pages/targets/components/HostsSelect'; import Explorer from '@/pages/targets/components/Explorer'; import EditBusinessGroups from '@/pages/targets/components/EditBusinessGroups'; @@ -31,7 +32,6 @@ import { NS } from '../../constants'; import { Item, OperateType } from '../../types'; import { getList } from '../../services'; import VersionIcon from './VersionIcon'; -import Tags from './Tags'; import { formatBeatTimeDisplay } from './formatBeatTimeDisplay'; const downtimeOptions = [1, 2, 3, 5, 10, 30]; @@ -527,7 +527,7 @@ export default function List(props: Props) { { if (!_.includes(params.query, tag)) { const val = params.query ? `${params.query.trim()} ${tag}` : tag; @@ -560,7 +560,7 @@ export default function List(props: Props) { { if (!_.includes(params.query, tag)) { const val = params.query ? `${params.query.trim()} ${tag}` : tag; @@ -584,7 +584,7 @@ export default function List(props: Props) { } return (
- +
); }, diff --git a/src/pages/hosts/pages/List/Tags.tsx b/src/pages/hosts/pages/List/Tags.tsx deleted file mode 100644 index 6a6f11ea0..000000000 --- a/src/pages/hosts/pages/List/Tags.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useRef, useState, useLayoutEffect } from 'react'; -import { Button, Popover } from 'antd'; -import { CopyOutlined } from '@ant-design/icons'; -import { Trans } from 'react-i18next'; - -import { copy2ClipBoard } from '@/utils'; - -import { NS } from '../../constants'; - -interface Props { - type: 'outline' | 'fill'; - bgColor?: string; // 背景颜色,仅在 type 为 'fill' 时生效 - fontColor?: string; // 字体颜色,仅在 type 为 'fill' 时生效 - data: string[]; - onTagClick?: (tag: string) => void; -} - -const GAP = 2; - -/** - * 核心布局算法:根据实际测量的 tag 宽度计算可见数量 - */ -function calcLayout(tagWidths: number[], overflowTagWidth: number, containerWidth: number): { visibleCount: number; overflowCount: number } { - if (!tagWidths.length || containerWidth <= 0) { - return { visibleCount: tagWidths.length, overflowCount: 0 }; - } - - const widths = tagWidths.map((w) => Math.min(w, containerWidth)); - - // ── 第一行 ── - let row1End = -1; - let rem = containerWidth; - for (let i = 0; i < widths.length; i++) { - const needed = i === 0 ? widths[i] : GAP + widths[i]; - if (rem >= needed) { - rem -= needed; - row1End = i; - } else { - break; - } - } - - if (row1End === widths.length - 1) { - return { visibleCount: widths.length, overflowCount: 0 }; - } - - // ── 第二行 ── - const r2start = row1End + 1; - rem = containerWidth; - let r2count = 0; - - for (let i = r2start; i < widths.length; i++) { - const isFirst = i === r2start; - const needed = isFirst ? widths[i] : GAP + widths[i]; - const isLast = i === widths.length - 1; - - if (isLast) { - if (rem >= needed) r2count++; - break; - } - - // 非最后一个:放置当前 tag 后,还需在第二行留出 overflow tag 的空间 - if (rem >= needed + GAP + overflowTagWidth) { - rem -= needed; - r2count++; - } else { - break; - } - } - - const visibleCount = r2start + r2count; - return { visibleCount, overflowCount: widths.length - visibleCount }; -} - -export default function Tags(props: Props) { - const { type = 'outline', data, onTagClick } = props; - const bgColor = props.bgColor || 'var(--fc-violet-3)'; - const fontColor = props.fontColor || 'var(--fc-violet-11)'; - const containerRef = useRef(null); - const tagMeasureRefs = useRef<(HTMLSpanElement | null)[]>([]); - const overflowMeasureRef = useRef(null); - const [layout, setLayout] = useState({ visibleCount: data.length, overflowCount: 0 }); - - // fill 模式下通过 inline style 设置动态颜色(Tailwind 不支持动态值) - const fillStyle: React.CSSProperties | undefined = - type === 'fill' - ? { - backgroundColor: bgColor, - backgroundClip: 'padding-box', - borderColor: bgColor, - color: fontColor, - } - : undefined; - - // tag 的 Tailwind 基础类(测量层和渲染层共用) - // p-[6px] border border-[var(--fc-border-color)] rounded-2xl leading-none whitespace-nowrap box-border - const tagBaseClass = `inline-block px-[6px] py-[4px] border ${type === 'fill' ? '' : 'border-[var(--fc-border-color)]'} rounded-2xl leading-none whitespace-nowrap box-border ${ - onTagClick ? 'cursor-pointer' : '' - }`; - - // 可见层额外加溢出省略 - const visibleTagClass = `${tagBaseClass} overflow-hidden text-ellipsis max-w-full shrink-0`; - - useLayoutEffect(() => { - const el = containerRef.current; - if (!el) return; - - const compute = () => { - const containerWidth = el.getBoundingClientRect().width; - if (containerWidth <= 0) return; - - const tagWidths = tagMeasureRefs.current.slice(0, data.length).map((span) => (span ? Math.ceil(span.getBoundingClientRect().width) : containerWidth)); - const overflowTagWidth = overflowMeasureRef.current ? Math.ceil(overflowMeasureRef.current.getBoundingClientRect().width) : 40; - - setLayout(calcLayout(tagWidths, overflowTagWidth, containerWidth)); - }; - - const ro = new ResizeObserver(compute); - ro.observe(el); - compute(); - - return () => ro.disconnect(); - }, [data]); - - const { visibleCount, overflowCount } = layout; - - return ( -
- {/* 隐藏测量层:绝对定位不占空间,用于获取各 tag 的真实渲染宽度 */} -
- {data.map((tag, i) => ( - { - tagMeasureRefs.current[i] = el; - }} - className={tagBaseClass} - > - {tag} - - ))} - {/* 用最大计数值预估 overflow tag 宽度上限 */} - - +{data.length} - -
- - {/* 可见布局层 */} -
- {data.slice(0, visibleCount).map((tag, i) => ( - { - e.stopPropagation(); - onTagClick?.(tag); - }} - > - {tag} - - ))} - {overflowCount > 0 && ( - - -
- } - content={ -
- {data.map((tag, i) => ( -
-
{ - e.stopPropagation(); - onTagClick?.(tag); - }} - > - {tag} -
-
- ))} -
- } - > - { - e.stopPropagation(); - }} - > - +{overflowCount} - - - )} -
-
- ); -} diff --git a/src/pages/log/IndexPatterns/index.tsx b/src/pages/log/IndexPatterns/index.tsx index 4b4491e06..17fee7815 100644 --- a/src/pages/log/IndexPatterns/index.tsx +++ b/src/pages/log/IndexPatterns/index.tsx @@ -15,7 +15,7 @@ * */ import React, { useState, useEffect, useContext } from 'react'; -import { Button, Input, Popconfirm, Space, Table, Tag, message } from 'antd'; +import { Button, Input, Modal, Dropdown, Menu, Table, Tag, message } from 'antd'; import _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; @@ -30,6 +30,7 @@ import './locale'; import { SearchOutlined } from '@ant-design/icons'; import EditField from './EditField'; import { useQuery } from '@/utils'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; export default function Servers() { const { t } = useTranslation('es-index-patterns'); @@ -142,66 +143,89 @@ export default function Servers() { }, { title: t('common:table.operations'), - width: 160, + width: 64, + fixed: 'right' as const, render: (record) => { return ( - - { - if (record) { - EditField({ - id: record.id, - datasourceList, - onOk(values, name) { - console.log('values', values); - const newFieldConfig = { - ...values, - version: 2, - }; - putESIndexPattern(record.id, { - ..._.omit(record, ['fieldConfig', 'id']), - fields_format: JSON.stringify(newFieldConfig), - name, - }).then(() => { - fetchData(); - message.success(t('common:success.save')); + + + { + if (record) { + EditField({ + id: record.id, + datasourceList, + onOk(values, name) { + console.log('values', values); + const newFieldConfig = { + ...values, + version: 2, + }; + putESIndexPattern(record.id, { + ..._.omit(record, ['fieldConfig', 'id']), + fields_format: JSON.stringify(newFieldConfig), + name, + }).then(() => { + fetchData(); + message.success(t('common:success.save')); + }); + }, + }); + } + }} + > + {t('common:btn.config')} + + + + { + FormModal({ + mode: 'edit', + initialValues: record, + indexPatterns: data, + datasourceList: groupedDatasourceList.elasticsearch, + onOk: () => { + fetchData(); + }, }); - }, - }); - } - }} - > - {t('common:btn.config')} - - { - FormModal({ - mode: 'edit', - initialValues: record, - indexPatterns: data, - datasourceList: groupedDatasourceList.elasticsearch, - onOk: () => { - fetchData(); - }, - }); - }} - > - {t('common:btn.edit')} - - { - deleteESIndexPattern(record.id).then(() => { - message.success(t('common:success.delete')); - fetchData(); - }); - }} - > - - - + }} + > + {t('common:btn.edit')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteESIndexPattern(record.id).then(() => { + message.success(t('common:success.delete')); + fetchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + +
); }, }, diff --git a/src/pages/metricsBuiltin/List.tsx b/src/pages/metricsBuiltin/List.tsx index c33ecfa87..d44154aed 100644 --- a/src/pages/metricsBuiltin/List.tsx +++ b/src/pages/metricsBuiltin/List.tsx @@ -19,13 +19,14 @@ import _ from 'lodash'; import { useAntdTable, useDebounceFn } from 'ahooks'; import { useTranslation } from 'react-i18next'; import { Space, Table, Button, Input, Dropdown, Select, message, Modal, Tooltip, Menu, Tag } from 'antd'; -import { SettingOutlined, DownOutlined, SearchOutlined, EyeOutlined, MoreOutlined } from '@ant-design/icons'; +import { SettingOutlined, DownOutlined, SearchOutlined, EyeOutlined } from '@ant-design/icons'; import { ColumnType } from 'antd/lib/table'; import { CommonStateContext } from '@/App'; import Markdown from '@/components/Markdown'; import PageLayout from '@/components/pageLayout'; import usePagination from '@/components/usePagination'; +import Tags from '@/components/TableTags/Tags'; import RefreshIcon from '@/components/RefreshIcon'; import OrganizeColumns, { getDefaultColumnsConfigs, setDefaultColumnsConfigs, ajustColumns } from '@/components/OrganizeColumns'; import { getUnitLabel, buildUnitOptions } from '@/pages/dashboard/Components/UnitPicker/utils'; @@ -33,6 +34,7 @@ import { getMenuPerm } from '@/services/common'; import Collapse from '@/pages/monitor/object/metricViews/components/Collapse'; import { getComponents, Component } from '@/pages/builtInComponents/services'; import { getDefaultDatasourceValue } from '@/utils'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; import { getMetrics, Record, Filter, getTypes, getCollectors, deleteMetrics, buildLabelFilterAndExpression } from './services'; import { defaultColumnsConfigs, LOCAL_STORAGE_KEY } from './constants'; @@ -230,13 +232,7 @@ export default function index() { title: t('extra_fields'), dataIndex: 'extra_fields', render: (val) => { - return ( - - {_.map(val, (item) => { - return {`${item.name}: ${item.value}`}; - })} - - ); + return `${item.name}: ${item.value}`)} maxWidth={180} />; }, }, { @@ -269,14 +265,20 @@ export default function index() { { title: t('common:table.operations'), dataIndex: 'operator', + width: 64, + fixed: 'right' as const, render: (data, record: any) => { return ( {actionAuth.add && ( - { setFormDrawerData({ open: true, @@ -287,12 +289,13 @@ export default function index() { }} > {t('common:btn.clone')} - + )} {actionAuth.edit && record.updated_by !== 'system' && ( - { setFormDrawerData({ open: true, @@ -303,34 +306,37 @@ export default function index() { }} > {t('common:btn.edit')} - + )} {actionAuth.delete && record.updated_by !== 'system' && ( - - - + <> + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk() { + deleteMetrics([record.id]).then(() => { + message.success(t('common:success.delete')); + setRefreshFlag(_.uniqueId('refreshFlag_')); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + )} {record.expression_type === 'metric_name' && ( - { setNewMetricExplorerDrawerState((prev) => { return { @@ -342,13 +348,13 @@ export default function index() { }} > {t('laset_over_time')} - + )} } > - - + + + + {t('common:btn.clone')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteItems([record.id]).then(() => { + message.success(t('common:success.delete')); + fetchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, diff --git a/src/pages/notificationChannels/pages/ListNG/index.tsx b/src/pages/notificationChannels/pages/ListNG/index.tsx index fafd5aedd..221689732 100644 --- a/src/pages/notificationChannels/pages/ListNG/index.tsx +++ b/src/pages/notificationChannels/pages/ListNG/index.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState } from 'react'; -import { Input, Select, Space, Table, Button, Modal, Switch, message, Tooltip } from 'antd'; -import { NotificationOutlined, PlusOutlined, CopyOutlined, DeleteOutlined } from '@ant-design/icons'; +import { Input, Select, Space, Table, Button, Modal, Switch, message, Tooltip, Dropdown, Menu } from 'antd'; +import { NotificationOutlined, PlusOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { map, upperCase, includes, filter } from 'lodash'; import { Link } from 'react-router-dom'; @@ -14,6 +14,7 @@ import { Import, Export } from '@/components/ExportImport'; import { NS, NOTIFICATION_CHANNEL_TYPES } from '../../constants'; import { getItems, putItem, deleteItems, postItems } from '../../services'; import { ChannelItem } from '../../types'; +import { TableActionButton, TableActionLink, TableActionTrigger } from '@/components/TableActionDropdown'; interface Filter { search?: string; @@ -294,40 +295,49 @@ export default function index() { }, { title: t('common:table.operations'), - width: 100, + width: 64, + fixed: 'right' as const, render: (record) => { return ( - - -
diff --git a/src/pages/notificationRules/pages/List.tsx b/src/pages/notificationRules/pages/List.tsx index 07abc8526..8c866fdc1 100644 --- a/src/pages/notificationRules/pages/List.tsx +++ b/src/pages/notificationRules/pages/List.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { Table, Space, Button, Switch, Modal, Input, Tag, Tooltip } from 'antd'; +import { Table, Space, Button, Switch, Modal, Input, Dropdown, Menu } from 'antd'; import { NotificationOutlined, SearchOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import _ from 'lodash'; @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'; import { IS_PLUS } from '@/utils/constant'; import PageLayout from '@/components/pageLayout'; +import Tags from '@/components/TableTags/Tags'; import { getSimplifiedItems as getNotificationChannels } from '@/pages/notificationChannels/services'; import { getTeamInfoList } from '@/services/manage'; import usePagination from '@/components/usePagination'; @@ -15,6 +16,7 @@ import usePagination from '@/components/usePagination'; import { getItems, putItem, deleteItems } from '../services'; import { NS, CN, TABLE_PAGINATION_CACHE_KEY } from '../constants'; import { RuleItem } from '../types'; +import { TableActionButton, TableActionLink, TableActionTrigger } from '@/components/TableActionDropdown'; interface Filter { search?: string; @@ -138,39 +140,62 @@ export default function List() { { title: t('notification_configuration.channel'), dataIndex: 'notify_configs', - render: (val) => { - return _.map(val, (item) => { - return ( - - {item.channel ?? item.channel_id} - - ); - }); + render: (val: { channel_id: number; channel?: string }[]) => { + return ( + item.channel_id} + getLabel={(item) => item.channel ?? String(item.channel_id)} + getTooltipTitle={(item) => (typeof item === 'string' ? undefined : item.channel ? undefined : t('channel_invalid_tip'))} + /> + ); }, }, { title: t('user_group_ids'), dataIndex: 'user_group_ids', - render: (val) => { - return _.map(val, (item) => { - const name = _.find(userGroups, { id: item })?.name; - return ( - - {name ?? item} - - ); - }); + render: (val: number[]) => { + return ( + item} + getLabel={(item) => { + const id = typeof item === 'number' ? item : Number(item); + return _.find(userGroups, { id })?.name ?? String(item); + }} + getTooltipTitle={(item) => { + const id = typeof item === 'number' ? item : Number(item); + return _.find(userGroups, { id })?.name ? undefined : t('user_group_id_invalid_tip'); + }} + /> + ); }, }, { title: t('common:table.update_by'), dataIndex: 'update_by', + render: (val, record: any) => ( +
+
{val}
+ {record.update_by_nickname &&
{record.update_by_nickname}
} +
+ ), }, { title: t('common:table.update_at'), dataIndex: 'update_at', render: (val) => { - return moment.unix(val).format('YYYY-MM-DD HH:mm:ss'); + const m = moment.unix(val); + return ( +
+
{m.format('YYYY-MM-DD')}
+
{m.format('HH:mm:ss')}
+
+ ); }, }, { @@ -203,48 +228,50 @@ export default function List() { }, { title: t('common:table.operations'), - width: 160, + width: 64, + fixed: 'right' as const, render: (record) => { return ( - - - {t('common:btn.edit')} - - - {t('common:btn.clone')} - - - + + + + {t('common:btn.edit')} + + + + + {t('common:btn.clone')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteItems([record.id]).then(() => { + fetchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, diff --git a/src/pages/recordingRules/PageTable.tsx b/src/pages/recordingRules/PageTable.tsx index 7da7fa058..89271993e 100644 --- a/src/pages/recordingRules/PageTable.tsx +++ b/src/pages/recordingRules/PageTable.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useMemo, useContext } from 'react'; -import { Button, Modal, message, Dropdown, Table, Switch, Select, Space, Tag } from 'antd'; +import { Button, Modal, message, Dropdown, Table, Switch, Select, Space, Tag, Menu } from 'antd'; import { useHistory } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ColumnType } from 'antd/lib/table'; @@ -13,6 +13,8 @@ import { strategyItem, strategyStatus } from '@/store/warningInterface'; import { deleteRecordingRule } from '@/services/recording'; import { CommonStateContext } from '@/App'; import localeCompare from '@/pages/dashboard/Renderer/utils/localeCompare'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; +import Tags from '@/components/TableTags/Tags'; import EditModal from './components/editModal'; import Import from './components/Import'; import Export from './components/Export'; @@ -113,49 +115,36 @@ const PageTable: React.FC = ({ gids }) => { getRecordingRules(); }; - const columns: ColumnType[] = _.concat( - businessGroup.isLeaf && gids !== '-2' - ? [] - : ([ - { - title: t('common:business_group'), - dataIndex: 'group_id', - width: 100, - render: (id) => { - return _.find(busiGroups, { id })?.name; - }, - }, - ] as any), - [ - { - title: t('common:datasource.name'), - dataIndex: 'datasource_ids', - render: (data) => { - return _.map( - _.filter(data, (item) => { - return _.find(groupedDatasourceList.prometheus, { id: item }); - }), - (item) => { - if (item === 0) { - return ( - - $all - - ); - } - return {_.find(groupedDatasourceList.prometheus, { id: item })?.name!}; - }, - ); - }, + const columns: ColumnType[] = _.concat([ + { + title: t('common:datasource.name'), + dataIndex: 'datasource_ids', + render: (data) => { + return ( + item === 0 || _.find(groupedDatasourceList.prometheus, { id: item })), + (item) => { + if (item === 0) return '$all'; + return _.find(groupedDatasourceList.prometheus, { id: item })?.name!; + }, + )} + /> + ); }, - { - title: t('name'), - dataIndex: 'name', - sorter: (a, b) => { - return localeCompare(a.name, b.name); - }, - render: (data, record) => { - return ( + }, + { + title: t('name'), + dataIndex: 'name', + sorter: (a, b) => { + return localeCompare(a.name, b.name); + }, + render: (data, record) => { + const groupName = !(businessGroup.isLeaf && gids !== '-2') ? _.find(busiGroups, { id: record.group_id })?.name : undefined; + return ( +
{ @@ -164,100 +153,117 @@ const PageTable: React.FC = ({ gids }) => { > {data}
- ); - }, + {groupName && {groupName}} +
+ ); }, - { - title: t('cron_pattern'), - dataIndex: 'cron_pattern', + }, + { + title: t('cron_pattern'), + dataIndex: 'cron_pattern', + }, + { + title: t('append_tags'), + dataIndex: 'append_tags', + render: (data) => { + const array = data || []; + return ; }, - { - title: t('append_tags'), - dataIndex: 'append_tags', - render: (data) => { - const array = data || []; - return ( - (array.length && - array.map((tag: string, index: number) => { - return ( - - {tag} - - ); - })) ||
- ); - }, + }, + { + title: t('common:table.update_at'), + dataIndex: 'update_at', + sorter: (a, b) => { + return a.update_at - b.update_at; }, - { - title: t('common:table.update_at'), - dataIndex: 'update_at', - sorter: (a, b) => { - return a.update_at - b.update_at; - }, - render: (text: number) => moment.unix(text).format('YYYY-MM-DD HH:mm:ss'), + render: (text: number) => { + const m = moment.unix(text); + return ( +
+
{m.format('YYYY-MM-DD')}
+
{m.format('HH:mm:ss')}
+
+ ); }, - { - title: t('disabled'), - dataIndex: 'disabled', - render: (disabled, record) => ( - { - const { id, disabled } = record; - updateRecordingRules( - { - ids: [id], - fields: { - disabled: !disabled ? 1 : 0, - }, + }, + { + title: t('disabled'), + dataIndex: 'disabled', + render: (disabled, record) => ( + { + const { id, disabled } = record; + updateRecordingRules( + { + ids: [id], + fields: { + disabled: !disabled ? 1 : 0, }, - record.group_id, - ).then(() => { - refreshList(); - }); - }} - /> - ), - }, - { - title: t('common:table.operations'), - dataIndex: 'operator', - render: (data, record) => { - return ( -
-
{ - handleClickEdit(record.id, true); - }} - > - {t('common:btn.clone')} -
-
{ - confirm({ - title: t('common:confirm.delete'), - onOk: () => { - deleteRecordingRule([record.id], record.group_id).then(() => { - message.success(t('common:success.delete')); - refreshList(); - }); - }, + }, + record.group_id, + ).then(() => { + refreshList(); + }); + }} + /> + ), + }, + { + title: t('common:table.operations'), + dataIndex: 'operator', + width: 64, + fixed: 'right' as const, + render: (data, record) => { + return ( + + + { + handleClickEdit(record.id, true); + }} + > + {t('common:btn.clone')} + + + + + { + confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteRecordingRule([record.id], record.group_id).then(() => { + message.success(t('common:success.delete')); + refreshList(); + }); + }, - onCancel() {}, - }); - }} - > - {t('common:btn.delete')} -
-
- ); - }, + onCancel() {}, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + + ); }, - ], - ); + }, + ]); const toOneArr = (arr, res, name) => { arr.forEach((ele) => { diff --git a/src/pages/targets/List.tsx b/src/pages/targets/List.tsx index 19c6745ed..c8eba3df1 100644 --- a/src/pages/targets/List.tsx +++ b/src/pages/targets/List.tsx @@ -20,6 +20,7 @@ import TargetMetaDrawer from './TargetMetaDrawer'; import Explorer from './components/Explorer'; import EditBusinessGroups from './components/EditBusinessGroups'; import HostsSelect from './components/HostsSelect'; +import Tags from '@/components/TableTags/Tags'; // @ts-ignore import CollectsDrawer from 'plus:/pages/collects/CollectsDrawer'; @@ -46,8 +47,9 @@ export interface ITargetProps { id: number; cluster: string; group_id: number; - group_objs: object[] | null; + group_objs: { name: string }[] | null; ident: string; + host_ip?: string; note: string; tags: string[]; beat_time: number; @@ -96,7 +98,7 @@ export default function List(props: IProps) { { title: ( - {t('common:table.ident')} + {t('host_ip')} { + render: (text, record: ITargetProps) => { + const groupNames = _.map(record.group_objs, 'name'); return ( -
- - {import.meta.env['VITE_IS_PRO'] && ( - - { - setCollectsDrawerVisible(true); - setCollectsDrawerIdent(text); - }} - /> - - )} +
+
+ + {import.meta.env['VITE_IS_PRO'] && ( + + { + setCollectsDrawerVisible(true); + setCollectsDrawerIdent(text); + }} + /> + + )} +
+ + {record.host_ip && record.host_ip !== text && {text}} + {!_.isEmpty(groupNames) ? {groupNames.join(' / ')} : {t('common:not_grouped')}} + {record.note && {record.note}} +
); }, @@ -178,13 +188,7 @@ export default function List(props: IProps) { _.forEach(columnsConfigs, (item) => { if (!item.visible) return; - if (item.name === 'host_ip') { - columns.push({ - title: t('host_ip'), - dataIndex: 'host_ip', - className: 'n9e-hosts-table-column-ip', - }); - } + if (item.name === 'host_ip' || item.name === 'group_obj') return; if (item.name === 'host_tags') { columns.push({ title: ( @@ -201,30 +205,19 @@ export default function List(props: IProps) { showTitle: false, }, render(tagArr) { - const content = - tagArr && - tagArr.map((item) => ( - { - if (!tableQueryContent.includes(item)) { - isAddTagToQueryInput.current = true; - const val = tableQueryContent ? `${tableQueryContent.trim()} ${item}` : item; - setTableQueryContent(val); - setSearchVal(val); - } - }} - > - {item} - - )); return ( - tagArr && ( - document.body} overlayClassName='mon-manage-table-tooltip'> - {content} - - ) + + data={tagArr} + maxWidth={180} + onTagClick={(item) => { + if (!tableQueryContent.includes(item)) { + isAddTagToQueryInput.current = true; + const val = tableQueryContent ? `${tableQueryContent.trim()} ${item}` : item; + setTableQueryContent(val); + setSearchVal(val); + } + }} + /> ); }, }); @@ -273,33 +266,6 @@ export default function List(props: IProps) { }, }); } - if (item.name === 'group_obj') { - columns.push({ - title: t('group_obj'), - dataIndex: 'group_objs', - className: 'n9e-hosts-table-column-tags', - ellipsis: { - showTitle: false, - }, - render(tagArr) { - if (_.isEmpty(tagArr)) return t('common:not_grouped'); - const content = - tagArr && - tagArr.map((item) => ( - - {item.name} - - )); - return ( - tagArr && ( - document.body}> - {content} - - ) - ); - }, - }); - } if (item.name === 'mem_util') { columns.push({ title: t('mem_util'), diff --git a/src/pages/task/index.tsx b/src/pages/task/index.tsx index 0f7f7a26c..35a141b19 100644 --- a/src/pages/task/index.tsx +++ b/src/pages/task/index.tsx @@ -16,13 +16,14 @@ */ import React, { useContext, useState, useEffect } from 'react'; import { Link, useHistory } from 'react-router-dom'; -import { Table, Divider, Checkbox, Row, Col, Select, Button, Space } from 'antd'; +import { Table, Checkbox, Row, Col, Select, Button, Space, Dropdown, Menu } from 'antd'; import { CodeOutlined } from '@ant-design/icons'; import { ColumnProps } from 'antd/lib/table'; import _ from 'lodash'; import moment from 'moment'; import { useTranslation } from 'react-i18next'; import { useAntdTable } from 'ahooks'; +import { TableActionButton, TableActionLink, TableActionTrigger } from '@/components/TableActionDropdown'; import request from '@/utils/request'; import api from '@/utils/api'; @@ -118,57 +119,81 @@ const index = (_props: any) => { }); }; + const showBusinessGroup = !(businessGroup.isLeaf && gids !== '-2'); const columns: ColumnProps[] = _.concat( - businessGroup.isLeaf && gids !== '-2' - ? [] - : ([ - { - title: t('common:business_group'), - dataIndex: 'group_id', - width: 100, - render: (id) => { - return _.find(busiGroups, { id })?.name; - }, - }, - ] as any), [ - { - title: 'ID', - dataIndex: 'id', - width: 100, - }, { title: t('task.title'), dataIndex: 'title', - width: 200, + width: 240, render: (text, record) => { - return {text}; - }, - }, - { - title: t('table.operations'), - width: 150, - render: (_text, record) => { + const groupName = _.find(busiGroups, { id: record.group_id })?.name; return ( - - {t('task.clone')} - - handleOpenMetaDrawer(record)}>{t('task.meta')} - +
+ {text} + + ID: {record.id} + {showBusinessGroup && groupName && {groupName}} + +
); }, }, + ] as any, + [ { title: t('task.creator'), dataIndex: 'create_by', width: 100, + render: (val, record: any) => ( +
+
{val}
+ {record.create_by_nickname &&
{record.create_by_nickname}
} +
+ ), }, { title: t('task.created'), dataIndex: 'create_at', width: 160, render: (text) => { - return moment.unix(text).format('YYYY-MM-DD HH:mm:ss'); + const m = moment.unix(text); + return ( +
+
{m.format('YYYY-MM-DD')}
+
{m.format('HH:mm:ss')}
+
+ ); + }, + }, + { + title: t('table.operations'), + width: 64, + fixed: 'right' as const, + render: (_text, record) => { + return ( + + + + {t('task.clone')} + + + + handleOpenMetaDrawer(record)}> + {t('task.meta')} + + + + } + > + + + ); }, }, ], diff --git a/src/pages/taskTpl/index.tsx b/src/pages/taskTpl/index.tsx index 6cc933572..4cdce2691 100644 --- a/src/pages/taskTpl/index.tsx +++ b/src/pages/taskTpl/index.tsx @@ -16,13 +16,15 @@ */ import React, { useState, useContext, useEffect } from 'react'; import { Link } from 'react-router-dom'; -import { Table, Divider, Popconfirm, Tag, Row, Col, Button, Dropdown, Menu, message, Space } from 'antd'; +import { Table, Modal, Tag, Row, Col, Button, Dropdown, Menu, message, Space } from 'antd'; import { DownOutlined, CodeOutlined } from '@ant-design/icons'; import { ColumnProps } from 'antd/lib/table'; import _ from 'lodash'; import moment from 'moment'; import { useAntdTable } from 'ahooks'; import { useTranslation } from 'react-i18next'; +import { TableActionButton, TableActionLink, TableActionTrigger } from '@/components/TableActionDropdown'; +import Tags from '@/components/TableTags/Tags'; import request from '@/utils/request'; import { RequestMethod } from '@/store/common'; @@ -112,81 +114,115 @@ const index = (_props: any) => { } } + const showBusinessGroup = !(businessGroup.isLeaf && gids !== '-2'); const columns: ColumnProps[] = _.concat( - businessGroup.isLeaf && gids !== '-2' - ? [] - : ([ - { - title: t('common:business_group'), - dataIndex: 'group_id', - width: 100, - render: (id) => { - return _.find(busiGroups, { id })?.name; - }, - }, - ] as any), [ - { - title: 'ID', - dataIndex: 'id', - }, { title: t('tpl.title'), dataIndex: 'title', + width: 360, render: (text, record) => { - return {text}; + const groupName = _.find(busiGroups, { id: record.group_id })?.name; + return ( +
+ {text} + + ID: {record.id} + {showBusinessGroup && groupName && {groupName}} + +
+ ); }, }, + ] as any, + [ { title: t('tpl.tags'), dataIndex: 'tags', + width: 280, render: (text) => { - return _.map(text, (item) => ( - handleTagClick(item)}> - {item} - - )); + return ; }, }, { title: t('tpl.creator'), dataIndex: 'create_by', - width: 100, + width: 120, + render: (val, record: any) => ( +
+
{val}
+ {record.create_by_nickname &&
{record.create_by_nickname}
} +
+ ), }, { title: t('tpl.last_updated'), dataIndex: 'update_at', - width: 160, + width: 180, render: (text) => { - return moment.unix(text).format('YYYY-MM-DD HH:mm:ss'); + const m = moment.unix(text); + return ( +
+
{m.format('YYYY-MM-DD')}
+
{m.format('HH:mm:ss')}
+
+ ); }, }, { title: t('table.operations'), - width: 220, + width: 64, + fixed: 'right' as const, render: (_text, record) => { return ( - - {t('task.create')} - - {t('common:btn.edit')} - - {t('common:btn.clone')} - - {t('common:confirm.delete')}
} - onConfirm={() => { - request(`${api.tasktpl(record.group_id)}/${record.id}`, { - method: 'DELETE', - }).then(() => { - message.success(t('msg.delete.success')); - refresh(); - }); - }} - > - {t('common:btn.delete')} - - + + + + {t('task.create')} + + + + + {t('common:btn.edit')} + + + + + {t('common:btn.clone')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + return request(`${api.tasktpl(record.group_id)}/${record.id}`, { + method: 'DELETE', + }).then(() => { + message.success(t('msg.delete.success')); + refresh(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, @@ -202,7 +238,7 @@ const index = (_props: any) => {
{gids ? ( -
+
{ { ...tableProps.pagination, showSizeChanger: true, - pageSizeOptions: ['10', '50', '100', '500', '1000'], + pageSizeOptions: ['10', '15', '50', '100', '500', '1000'], showTotal: (total) => { return i18n.language == 'en' ? `Total ${total} items` : `共 ${total} 条`; }, diff --git a/src/pages/user/business.tsx b/src/pages/user/business.tsx index c41161a62..e7c63ca79 100644 --- a/src/pages/user/business.tsx +++ b/src/pages/user/business.tsx @@ -19,13 +19,14 @@ import moment from 'moment'; import _ from 'lodash'; import classNames from 'classnames'; import PageLayout, { HelpLink } from '@/components/pageLayout'; -import { Button, Table, Input, message, Row, Col, Modal, Space } from 'antd'; +import { Button, Table, Input, message, Row, Col, Modal, Space, Dropdown, Menu } from 'antd'; import { EditOutlined, DeleteOutlined, SearchOutlined, UserOutlined, InfoCircleOutlined } from '@ant-design/icons'; import UserInfoModal from './component/createModal'; import { deleteBusinessTeamMember, getBusinessTeamList, getBusinessTeamInfo, deleteBusinessTeam } from '@/services/manage'; import { Team, ActionType } from '@/store/manageInterface'; import { CommonStateContext } from '@/App'; import { ColumnsType } from 'antd/lib/table'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@/utils'; import { listToTree, getCollapsedKeys, getLocaleExpandedKeys, setLocaleExpandedKeys, getDefaultBusiness } from '@/components/BusinessGroup'; @@ -69,35 +70,49 @@ const Resource: React.FC = () => { }, { title: t('common:table.operations'), - width: '100px', + width: 64, + fixed: 'right' as const, render: (text: string, record) => ( - + + ), }, ]; diff --git a/src/pages/user/component/Tags/index.tsx b/src/pages/user/component/Tags/index.tsx deleted file mode 100644 index f916d686c..000000000 --- a/src/pages/user/component/Tags/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { Tag, Tooltip } from 'antd'; -import { Link } from 'react-router-dom'; - -interface Props { - data: { id: number; name: string }[]; - tagLinkTo: (item) => { - pathname: string; - search: string; - }; -} - -export default function index(props: Props) { - const { data, tagLinkTo } = props; - const displayData = _.slice(data, 0, 3); - return ( - -
- {_.map(displayData, (item) => { - return ( - - -
- {item.name} -
-
- - ); - })} - {data.length > 2 && ( -
- ... -
- )} -
-
- ); -} diff --git a/src/pages/user/constants.ts b/src/pages/user/constants.ts index a5ecc63a7..1426f0667 100644 --- a/src/pages/user/constants.ts +++ b/src/pages/user/constants.ts @@ -24,21 +24,6 @@ export const defaultColumnsConfigs = [ i18nKey: 'account:profile.username', visible: true, }, - { - name: 'nickname', - i18nKey: 'account:profile.nickname', - visible: true, - }, - { - name: 'email', - i18nKey: 'account:profile.email', - visible: false, - }, - { - name: 'phone', - i18nKey: 'account:profile.phone', - visible: false, - }, { name: 'roles', i18nKey: 'account:profile.role', diff --git a/src/pages/user/groups.tsx b/src/pages/user/groups.tsx index 86ee61915..36bb210c7 100644 --- a/src/pages/user/groups.tsx +++ b/src/pages/user/groups.tsx @@ -20,16 +20,18 @@ import _ from 'lodash'; import { useLocation } from 'react-router-dom'; import queryString from 'query-string'; import PageLayout, { HelpLink } from '@/components/pageLayout'; -import { Button, Table, Input, message, List, Row, Col, Modal, Space, Tag } from 'antd'; +import { Button, Table, Input, message, List, Row, Col, Modal, Space, Tag, Dropdown, Menu } from 'antd'; import { EditOutlined, DeleteOutlined, SearchOutlined, UserOutlined, InfoCircleOutlined, PlusSquareOutlined } from '@ant-design/icons'; import UserInfoModal from './component/createModal'; import { getTeamInfoList, getTeamInfo, deleteTeam, deleteMember } from '@/services/manage'; import { User, Team, UserType, ActionType, TeamInfo } from '@/store/manageInterface'; import { ColumnsType } from 'antd/lib/table'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; import { useTranslation } from 'react-i18next'; import { listToTree } from '@/components/BusinessGroup'; import { CommonStateContext } from '@/App'; import Tree from '@/components/BusinessGroup/components/Tree'; +import Tags from '@/components/TableTags/Tags'; import './index.less'; import './locale'; import usePagination from '@/components/usePagination'; @@ -102,9 +104,7 @@ const Resource: React.FC = () => { dataIndex: 'busi_groups', render: (text: string, record) => { if (_.isEmpty(record.busi_groups)) return '-'; - return _.map(record.busi_groups, (item) => { - return {item.name}; - }); + return item.id} getLabel={(item) => item.name} />; }, }, ]; @@ -113,30 +113,43 @@ const Resource: React.FC = () => { ...userColumn, { title: t('common:table.operations'), - width: '100px', + width: 64, + fixed: 'right' as const, render: (text: string, record) => ( - + + ), }, ]; diff --git a/src/pages/user/users.tsx b/src/pages/user/users.tsx index 47b1815e1..2475ec94b 100644 --- a/src/pages/user/users.tsx +++ b/src/pages/user/users.tsx @@ -18,11 +18,12 @@ import React, { useState, useContext } from 'react'; import moment from 'moment'; import _ from 'lodash'; import { Button, Input, message, Row, Modal, Table, Space, Dropdown, Menu } from 'antd'; -import { SearchOutlined, UserOutlined, EyeOutlined, MoreOutlined } from '@ant-design/icons'; +import { SearchOutlined, UserOutlined, EyeOutlined } from '@ant-design/icons'; import { ColumnsType } from 'antd/lib/table'; import { useTranslation } from 'react-i18next'; import { useAntdTable } from 'ahooks'; -import PageLayout, { HelpLink } from '@/components/pageLayout'; +import PageLayout from '@/components/pageLayout'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; import UserInfoModal from './component/createModal'; import { getUserInfoList, deleteUser } from '@/services/manage'; import { User, UserType, ActionType } from '@/store/manageInterface'; @@ -31,7 +32,7 @@ import usePagination from '@/components/usePagination'; import TimeRangePicker, { IRawTimeRange, parseRange } from '@/components/TimeRangePicker'; import OrganizeColumns, { getDefaultColumnsConfigs, setDefaultColumnsConfigs, ajustColumns } from '@/components/OrganizeColumns'; import { defaultColumnsConfigs, LOCAL_STORAGE_KEY } from './constants'; -import Tags from './component/Tags'; +import Tags from '@/components/TableTags/Tags'; import './index.less'; import './locale'; @@ -45,30 +46,24 @@ const Resource: React.FC = () => { const [memberId, setMemberId] = useState(''); const [query, setQuery] = useState(''); const [range, setRange] = useState(); - const { profile, perms } = useContext(CommonStateContext); + const { perms } = useContext(CommonStateContext); const pagination = usePagination({ PAGESIZE_KEY: 'users' }); const [columnsConfigs, setColumnsConfigs] = useState<{ name: string; visible: boolean }[]>(getDefaultColumnsConfigs(defaultColumnsConfigs, LOCAL_STORAGE_KEY)); const userColumn: ColumnsType = [ { title: t('account:profile.username'), dataIndex: 'username', + width: 260, ellipsis: true, - }, - { - title: t('account:profile.nickname'), - dataIndex: 'nickname', - ellipsis: true, - render: (text: string, record) => record.nickname || '-', - }, - { - title: t('account:profile.email'), - dataIndex: 'email', - render: (text: string, record) => record.email || '-', - }, - { - title: t('account:profile.phone'), - dataIndex: 'phone', - render: (text: string, record) => record.phone || '-', + sorter: true, + render: (text: string, record) => { + return ( +
+ {record.nickname || text} + {text} +
+ ); + }, }, ]; const userColumns: ColumnsType = [ @@ -76,20 +71,22 @@ const Resource: React.FC = () => { { title: t('account:profile.role'), dataIndex: 'roles', + width: 120, render: (text: []) => text.join(', '), }, { title: t('user.busi_groups'), dataIndex: 'busi_groups', - render: (value) => { + width: 160, + render: (value: { id: number; name: string }[]) => { return ( { - return { - pathname: '/busi-groups', - search: `?id=${item.id}`, - }; + maxWidth={220} + getKey={(item) => item.id} + getLabel={(item) => item.name} + onTagClick={(item) => { + if (typeof item !== 'string') window.open(`/busi-groups?id=${item.id}`, '_blank'); }} /> ); @@ -98,15 +95,16 @@ const Resource: React.FC = () => { { title: t('user.user_groups'), dataIndex: 'user_groups', - render: (value) => { + width: 160, + render: (value: { id: number; name: string }[]) => { return ( { - return { - pathname: '/user-groups', - search: `?id=${item.id}`, - }; + maxWidth={220} + getKey={(item) => item.id} + getLabel={(item) => item.name} + onTagClick={(item) => { + if (typeof item !== 'string') window.open(`/user-groups?id=${item.id}`, '_blank'); }} /> ); @@ -115,6 +113,7 @@ const Resource: React.FC = () => { { title: t('common:table.create_at'), dataIndex: 'create_at', + width: 170, render: (text) => { return moment.unix(text).format('YYYY-MM-DD HH:mm:ss'); }, @@ -123,6 +122,7 @@ const Resource: React.FC = () => { { title: t('user.last_active_time'), dataIndex: 'last_active_time', + width: 170, render: (text) => { if (!text) { return '-'; @@ -133,26 +133,27 @@ const Resource: React.FC = () => { }, { title: t('common:table.operations'), - width: i18n.language === 'en_US' || i18n.language === 'ru_RU' ? 80 : 40, + width: 64, + fixed: 'right' as const, render: (text: string, record) => { return ( {_.includes(perms, '/users/put') && ( handleClick(ActionType.EditUser, record.id)}> - + {t('common:btn.edit')} )} {_.includes(perms, '/users/put') && ( handleClick(ActionType.Reset, record.id)}> - + {t('account:password.reset')} )} + {_.includes(perms, '/users/del') && } {_.includes(perms, '/users/del') && ( { @@ -168,15 +169,15 @@ const Resource: React.FC = () => { }); }} > - + )} } > - - - { - deleteVariableConfigs(record.id).then(() => { - message.success(t('common:success.delete')); - fetchData(); - }); - }} - > - - - + + + { + FormModal({ + title: t('common:btn.clone'), + rsaConfig, + data: record, + onOk: (values) => { + return postVariableConfigs(values).then(() => { + fetchData(); + message.success(t('common:success.clone')); + }); + }, + }); + }} + > + {t('common:btn.clone')} + + + + { + FormModal({ + title: t('common:btn.edit'), + rsaConfig, + data: record, + onOk: (values) => { + return putVariableConfigs(record.id, values).then(() => { + fetchData(); + message.success(t('common:success.edit')); + }); + }, + }); + }} + > + {t('common:btn.edit')} + + + + + { + Modal.confirm({ + title: t('common:confirm.delete'), + onOk: () => { + deleteVariableConfigs(record.id).then(() => { + message.success(t('common:success.delete')); + fetchData(); + }); + }, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + + ); }, }, diff --git a/src/pages/warning/shield/index.tsx b/src/pages/warning/shield/index.tsx index 6b36d1f56..cb363b598 100644 --- a/src/pages/warning/shield/index.tsx +++ b/src/pages/warning/shield/index.tsx @@ -15,15 +15,16 @@ * */ import React, { useState, useEffect, useContext } from 'react'; -import { Button, Input, Table, Tooltip, message, Modal, Switch, Space, Tag } from 'antd'; +import { Button, Input, Table, Tooltip, message, Modal, Switch, Space, Tag, Dropdown, Menu } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { CloseCircleOutlined, ExclamationCircleOutlined, SearchOutlined } from '@ant-design/icons'; import moment from 'moment'; import _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useHistory, Link } from 'react-router-dom'; +import { TableActionButton, TableActionTrigger } from '@/components/TableActionDropdown'; -import Tags from '@/components/Tags'; +import Tags from '@/components/TableTags/Tags'; import PageLayout from '@/components/pageLayout'; import { getBusiGroupsAlertMutes, deleteShields, updateShields } from '@/services/shield'; import { shieldItem, strategyStatus } from '@/store/warningInterface'; @@ -32,7 +33,7 @@ import RefreshIcon from '@/components/RefreshIcon'; import { DatasourceSelect } from '@/components/DatasourceSelect'; import { CommonStateContext } from '@/App'; import usePagination from '@/components/usePagination'; -import { allCates } from '@/components/AdvancedWrap/utils'; +import { allCates, getCateDisplayLabel } from '@/components/AdvancedWrap/utils'; import DeleteMutesModal from './components/DeleteMutesModal'; import './locale'; @@ -52,7 +53,7 @@ interface Filter { const FILTER_SESSION_STORAGE_KEY = 'alert-mutes-filter'; const Shield: React.FC = () => { - const { t } = useTranslation('alertMutes'); + const { t, i18n } = useTranslation('alertMutes'); const history = useHistory(); const { datasourceList, groupedDatasourceList, businessGroup, busiGroups } = useContext(CommonStateContext); const [gids, setGids] = useState(getDefaultGids(N9E_GIDS_LOCALKEY, businessGroup)); @@ -72,63 +73,62 @@ const Shield: React.FC = () => { const prev = JSON.parse(window.sessionStorage.getItem(FILTER_SESSION_STORAGE_KEY) || '{}'); window.sessionStorage.setItem(FILTER_SESSION_STORAGE_KEY, JSON.stringify({ ...prev, ...patch })); }; + const showBusinessGroup = !(businessGroup.isLeaf && gids !== '-2'); const columns: ColumnsType = _.concat( - businessGroup.isLeaf && gids !== '-2' - ? [] - : ([ - { - title: t('common:business_group'), - dataIndex: 'group_id', - render: (id) => { - return _.find(busiGroups, { id })?.name; - }, - }, - ] as any), [ { title: t('note'), dataIndex: 'note', render: (data, record: any) => { + const groupName = _.find(busiGroups, { id: record.group_id })?.name; return ( - - {data} - +
+ + {data} + + {showBusinessGroup && groupName && {groupName}} +
); }, }, - { - title: t('common:datasource.type'), - dataIndex: 'cate', - render: (val) => { - let logoSrc = _.find(allCates, { value: val })?.logo; - if (val === 'host') { - logoSrc = '/image/logos/host.png'; - } - return {val}; - }, - }, + ] as any, + [ { title: t('common:datasource.id'), dataIndex: 'datasource_ids', render(value, record: any) { if (!value) return '-'; + const cate = _.find(allCates, { value: record.cate }); + const cateLabel = record.cate === 'host' ? 'Host' : getCateDisplayLabel(cate, i18n.language); + let logoSrc = cate?.logo; + if (record.cate === 'host') { + logoSrc = '/image/logos/host.png'; + } return ( - { - if (item === 0) return '$all'; - const name = _.find(groupedDatasourceList[record.cate], { id: item })?.name; - if (!name) return ''; - return name; - }), + + {logoSrc && ( + + {record.cate} + )} - /> + { + if (item === 0) return '$all'; + const name = _.find(groupedDatasourceList[record.cate], { id: item })?.name; + if (!name) return ''; + return name; + }), + )} + /> + ); }, }, @@ -137,17 +137,11 @@ const Shield: React.FC = () => { dataIndex: 'tags', render: (text: any) => { return ( -
- {text - ? text.map((tag, index) => { - return tag ? ( -
{`${tag.key} ${tag.func} ${ - tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value - }`}
- ) : null; - }) - : ''} -
+ (tag ? `${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}` : '')))} + /> ); }, }, @@ -237,12 +231,24 @@ const Shield: React.FC = () => { title: t('common:table.update_at'), dataIndex: 'update_at', render: (value) => { - return moment.unix(value).format('YYYY-MM-DD HH:mm:ss'); + const m = moment.unix(value); + return ( +
+
{m.format('YYYY-MM-DD')}
+
{m.format('HH:mm:ss')}
+
+ ); }, }, { title: t('common:table.update_by'), dataIndex: 'update_by', + render: (val, record: any) => ( +
+
{val}
+ {record.update_by_nickname &&
{record.update_by_nickname}
} +
+ ), }, { title: t('common:table.enabled'), @@ -273,60 +279,66 @@ const Shield: React.FC = () => { title: t('common:table.operations'), dataIndex: 'operation', fixed: 'right', + width: 64, render: (text: undefined, record: shieldItem) => { return ( - <> -
-
{ - history.push({ - pathname: `/alert-mutes/edit/${record.id}`, - search: `?mode=clone&bgid=${record.group_id}`, - }); - }} - > - {t('common:btn.clone')} -
-
{ - confirm({ - title: t('common:confirm.delete'), - icon: , - onOk: () => { - deleteShields({ ids: [record.id] }, record.group_id).then((res) => { - refreshList(); - if (res.err) { - message.success(res.err); - } else { - message.success(t('common:success.delete')); - } + + + { + history.push({ + pathname: `/alert-mutes/edit/${record.id}`, + search: `?mode=clone&bgid=${record.group_id}`, }); - }, + }} + > + {t('common:btn.clone')} + + + + + { + confirm({ + title: t('common:confirm.delete'), + icon: , + onOk: () => { + deleteShields({ ids: [record.id] }, record.group_id).then((res) => { + refreshList(); + if (res.err) { + message.success(res.err); + } else { + message.success(t('common:success.delete')); + } + }); + }, - onCancel() {}, - }); - }} - > - {t('common:btn.delete')} -
-
- + onCancel() {}, + }); + }} + > + {t('common:btn.delete')} + + + + } + > + +
); }, }, ], ); - const pagination = usePagination({ pageSizeLocalstorageKey: 'alert-mutes-table-pagesize', defaultPageSize: 30, pageSizeOptions: ['30', '50', '100', '300'] }); + const pagination = usePagination({ pageSizeLocalstorageKey: 'alert-mutes-table-pagesize' }); useEffect(() => { getList(); diff --git a/src/pages/warning/subscribe/ListNG.tsx b/src/pages/warning/subscribe/ListNG.tsx index 2f2996436..26edbe931 100644 --- a/src/pages/warning/subscribe/ListNG.tsx +++ b/src/pages/warning/subscribe/ListNG.tsx @@ -1,6 +1,6 @@ import React, { useState, useContext, useEffect } from 'react'; import { Button, Input, Table, message, Modal, Space, Switch, Tag, Dropdown, Menu, Tooltip } from 'antd'; -import { ExclamationCircleOutlined, SearchOutlined, EyeOutlined, MoreOutlined } from '@ant-design/icons'; +import { ExclamationCircleOutlined, SearchOutlined, EyeOutlined } from '@ant-design/icons'; import { ColumnsType } from 'antd/lib/table'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,8 @@ import { CommonStateContext } from '@/App'; import { priorityColor } from '@/utils/constant'; import { DatasourceSelect } from '@/components/DatasourceSelect'; import { strategyStatus } from '@/store/warningInterface'; -import Tags from '@/components/Tags'; +import Tags from '@/components/TableTags/Tags'; +import { allCates, getCateDisplayLabel } from '@/components/AdvancedWrap/utils'; import OrganizeColumns, { getDefaultColumnsConfigs, setDefaultColumnsConfigs, ajustColumns } from '@/components/OrganizeColumns'; import usePagination from '@/components/usePagination'; import { NS as notificationRulesNS } from '@/pages/notificationRules/constants'; @@ -22,6 +23,7 @@ import { getItems as getNotificationRules, RuleItem as NotificationRuleItem } fr import { defaultColumnsConfigs, LOCAL_STORAGE_KEY } from './constants'; import './locale'; import './index.less'; +import { TableActionButton, TableActionLink, TableActionTrigger } from '@/components/TableActionDropdown'; export { default as Add } from './add'; export { default as Edit } from './edit'; @@ -46,7 +48,7 @@ interface Props { } const Subscribe = (props: Props) => { - const { t } = useTranslation('alertSubscribes'); + const { t, i18n } = useTranslation('alertSubscribes'); const { datasourceList, busiGroups } = useContext(CommonStateContext); const { hideBusinessGroupColumn, readonly, headerExtra, data, loading, setRefreshFlag, linkTarget } = props; const [columnsConfigs, setColumnsConfigs] = useState<{ name: string; visible: boolean }[]>(getDefaultColumnsConfigs(defaultColumnsConfigs, LOCAL_STORAGE_KEY)); @@ -65,52 +67,61 @@ const Subscribe = (props: Props) => { const [notificationRules, setNotificationRules] = useState(); const columns: ColumnsType = _.concat( - hideBusinessGroupColumn - ? [] - : ([ - { - title: t('common:business_group'), - dataIndex: 'group_id', - render: (id) => { - return _.find(busiGroups, { id })?.name; - }, - }, - ] as any), [ { title: t('note'), dataIndex: 'note', render: (data, record: any) => { + const groupName = _.find(busiGroups, { id: record.group_id })?.name; return ( - - {data} - +
+ + {data} + + {!hideBusinessGroupColumn && groupName && {groupName}} +
); }, }, + ] as any, + [ { title: t('common:datasource.id'), dataIndex: 'datasource_ids', - render(value) { + render(value, record: any) { if (!value) return '-'; + const cate = _.find(allCates, { value: record.cate }); + const cateLabel = record.cate === 'host' ? 'Host' : getCateDisplayLabel(cate, i18n.language); + let logoSrc = cate?.logo; + if (record.cate === 'host') { + logoSrc = '/image/logos/host.png'; + } return ( - { - if (item === 0) return '$all'; - const name = _.find(datasourceList, { id: item })?.name; - if (!name) return ''; - return name; - }), + + {logoSrc && ( + + {record.cate} + )} - /> + { + if (item === 0) return '$all'; + const name = _.find(datasourceList, { id: item })?.name; + if (!name) return ''; + return name; + }), + )} + /> + ); }, }, @@ -119,27 +130,19 @@ const Subscribe = (props: Props) => { dataIndex: 'severities', render: (data) => { return ( -
`S${severity}`)} + bgColor={(tagname) => { + const bgColorMap = { S1: 'var(--fc-red-3)', S2: 'var(--fc-orange-3)', S3: 'var(--fc-yellow-3)' }; + return bgColorMap[tagname as string] || 'var(--fc-gray-3)'; }} - > - {_.map(data, (severity) => { - return ( - - S{severity} - - ); - })} -
+ fontColor={(tagname) => { + const fontColorMap = { S1: 'var(--fc-red-11)', S2: 'var(--fc-orange-11)', S3: 'var(--fc-yellow-11)' }; + return fontColorMap[tagname as string] || 'var(--fc-gray-11)'; + }} + /> ); }, }, @@ -157,13 +160,11 @@ const Subscribe = (props: Props) => { render: (text: any) => { if (!text) return '-'; return ( - <> - {text - ? text.map((tag, index) => { - return tag ?
{`${tag.func} ${_.includes(['in', 'not in'], tag.func) ? tag.value.split(' ').join(', ') : tag.value}`}
: null; - }) - : ''} - + (tag ? `${tag.func} ${_.includes(['in', 'not in'], tag.func) ? tag.value.split(' ').join(', ') : tag.value}` : '')))} + /> ); }, }, @@ -172,13 +173,11 @@ const Subscribe = (props: Props) => { dataIndex: 'tags', render: (text: any) => { return ( - <> - {text - ? text.map((tag, index) => { - return tag ?
{`${tag.key} ${tag.func} ${_.includes(['in', 'not in'], tag.func) ? tag.value.split(' ').join(', ') : tag.value}`}
: null; - }) - : ''} - + (tag ? `${tag.key} ${tag.func} ${_.includes(['in', 'not in'], tag.func) ? tag.value.split(' ').join(', ') : tag.value}` : '')))} + /> ); }, }, @@ -186,7 +185,7 @@ const Subscribe = (props: Props) => { title: t('user_groups'), dataIndex: 'user_groups', render: (data) => { - return ; + return ; }, }, { @@ -194,28 +193,14 @@ const Subscribe = (props: Props) => { dataIndex: 'notify_rule_ids', render: (data) => { return ( -
- {_.map(data, (id) => { - const val = _.find(notificationRules, { id })?.name || id; - return ( - - - -
- {val} -
-
-
- - ); - })} -
+ + type='outline' + maxWidth={180} + data={data} + getKey={(id) => id} + getLabel={(id) => _.find(notificationRules, { id })?.name || _.toString(id)} + onTagClick={(id) => window.open(`/${notificationRulesNS}/edit/${id}`, '_blank')} + /> ); }, }, @@ -237,11 +222,12 @@ const Subscribe = (props: Props) => { title: t('common:table.username'), ellipsis: true, dataIndex: 'update_by', - }, - { - title: t('common:table.nickname'), - ellipsis: true, - dataIndex: 'update_by_nickname', + render: (val, record: any) => ( +
+
{val}
+ {record.update_by_nickname &&
{record.update_by_nickname}
} +
+ ), }, ], readonly @@ -286,35 +272,41 @@ const Subscribe = (props: Props) => { title: t('common:table.operations'), dataIndex: 'operation', fixed: 'right', + width: 64, render: (text: string, record: subscribeItem) => { return ( - {t('common:btn.edit')} - + - {t('common:btn.clone')} - + + - + } > -