- 日志分析
- 运维观测
【免费下载链接】graylog2-server
Free and open log management
EntityDataTable是 Graylog Web 界面(位于graylog2-web-interface前端仓库)中负责渲染"实体数据表"(Entity Data Table)的核心通用组件。本文以组件自带的开发文档 EntityDataTable.md 为主体,结合其源码实现,系统讲解如何为表格定义自定义列渲染器(cell/header)、按权限控制列显隐、渲染行操作按钮,以及使用width、minWidth、staticWidth控制列宽布局。读完本文,你将能在 Graylog 前端(如集群节点列表、Data 节点、采集器实例等页面)中定制出符合业务需求的实体数据表格。
一、组件定位:从数据到表格的通用桥梁
在深入示例之前,先明确EntityDataTable在 Graylog Web 界面中的位置。它位于通用组件目录 graylog2-web-interface/src/components/common/EntityDataTable/,是上层封装组件PaginatedEntityTable(见 PaginatedEntityTable.tsx)的内部表格载体。此外,集群配置页的 Data 节点、Graylog 节点、MongoDB 节点、OpenSearch 节点列表,以及 Collectors 部署中的采集器实例列表(如 collectors/instances/ColumnRenderers.tsx)都直接使用该组件。
从源码看,组件签名定义在 EntityDataTable.tsx 的Props<Entity extends EntityBase, Meta>中,关键 props 包括:
| Props | 作用 | 默认值 |
|---|---|---|
columnSchemas | 全部可用列的 schema(列 id、标题、类型、是否可排序、权限等) | 必填 |
entities | 表格数据行,每行必须含id(EntityBase) | 必填 |
columnRenderers | 自定义 cell / header 渲染器,可针对属性或类型 | 无 |
entityActions | 每行操作区的渲染函数 | 无 |
entityAttributesAreCamelCase | 列 id 是否为 snake_case、属性是否为 camelCase 的映射开关 | 必填 |
bulkSelection | 批量选择与批量操作(actions、onChangeSelection、initialSelection、isEntitySelectable) | 无 |
expandedSectionRenderers/rowOverride | 行展开区域 / 整行覆盖渲染 | 无 |
layoutPreferences/onLayoutPreferencesChange/onResetLayoutPreferences | 用户列布局偏好(列显隐、顺序、宽度)的读写 | 必填 |
activeSort/onSortChange | 当前排序与排序回调 | 无 |
pageSize/onPageSizeChange/noPageSizeSelect | 每页条数选择 | 无 |
noColumnReordering | 禁用列拖拽重排 | false |
需要说明的是:组件自带文档中的示例为了突出核心概念,使用了简化的 prop 命名(如data、columnDefinitions、columnPreferences),它们与当前源码中的实际 prop 存在如下对应关系——data对应entities,columnDefinitions对应columnSchemas,columnPreferences对应layoutPreferences.attributes(每个属性以{ status: 'show' | 'hide' }表示显隐,ATTRIBUTE_STATUS定义见 Constants.ts)。后续示例将按文档语义呈现,并标注当前源码下的等价写法。
二、自定义 Cell 与 Header 渲染器
文档给出的第一个示例,展示了如何为指定列注入自定义单元格渲染与表头渲染。原始示例(保留)如下:
import EntityDataTable from './EntityDataTable'; <EntityDataTable columnPreferences={{ title: { status: 'show' }, description: { status: 'show' } }} data={[ { id: 'row-id', title: 'Row title', description: 'Row description', }, ]} columnDefinitions={[ { id: 'title', title: 'Title' }, { id: 'description', title: 'Description' }, ]} columnRenderers={{ title: { renderCell: (listItem) => `The title: ${listItem.title}`, renderHeader: (title) => `Custom ${title}`, }, }} />;对应到当前源码,columnRenderers的实际结构定义在 types.ts:它是一个包含attributes(按属性 id)与types(按数据类型)两级的对象。单个列渲染器ColumnRenderer(types.ts)支持以下字段:
type ColumnRenderer<Entity extends EntityBase, Meta = unknown> = { renderCell?: (value: unknown, entity: Entity, meta: Meta, additionalInfo?: unknown) => React.ReactNode; renderHeader?: (title: string) => React.ReactNode; textAlign?: string; minWidth?: number; // px width?: number; // 可分配宽度的分数,类似 CSS 单位 fr staticWidth?: number | 'matchHeader'; };因此,在当前组件 API 下,文档示例的等价写法是:
<EntityDataTable layoutPreferences={{ attributes: { title: { status: 'show' }, description: { status: 'show' } } }} entities={[{ id: 'row-id', title: 'Row title', description: 'Row description' }]} columnSchemas={[ { id: 'title', title: 'Title', type: 'STRING' }, { id: 'description', title: 'Description', type: 'STRING' }, ]} columnRenderers={{ attributes: { title: { renderCell: (title) => `The title: ${title}`, renderHeader: (title) => `Custom ${title}`, }, }, }} entityAttributesAreCamelCase />渲染器合并机制:默认渲染器 + 自定义渲染器
columnRenderers并不会完全替代默认渲染逻辑,而是与内置默认渲染器深度合并。这一过程实现在 useColumnRenderers.ts 中:
const renderers = merge({}, DefaultColumnRenderers, customColumnRenderers); return Object.fromEntries( columnSchemas.map(({ id, type }) => { const typeRenderer = renderers.types?.[type]; const attributeRenderer = renderers.attributes?.[id]; const columnRenderer = merge({}, typeRenderer, attributeRenderer); return [id, columnRenderer]; }), );即:先合并DefaultColumnRenderers与自定义渲染器;再为每个列取"类型级渲染器"与"属性级渲染器"并再次合并。因此,即使你只为title列提供了renderCell,该列仍会继承类型(如STRING)的默认渲染,而属性级配置始终优先于类型级配置。组件主文件在渲染前通过useColumnRenderers与useAuthorizedColumnSchemas生成"按列授权后的渲染器映射"(见 EntityDataTable.tsx)。
三、渲染行操作(Row Actions)
文档的第二个示例展示了如何为每一行渲染操作按钮。原始示例(保留)如下:
import EntityDataTable from './EntityDataTable'; <EntityDataTable columnPreferences={{ title: { status: 'show' }, description: { status: 'show' } }} data={[ { id: 'row-id', title: 'Row title', description: 'Row description', }, ]} columnDefinitions={[ { id: 'title', title: 'Title' }, { id: 'description', title: 'Description' }, ]} rowActions={() => ( <div> <button type="button">Actions</button> </div> )} />;在当前源码中,行操作对应 props 是entityActions: (entity: Entity) => React.ReactNode。EntityDataTable内部会固定生成一个"操作列"(列 id 为ACTIONS_COL_ID = 'actions',见 Constants.ts),其列定义由 useActionsColumnDefinition.tsx 创建。从 EntityDataTable.tsx 可以看到,组件以typeof entityActions === 'function'判断是否存在行操作;即便没有传entityActions,操作列仍会保留并充当"弹性尾列"(elastic tail),用于吸收全部列均为静态宽度时剩余的空间(见 useColumnWidths.ts 的注释与实现)。
等价写法的当前 API 形式:
<EntityDataTable layoutPreferences={{ attributes: { title: { status: 'show' }, description: { status: 'show' } } }} entities={[{ id: 'row-id', title: 'Row title', description: 'Row description' }]} columnSchemas={[ { id: 'title', title: 'Title', type: 'STRING' }, { id: 'description', title: 'Description', type: 'STRING' }, ]} entityActions={() => ( <div> <button type="button">Actions</button> </div> )} entityAttributesAreCamelCase />实际页面中,行操作通常不止一个按钮:MoreActions.tsx将多个操作收敛为"More"下拉菜单(标题常量MORE_ACTIONS_TITLE见 Constants.ts),典型参考如集群节点页的 GraylogNodeActions.tsx 与采集器实例的 InstanceActions.tsx。
四、按权限控制列渲染
文档的第三个示例,演示了"仅当用户具备所需权限时才渲染某一列"。原始示例(保留)如下:
import EntityDataTable from './EntityDataTable'; <EntityDataTable columnPreferences={{ title: { status: 'show' }, description: { status: 'show' } }} data={[ { id: 'row-id', title: 'Row title', description: 'Row description', }, ]} columnDefinitions={[ { id: 'title', title: 'Title' }, { id: 'description', title: 'Description' }, ]} attributePermissions={{ description: { permissions: ['description:read'], }, }} />;在当前源码中,这一能力被抽象到ColumnSchema上:schema 可选携带permissions、anyPermissions与hidden(见 types.ts)。组件通过 useAuthorizedColumnSchemas.ts 基于当前登录用户(useCurrentUser)过滤出可渲染的列:
columnSchemas.filter(({ permissions, anyPermissions, hidden }) => { if (hidden) return false; if (permissions?.length) { return anyPermissions ? isAnyPermitted(currentUser.permissions, permissions) : isPermitted(currentUser.permissions, permissions); } return true; });hidden为true的列直接剔除;- 声明了
permissions的列,默认要求用户同时具备全部权限(isPermitted);若设置anyPermissions: true,则只要具备其中任一权限即可(isAnyPermitted); - 未声明权限的列默认对所有人可见。
EntityDataTable.test.tsx中的测试数据正是这样构造的:status列带有permissions: ['status:read'](见 EntityDataTable.test.tsx)。因此,当前 API 下实现"按权限控制列渲染"的推荐写法,是在columnSchemas中声明权限,而非在渲染器层做判断:
columnSchemas={[ { id: 'title', title: 'Title', type: 'STRING' }, { id: 'description', title: 'Description', type: 'STRING', permissions: ['description:read'] }, ]}这样,无权限用户看到的是"该列根本不存在",而不是"列存在但内容为空"——列过滤发生在列定义生成之前(useColumnDefinitions只接收authorizedColumnSchemas,见 EntityDataTable.tsx),列显隐选择器中也不会出现无权列。
五、列宽布局:width、minWidth 与 staticWidth
文档的核心篇幅集中于列宽控制,并明确指出Column renderer 可以声明两种互斥的宽度模式:
列渲染器可以:
- 定义
width为分数(如2)。若未定义宽度,则使用默认值1。width定义该列应占可分配空间的比例,行为类似 CSS 属性flex。可选地,还可定义minWidth覆盖弹性列的默认最小宽度,确保列无论表格多宽都能保有足够空间。- 或定义以 px 为单位的
staticWidth,适用于单元格内容宽度恒定不变(如仅包含一个图标)的列。请参考
EntityDataTable中定义的默认列渲染器,其中已为description等常见属性预置了宽度。
文档第四个示例(原始保留):
import EntityDataTable from './EntityDataTable'; <EntityDataTable columnPreferences={{ title: { status: 'show' }, description: { status: 'show' }, status: { status: 'show' } }} data={[ { id: 'row-id', title: 'Entity title', summary: 'Entity summary', status: 'status', }, ]} columnDefinitions={[ { id: 'title', title: 'Title' }, { id: 'summary', title: 'Summary' }, { id: 'status', title: 'Status' }, ]} columnRenderers={{ summary: { width: 2, minWidth: 200, }, status: { staticWidth: 100, }, }} />;宽度语义与计算原理
上面的语义在源码中得到了精确对应,相关常量定义于 Constants.ts:
DEFAULT_COL_WIDTH = 1:未指定width时的默认分数;DEFAULT_COL_MIN_WIDTH = 150(px):弹性列默认最小宽度;- 另有一系列单元格 padding、边框常量参与计算。
实际布局算法位于 useColumnWidths.ts,核心步骤为:
- 计算可分配宽度(
calculateAssignableWidth):从滚动容器宽度中扣除表格边框宽、批量选择列宽、操作列最小宽度与所有静态列宽; - 统计弹性列总分数(
totalFlexColumns):将所有未设置静态宽度列的width(缺省为1)累加; - 按分数均分:
flexColWidth = assignableWidth / totalFlexColumns,每列目标宽度为Math.floor(flexColWidth * width); - 应用最小宽度兜底:
resolvedMinWidth = Math.max(minWidth ?? DEFAULT_COL_MIN_WIDTH, headerMinWidths[id] ?? 0),弹性列目标宽度低于该值时取最小值; - 静态列与弹性尾列:
staticWidth直接作为固定像素(若为'matchHeader'或小于表头最小宽度,则取表头最小宽度,见 useColumnWidths.ts);当所有数据列都是静态宽度时,操作列吸收剩余宽度充当弹性尾列。
因此:
summary: { width: 2 }表示 summary 列在弹性列中的权重为 2,其宽度是width: 1列的约两倍;summary: { minWidth: 200 }确保即使容器很窄,summary 列也至少占 200px;status: { staticWidth: 100 }表示 status 列恒定 100px,不参与弹性分配,适合图标等定宽内容。
内置默认渲染器的宽度预置
文档提醒"请查看EntityDataTable中定义的默认列渲染器",即 DefaultColumnRenderers.tsx。内置配置包含类型级与属性级两部分:
const DefaultColumnRenderers = { types: { DATE: { renderCell: (dateTime) => <Timestamp dateTime={dateTime} />, staticWidth: 160 }, STRING: { renderCell: (text) => <TextOverflowEllipsis>{text}</TextOverflowEllipsis> }, DOUBLE: { textAlign: 'right' }, INT: { textAlign: 'right' }, LONG: { textAlign: 'right' }, }, attributes: { description: { width: 2 }, summary: { width: 1.5 }, favorite: { renderHeader: () => '', staticWidth: 30 }, }, };从中可以看到几个可直接复用的约定:
description列默认弹性权重为2(信息密度高,需要更宽空间),summary为1.5;- 日期时间列使用
Timestamp组件渲染并固定160px; - 数字类型列默认右对齐;
favorite(收藏星标)这类纯图标列用staticWidth: 30固定宽度,并隐藏表头文字;- 字符串默认以
TextOverflowEllipsis渲染,超长文本自动省略,这也是为何长文本列建议配置较大的minWidth。
自定义columnRenderers会通过lodash/merge与上述默认值深度合并(见上文"渲染器合并机制"),所以你可以只覆盖单个字段(例如仅给description换一个renderCell,宽度仍保持默认的2),其余行为沿用默认。
六、在 PaginatedEntityTable 中的集成方式
EntityDataTable通常不单独使用,而是由PaginatedEntityTable提供数据获取、搜索、过滤与分页能力后,将entities、columnSchemas、layoutPreferences等透传给它。可参考 PaginatedEntityTable.tsx 中useTableLayout、useUpdateUserLayoutPreferences、useTableEventHandlers的配合:列布局偏好(显隐、顺序、宽度、排序、每页条数、切片)统一由 useTableLayout.ts 管理,并通过useUpdateUserLayoutPreferences持久化。
此外,组件还内置了列拖拽重排(TableDndProvider+useDndCollisionDetection,支持以noColumnReordering关闭)、列显隐下拉(ColumnsVisibilitySelect,支持一键重置布局)、批量选择行(bulkSelection,勾选列 id 为bulk-select)、行展开区域(expandedSectionRenderers)等能力,均可按需在真实页面中组合使用。
七、测试验证与质量保障
组件行为有较完整的测试覆盖,见 EntityDataTable.test.tsx(约 500 行),其中与本文档主题直接相关的用例包括:
- 渲染选中列与表头:验证
title、status等已 show 的列出现,未选中的stream列不出现(对应列显隐与columnPreferences/layoutPreferences.attributes); - 渲染默认 cell 渲染器:
description列内容按默认渲染呈现; - 渲染自定义 cell 与 header 渲染器:传入
columnRenderers={{ attributes: { title: {...} } }},断言单元格输出The title: ${title}、表头输出Custom ... Header(见 EntityDataTable.test.tsx),正是文档第一节示例的回归验证。
这些测试表明,文档中的用法(自定义渲染器、权限过滤、布局偏好)均已落实为组件实际行为,可直接作为实现自定义表格时的参考基准。
结语
EntityDataTable用一层清晰的渲染器抽象,把"列如何显示"与"数据从哪来"解耦:权限过滤由ColumnSchema声明并交给useAuthorizedColumnSchemas统一处理;显示样式由columnRenderers声明并与DefaultColumnRenderers深度合并;列宽则由width(弹性分数)、minWidth(弹性下限)与staticWidth(固定像素)三种维度共同决定。在 Graylog Web 界面中开发新的实体列表页面时,只需组合 EntityDataTable.tsx、types.ts 与默认渲染器 DefaultColumnRenderers.tsx,即可获得与集群节点、采集器实例等页面一致的交互体验。
- 日志分析
- 运维观测
【免费下载链接】graylog2-server
Free and open log management
相关推荐
Graylog 前端 `ExpandableList` 可折叠列表组件实战指南
Graylog 前端 ExpandableList 可折叠列表组件实战指南 导读 ExpandableList 是 Graylog Web 前端( graylo
日志分析运维观测react-admin 的 `<RecordsIterator>` 组件:用自定义布局渲染记录列表的完整指南
react admin 的 <RecordsIterator 组件:用自定义布局渲染记录列表的完整指南 <RecordsIterator 是 react adm
前端UI组件Element UI自定义列:Table列自定义渲染技巧
Element UI自定义列:Table列自定义渲染技巧 在前端开发中,表格(Table)是展示数据的核心组件,但面对复杂的业务场景时,默认的列渲染方式往往难以
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考