feat(web): Lists/Rules UX parity с Агентами и правило kpi-hybrid
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m50s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

Hybrid row icons, row click, icon actions; DetailPanel header; ToggleGroup режима фильтра (settings-3).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-21 02:36:09 +07:00
co-authored by Cursor
parent 90d50c2556
commit 41e4ca1410
12 changed files with 432 additions and 171 deletions
+37 -32
View File
@@ -1,9 +1,9 @@
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
import type { ColumnDef } from '@tanstack/react-table'
import { Pencil, Plus, Trash2 } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { Trash2 } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import type { ColumnDef } from '@tanstack/react-table'
import type { Filter, FilterFieldConfig } from '@/components/reui/filters'
import { PageHeader, PageShell, ResourcePage } from '@/components/reui-kit'
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
@@ -11,6 +11,7 @@ import { DataGridPrimaryCell } from '@/components/data-grid-cell'
import { StatusBadge } from '@/components/status-badge'
import { Badge } from '@/components/reui/badge'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { PolicySetIcon } from '@/components/rules/policy-set-icon'
import { policySetsQueryOptions } from '@/queries'
import { apiFetch } from '@/lib/api'
import { Button } from '@evofw/ui/components/button'
@@ -33,7 +34,7 @@ export const Route = createFileRoute('/_auth/rules/')({
/**
* Policy sets list — ResourcePage (Frame + tabs + Filters + DataGrid).
* Preview: https://reui.io/preview/base/data-grid-filtering-2
* Preview: https://reui.io/preview/base/data-grid-filtering-2 · stats-12
* Empty: https://reui.io/preview/base/empty-state-12
* Create sheet: https://reui.io/preview/base/sheet-8
*/
@@ -119,17 +120,14 @@ function PolicySetsPage() {
<DataGridColumnHeader column={column} title="Набор" />
),
cell: ({ row }) => (
<Link
to="/rules/$setId"
params={{ setId: row.original.id }}
className="min-w-0"
>
<div className="flex min-w-0 items-center gap-3">
<PolicySetIcon mode={row.original.policy_mode} />
<DataGridPrimaryCell
accent="primary"
title={row.original.name}
subtitle={row.original.description ?? undefined}
/>
</Link>
</div>
),
},
{
@@ -190,16 +188,18 @@ function PolicySetsPage() {
cell: ({ row }) => (
<div className="flex justify-end gap-1">
<Button
size="sm"
variant="outline"
render={
<Link
to="/rules/$setId"
params={{ setId: row.original.id }}
/>
}
size="icon-sm"
variant="ghost"
aria-label="Открыть"
onClick={(e) => {
e.stopPropagation()
void navigate({
to: '/rules/$setId',
params: { setId: row.original.id },
})
}}
>
Открыть
<Pencil className="size-3.5" />
</Button>
{row.original.id !== 'set-shared-default' ? (
<Button
@@ -207,7 +207,10 @@ function PolicySetsPage() {
variant="ghost"
className="text-destructive"
aria-label="Удалить"
onClick={() => setDeleteId(row.original.id)}
onClick={(e) => {
e.stopPropagation()
setDeleteId(row.original.id)
}}
>
<Trash2 className="size-3.5" />
</Button>
@@ -216,7 +219,14 @@ function PolicySetsPage() {
),
},
],
[],
[navigate],
)
const addButton = (
<Button size="sm" onClick={() => setSheetOpen(true)}>
<Plus data-icon="inline-start" />
Новый набор
</Button>
)
return (
@@ -224,11 +234,7 @@ function PolicySetsPage() {
<PageHeader
title="Наборы правил"
description="Именованные наборы назначаются агентам (можно несколько). Источник правила: список, CIDR или DNS."
actions={
<Button size="sm" onClick={() => setSheetOpen(true)}>
Новый набор
</Button>
}
actions={addButton}
/>
<ResourcePage
@@ -254,14 +260,13 @@ function PolicySetsPage() {
isError={setsQ.isError}
error={setsQ.error}
onRetry={() => void setsQ.refetch()}
onRowClick={(row) =>
void navigate({ to: '/rules/$setId', params: { setId: row.id } })
}
emptyState={{
title: 'Нет наборов',
description: 'Создайте первый набор правил политики.',
action: (
<Button size="sm" onClick={() => setSheetOpen(true)}>
Новый набор
</Button>
),
action: addButton,
}}
/>