Skip to content
Closed
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a08069c
fix(table): set table-body-sort-bg per design spec
Fiona2016 May 12, 2026
5ff8a6d
fix(table): force header nowrap per design rule §5
Fiona2016 May 12, 2026
ee0c0cc
fix(table): default pageSize 15 on alert-mutes / alert-subscribes / j…
Fiona2016 May 12, 2026
1d3dedc
fix(alert-subscribes): combine name + business group into Column1
Fiona2016 May 12, 2026
f06da5e
fix(alert-mutes): collapse name + datasource type + business group in…
Fiona2016 May 12, 2026
53afed2
fix(job-tpls): collapse title + ID + business group into Column1
Fiona2016 May 12, 2026
efa0f94
fix(job-tasks): collapse title + ID + business group into Column1
Fiona2016 May 12, 2026
3d661d8
fix(users): collapse identity columns into Column1
Fiona2016 May 12, 2026
f5b951b
fix(alert-rules): collapse name + cate + business group + severity in…
Fiona2016 May 12, 2026
69be9a3
fix(table): consolidate row actions into overflow menus
Fiona2016 May 13, 2026
44bce41
fix(table): use overflow menus on route list pages
Fiona2016 May 13, 2026
412fdac
fix(table): collapse remaining menu route actions
Fiona2016 May 13, 2026
ad19f99
feat(table): add shared action dropdown
Fiona2016 May 13, 2026
194da29
fix(table): align ai config actions
Fiona2016 May 13, 2026
0555ce2
fix(table): align notification actions
Fiona2016 May 13, 2026
e44a95f
fix(table): align alert event actions
Fiona2016 May 13, 2026
2fc3e1f
fix(table): align builtin component actions
Fiona2016 May 13, 2026
77a7986
fix(table): align data route actions
Fiona2016 May 13, 2026
d39465f
fix(table): align task and user actions
Fiona2016 May 13, 2026
e5b4a5b
fix(table): forward action dropdown trigger events
Fiona2016 May 13, 2026
573387d
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 May 14, 2026
84b5a39
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 May 15, 2026
439d4f1
refactor(table): compact primary list metadata columns
Fiona2016 May 15, 2026
d29310e
feat(table): refine users list primary column
Fiona2016 May 17, 2026
920cd99
feat(table): refine sorter interaction
Fiona2016 May 18, 2026
1934313
feat(table): unify route table tags
Fiona2016 May 18, 2026
0871dc4
style(table): align sorted column body bg with header
Fiona2016 May 18, 2026
1d4e465
fix(table): keep fixed cells opaque
Fiona2016 May 18, 2026
fab7e0a
feat(table): fix route table action columns
Fiona2016 May 18, 2026
617fcfe
fix(table): remove measure-row collapse and tune fixed-column edge
Fiona2016 May 18, 2026
73b5a4c
fix(table): stabilize task table layouts
Fiona2016 May 18, 2026
552d2b4
fix(table): refine dropdown interactions and sorter style
Fiona2016 May 18, 2026
46c0542
fix(table): align sorter icon with title
Fiona2016 May 18, 2026
8142e2e
fix(table): use fill-2-5 for sorted column bg
Fiona2016 May 18, 2026
dad392d
fix(table): remove sorted column color override
Fiona2016 May 18, 2026
f74a5cc
feat(alerts): add event tag collapse toggle
Fiona2016 May 18, 2026
5de23be
fix(table): apply acceptance feedback on 0517
Fiona2016 May 20, 2026
0691219
feat(table): normalize table action dropdown alignment and trigger
jsers May 20, 2026
60ac063
fix(table): comment out selected row styles to prevent fixed column b…
jsers May 20, 2026
1a8f2d9
feat(alertRules): replace event count column icons with Tags component
jsers May 20, 2026
79c38e1
refactor(tags): relocate Tags component to shared directory and adopt…
jsers May 20, 2026
3bf95de
feat(alertRules): replace TableTags with generic Tags and add severit…
jsers May 20, 2026
a53ed5c
refactor(alertRules): render null instead of '-' for empty datasource…
jsers May 20, 2026
d27dde7
refactor(table): unify table styling and Tags component usage
jsers May 20, 2026
5338bb0
refactor(table): unify tag rendering and replace TableTags with Tags …
jsers May 21, 2026
0d17533
refactor: add explicit type annotations to callback parameters
jsers May 21, 2026
e6cd901
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 May 22, 2026
1cd69fa
Merge remote-tracking branch 'origin/main' into feat-table-design-srm…
Fiona2016 May 26, 2026
b781a7c
fix(table): add shared sorter icon theme
Fiona2016 May 26, 2026
130e222
fix(table): use move icons for sorted columns
Fiona2016 May 26, 2026
20498d2
fix(table): scope sorter icon style to accepted tables
Fiona2016 May 26, 2026
f4dd39e
fix(table): opt in sortable fe tables
Fiona2016 May 26, 2026
5977c84
Revert "fix(table): opt in sortable fe tables"
Fiona2016 May 27, 2026
4ae5372
fix(table): use global sorter icon theme
Fiona2016 May 27, 2026
1076c25
fix(table): collapse remaining row actions into dropdown
Fiona2016 May 31, 2026
fdc085c
feat(table): surface frequent row actions as inline icons beside the …
Fiona2016 May 31, 2026
45825fb
Merge pull request #2117 from n9e/feat-table-action-expose-0531
Fiona2016 May 31, 2026
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
8 changes: 4 additions & 4 deletions scripts/generate_antd_dark_less.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand All @@ -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',
Expand Down
8 changes: 4 additions & 4 deletions scripts/generate_antd_gold_less.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand All @@ -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',
Expand Down
64 changes: 53 additions & 11 deletions src/App.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
-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;
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/components/TableActionDropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <Icon className='fc-table-action-menu-icon' />;
}

interface TableActionButtonProps extends Omit<ButtonProps, 'icon'> {
actionIcon?: TableActionIconName;
icon?: React.ReactNode;
}

export function TableActionButton({ actionIcon, icon, className, type = 'link', ...rest }: TableActionButtonProps) {
return (
<Button
type={type}
className={classNames('fc-table-action-menu-button', className)}
icon={icon || (actionIcon ? <TableActionIcon name={actionIcon} /> : undefined)}
{...rest}
/>
);
}

interface TableActionLinkProps extends LinkProps {
actionIcon?: TableActionIconName;
}

export function TableActionLink({ actionIcon, className, children, ...rest }: TableActionLinkProps) {
return (
<Link className={classNames('fc-table-action-menu-link', className)} {...rest}>
{actionIcon && <TableActionIcon name={actionIcon} />}
<span>{children}</span>
</Link>
);
}

export const TableActionTrigger = React.forwardRef<HTMLElement, ButtonProps>(function TableActionTrigger({ type = 'text', icon, ...rest }, ref) {
return <Button ref={ref as any} type={type} icon={icon || <MoreVertical size={16} />} {...rest} />;
});
126 changes: 126 additions & 0 deletions src/components/TableTags/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import { Popover, Tooltip } from 'antd';
import { Link } from 'react-router-dom';
import type { LinkProps } from 'react-router-dom';
import _ from 'lodash';

import './style.less';

export interface TableTagItem {
key?: React.Key;
label: React.ReactNode;
tooltip?: React.ReactNode;
to?: LinkProps['to'];
onClick?: () => void;
}

interface Props<T = any> {
data?: T[];
maxVisible?: number;
maxTagWidth?: number | string;
emptyText?: React.ReactNode;
getKey?: (item: T, index: number) => React.Key;
getLabel?: (item: T, index: number) => React.ReactNode;
getTooltip?: (item: T, index: number) => React.ReactNode;
getLinkTo?: (item: T, index: number) => LinkProps['to'] | undefined;
linkTarget?: string;
onTagClick?: (item: T, index: number) => void;
}

function getDefaultLabel(item: any) {
if (_.isObject(item) && 'label' in item) {
return item.label;
}
return item;
}

function getDefaultKey(item: any, index: number): React.Key {
if (_.isObject(item) && 'key' in item && item.key !== undefined) {
return item.key as React.Key;
}
return `${getDefaultLabel(item)}-${index}`;
}

function isEmptyLabel(label: React.ReactNode) {
return label === undefined || label === null || label === '';
}

export default function TableTags<T = any>(props: Props<T>) {
const { data, maxVisible = 2, maxTagWidth = 160, emptyText = '-', getKey, getLabel, getTooltip, getLinkTo, linkTarget, onTagClick } = props;
const items = _.filter(data || [], (item, index) => !isEmptyLabel(getLabel ? getLabel(item, index) : getDefaultLabel(item)));

if (_.isEmpty(items)) {
return <>{emptyText}</>;
}

const getItemLabel = (item: T, index: number) => (getLabel ? getLabel(item, index) : getDefaultLabel(item));
const getItemTooltip = (item: T, index: number) => {
if (getTooltip) return getTooltip(item, index);
if (_.isObject(item) && 'tooltip' in (item as any)) return (item as any).tooltip;
return getItemLabel(item, index);
};
const getItemLinkTo = (item: T, index: number) => {
if (getLinkTo) return getLinkTo(item, index);
if (_.isObject(item) && 'to' in (item as any)) return (item as any).to;
return undefined;
};

const renderTag = (item: T, index: number, inPopover = false) => {
const label = getItemLabel(item, index);
const tooltip = getItemTooltip(item, index);
const linkTo = getItemLinkTo(item, index);
const clickable = !!linkTo || !!onTagClick || (_.isObject(item) && 'onClick' in (item as any));
const style: React.CSSProperties = {
maxWidth: inPopover ? 320 : maxTagWidth,
};
const tag = (
<span
className={`fc-table-tag${clickable ? ' fc-table-tag-clickable' : ''}`}
style={style}
onClick={(e) => {
e.stopPropagation();
if (_.isObject(item) && 'onClick' in (item as any)) {
(item as any).onClick?.();
}
onTagClick?.(item, index);
}}
Comment on lines +80 to +86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid blocking row click for non-interactive tags.

stopPropagation() runs on every tag click, including non-clickable tags, which can break row click/selection behaviors in tables.

Suggested fix
-      <span
+      <span
         className={`fc-table-tag${clickable ? ' fc-table-tag-clickable' : ''}`}
         style={style}
-        onClick={(e) => {
-          e.stopPropagation();
-          if (_.isObject(item) && 'onClick' in (item as any)) {
-            (item as any).onClick?.();
-          }
-          onTagClick?.(item, index);
-        }}
+        onClick={
+          clickable
+            ? (e) => {
+                e.stopPropagation();
+                if (_.isObject(item) && 'onClick' in (item as any)) {
+                  (item as any).onClick?.();
+                }
+                onTagClick?.(item, index);
+              }
+            : undefined
+        }
       >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/TableTags/index.tsx` around lines 80 - 86, The click handler
currently always calls e.stopPropagation, which blocks row clicks even for
non-interactive tags; change it so stopPropagation is only called when the tag
is actually interactive—i.e., when the item has an onClick handler (the
_.isObject && 'onClick' in (item as any) check) or when the component-level
onTagClick exists and is intended to consume the event—then invoke (item as
any).onClick?.() and onTagClick?.(item, index) as before; otherwise do not call
e.stopPropagation so table row click/selection still works.

>
{label}
</span>
);

const content = linkTo ? (
<Link to={linkTo} target={linkTarget} onClick={(e) => e.stopPropagation()}>
{tag}
</Link>
) : (
tag
);

return (
<Tooltip key={String(getKey ? getKey(item, index) : getDefaultKey(item, index))} title={tooltip}>
{content}
</Tooltip>
);
};

const visibleItems = items.slice(0, maxVisible);
const overflowItems = items.slice(maxVisible);

return (
<div className='fc-table-tags'>
{visibleItems.map((item, index) => renderTag(item, index))}
{overflowItems.length > 0 && (
<Popover
placement='topLeft'
overlayClassName='fc-table-tags-popover'
content={<div className='fc-table-tags-popover-content'>{items.map((item, index) => renderTag(item, index, true))}</div>}
>
<span className='fc-table-tag fc-table-tag-overflow' onClick={(e) => e.stopPropagation()}>
+{overflowItems.length}
</span>
</Popover>
)}
</div>
);
}
50 changes: 50 additions & 0 deletions src/components/TableTags/style.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.fc-table-tags {
display: inline-flex;
max-width: 100%;
align-items: center;
gap: 4px;
white-space: nowrap;
}

.fc-table-tag {
display: inline-block;
height: 22px;
max-width: 160px;
padding: 0 8px;
overflow: hidden;
color: var(--fc-text-2);
font-size: 12px;
font-weight: 400;
line-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
background: var(--fc-fill-2-5);
border: 1px solid var(--fc-border-color);
border-radius: 4px;
cursor: default;
}

.fc-table-tag-overflow {
color: var(--fc-text-3);
cursor: pointer;
background: var(--fc-fill-3);
}

.fc-table-tag-clickable {
cursor: pointer;
}

.fc-table-tag-clickable:hover,
.fc-table-tag-overflow:hover {
color: var(--fc-text-1);
background: var(--fc-fill-3);
border-color: var(--fc-border-color-hover, var(--fc-border-color));
}

.fc-table-tags-popover-content {
display: flex;
max-width: 360px;
flex-wrap: wrap;
gap: 6px;
}
Loading