refactor(ui): migrate guardrails content tables to shared DataTable

This commit is contained in:
Yuneng Jiang 2026-08-12 16:08:22 -07:00
parent 243ade081c
commit 30fb400cbd
No known key found for this signature in database
4 changed files with 119 additions and 117 deletions

View file

@ -1,6 +1,8 @@
import React from "react";
import { Typography, Select, Table, Tag, Button } from "antd";
import { Typography, Select, Tag, Button } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import type { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@/components/shared/DataTable";
const { Text } = Typography;
const { Option } = Select;
@ -28,30 +30,32 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
onRemove,
readOnly = false,
}) => {
const columns = [
const columns: ColumnDef<ContentCategory>[] = [
{
title: "Category",
dataIndex: "display_name",
key: "display_name",
render: (displayName: string, record: ContentCategory) => (
<div>
<Text strong>{displayName}</Text>
{displayName !== record.category && (
<div>
<Text type="secondary" style={{ fontSize: 12 }}>
{record.category}
</Text>
</div>
)}
</div>
),
header: "Category",
accessorKey: "display_name",
cell: ({ row }) => {
const { category, display_name: displayName } = row.original;
return (
<div>
<Text strong>{displayName}</Text>
{displayName !== category && (
<div>
<Text type="secondary" style={{ fontSize: 12 }}>
{category}
</Text>
</div>
)}
</div>
);
},
},
{
title: "Severity Threshold",
dataIndex: "severity_threshold",
key: "severity_threshold",
width: 180,
render: (severity: string, record: ContentCategory) => {
header: "Severity Threshold",
accessorKey: "severity_threshold",
size: 180,
cell: ({ row }) => {
const { id, severity_threshold: severity } = row.original;
if (readOnly) {
const colorMap = {
high: "red",
@ -63,7 +67,7 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
return (
<Select
value={severity}
onChange={(value) => onSeverityChange?.(record.id, value as "high" | "medium" | "low")}
onChange={(value) => onSeverityChange?.(id, value as "high" | "medium" | "low")}
style={{ width: 150 }}
size="small"
>
@ -75,18 +79,18 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
},
},
{
title: "Action",
dataIndex: "action",
key: "action",
width: 150,
render: (action: string, record: ContentCategory) => {
header: "Action",
accessorKey: "action",
size: 150,
cell: ({ row }) => {
const { action, id } = row.original;
if (readOnly) {
return <Tag color={action === "BLOCK" ? "red" : "blue"}>{action}</Tag>;
}
return (
<Select
value={action}
onChange={(value) => onActionChange?.(record.id, value as "BLOCK" | "MASK")}
onChange={(value) => onActionChange?.(id, value as "BLOCK" | "MASK")}
style={{ width: 120 }}
size="small"
>
@ -100,22 +104,22 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
if (!readOnly) {
columns.push({
title: "",
key: "actions",
width: 100,
render: (_: any, record: ContentCategory) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove?.(record.id)}>
header: "",
id: "actions",
size: 100,
cell: ({ row }) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove?.(row.original.id)}>
Delete
</Button>
),
} as any);
});
}
if (categories.length === 0) {
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No categories configured.</div>;
}
return <Table dataSource={categories} columns={columns} rowKey="id" pagination={false} size="small" />;
return <DataTable data={categories} columns={columns} getRowId={(row) => row.id} size="compact" />;
};
export default CategoryTable;

View file

@ -1,7 +1,9 @@
import React from "react";
import { Card, Typography, Select, Table, Tag, Collapse, Button } from "antd";
import { Card, Typography, Select, Tag, Collapse, Button } from "antd";
import { DeleteOutlined, PlusOutlined, FileTextOutlined } from "@ant-design/icons";
import type { ColumnDef } from "@tanstack/react-table";
import { getCategoryYaml } from "@/components/networking";
import { DataTable } from "@/components/shared/DataTable";
const { Title, Text } = Typography;
const { Option } = Select;
@ -160,16 +162,15 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedCategoryName, accessToken]);
const columns = [
const columns: ColumnDef<SelectedCategory>[] = [
{
title: "Category",
dataIndex: "display_name",
key: "display_name",
render: (text: string, record: SelectedCategory) => {
const category = availableCategories.find((c) => c.name === record.category);
header: "Category",
accessorKey: "display_name",
cell: ({ row }) => {
const category = availableCategories.find((c) => c.name === row.original.category);
return (
<div>
<div style={{ fontWeight: 500 }}>{text}</div>
<div style={{ fontWeight: 500 }}>{row.original.display_name}</div>
{category?.description && (
<div style={{ fontSize: "12px", color: "#888", marginTop: "4px" }}>{category.description}</div>
)}
@ -178,14 +179,13 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
},
},
{
title: "Action",
dataIndex: "action",
key: "action",
width: 150,
render: (action: string, record: SelectedCategory) => (
header: "Action",
accessorKey: "action",
size: 150,
cell: ({ row }) => (
<Select
value={action}
onChange={(value) => onCategoryUpdate(record.id, "action", value)}
value={row.original.action}
onChange={(value) => onCategoryUpdate(row.original.id, "action", value)}
style={{ width: "100%" }}
>
<Option value="BLOCK">
@ -198,14 +198,13 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
),
},
{
title: "Severity Threshold",
dataIndex: "severity_threshold",
key: "severity_threshold",
width: 180,
render: (threshold: string, record: SelectedCategory) => (
header: "Severity Threshold",
accessorKey: "severity_threshold",
size: 180,
cell: ({ row }) => (
<Select
value={threshold}
onChange={(value) => onCategoryUpdate(record.id, "severity_threshold", value)}
value={row.original.severity_threshold}
onChange={(value) => onCategoryUpdate(row.original.id, "severity_threshold", value)}
style={{ width: "100%" }}
>
<Option value="low">Low</Option>
@ -215,11 +214,11 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
),
},
{
title: "",
key: "actions",
width: 80,
render: (_: any, record: SelectedCategory) => (
<Button icon={<DeleteOutlined />} onClick={() => onCategoryRemove(record.id)} size="small">
header: "",
id: "actions",
size: 80,
cell: ({ row }) => (
<Button icon={<DeleteOutlined />} onClick={() => onCategoryRemove(row.original.id)} size="small">
Remove
</Button>
),
@ -322,7 +321,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
{selectedCategories.length > 0 ? (
<>
<Table dataSource={selectedCategories} columns={columns} pagination={false} size="small" rowKey="id" />
<DataTable data={selectedCategories} columns={columns} getRowId={(row) => row.id} size="compact" />
<div style={{ marginTop: 16 }}>
<Collapse
activeKey={expandedYamlCategories}

View file

@ -1,6 +1,8 @@
import { DeleteOutlined } from "@ant-design/icons";
import { Button, Select, Table } from "antd";
import type { ColumnDef } from "@tanstack/react-table";
import { Button, Select } from "antd";
import React from "react";
import { DataTable } from "@/components/shared/DataTable";
const { Option } = Select;
@ -18,21 +20,19 @@ interface KeywordTableProps {
}
const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, onRemove }) => {
const columns = [
const columns: ColumnDef<BlockedWord>[] = [
{
title: "Keyword",
dataIndex: "keyword",
key: "keyword",
header: "Keyword",
accessorKey: "keyword",
},
{
title: "Action",
dataIndex: "action",
key: "action",
width: 150,
render: (action: string, record: BlockedWord) => (
header: "Action",
accessorKey: "action",
size: 150,
cell: ({ row }) => (
<Select
value={action}
onChange={(value) => onActionChange(record.id, "action", value)}
value={row.original.action}
onChange={(value) => onActionChange(row.original.id, "action", value)}
style={{ width: 120 }}
size="small"
>
@ -42,17 +42,16 @@ const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, o
),
},
{
title: "Description",
dataIndex: "description",
key: "description",
render: (desc: string) => desc || "-",
header: "Description",
accessorKey: "description",
cell: ({ row }) => row.original.description || "-",
},
{
title: "",
key: "actions",
width: 100,
render: (_: any, record: BlockedWord) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(record.id)}>
header: "",
id: "actions",
size: 100,
cell: ({ row }) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(row.original.id)}>
Delete
</Button>
),
@ -63,7 +62,7 @@ const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, o
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No keywords added.</div>;
}
return <Table dataSource={keywords} columns={columns} rowKey="id" pagination={false} size="small" />;
return <DataTable data={keywords} columns={columns} getRowId={(row) => row.id} size="compact" />;
};
export default KeywordTable;

View file

@ -1,6 +1,8 @@
import React from "react";
import { Typography, Select, Table, Tag, Button } from "antd";
import { Typography, Select, Tag, Button } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import type { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@/components/shared/DataTable";
const { Text } = Typography;
const { Option } = Select;
@ -21,44 +23,42 @@ interface PatternTableProps {
}
const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, onRemove }) => {
const columns = [
const columns: ColumnDef<Pattern>[] = [
{
title: "Type",
dataIndex: "type",
key: "type",
width: 100,
render: (type: string) => (
<Tag color={type === "prebuilt" ? "blue" : "green"}>{type === "prebuilt" ? "Prebuilt" : "Custom"}</Tag>
header: "Type",
accessorKey: "type",
size: 100,
cell: ({ row }) => (
<Tag color={row.original.type === "prebuilt" ? "blue" : "green"}>
{row.original.type === "prebuilt" ? "Prebuilt" : "Custom"}
</Tag>
),
},
{
title: "Pattern name",
dataIndex: "name",
key: "name",
render: (_: string, record: Pattern) => record.display_name || record.name,
header: "Pattern name",
accessorKey: "name",
cell: ({ row }) => row.original.display_name || row.original.name,
},
{
title: "Regex pattern",
dataIndex: "pattern",
key: "pattern",
render: (pattern: string) =>
pattern ? (
header: "Regex pattern",
accessorKey: "pattern",
cell: ({ row }) =>
row.original.pattern ? (
<Text code style={{ fontSize: 12 }}>
{pattern.substring(0, 40)}...
{row.original.pattern.substring(0, 40)}...
</Text>
) : (
"-"
),
},
{
title: "Action",
dataIndex: "action",
key: "action",
width: 150,
render: (action: string, record: Pattern) => (
header: "Action",
accessorKey: "action",
size: 150,
cell: ({ row }) => (
<Select
value={action}
onChange={(value) => onActionChange(record.id, value as "BLOCK" | "MASK")}
value={row.original.action}
onChange={(value) => onActionChange(row.original.id, value as "BLOCK" | "MASK")}
style={{ width: 120 }}
size="small"
>
@ -68,11 +68,11 @@ const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, o
),
},
{
title: "",
key: "actions",
width: 100,
render: (_: any, record: Pattern) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(record.id)}>
header: "",
id: "actions",
size: 100,
cell: ({ row }) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(row.original.id)}>
Delete
</Button>
),
@ -83,7 +83,7 @@ const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, o
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No patterns added.</div>;
}
return <Table dataSource={patterns} columns={columns} rowKey="id" pagination={false} size="small" />;
return <DataTable data={patterns} columns={columns} getRowId={(row) => row.id} size="compact" />;
};
export default PatternTable;