fix(web): колонка выделения в таблице агентов, корень Command в CommandDialog, RU-метки KPI и дашборда, пустой f не пишется в URL
quality / commitlint (push) Skipped
quality / changes (push) Successful in 11s
quality / docker-check (push) Skipped
quality / openapi (push) Failing after 36s
quality / web (push) Failing after 36s
quality / api (push) Failing after 36s
CD / quality (push) Failing after 2m7s
CD / publish (push) Skipped

This commit is contained in:
Denozordec
2026-09-25 02:06:29 +07:00
parent 19af67f8a3
commit 27c1ba7e23
8 changed files with 39 additions and 18 deletions
@@ -6,6 +6,10 @@ import type { Filter, FilterFieldConfig } from '@/components/reui/filters'
import { ResourcePage } from '@/components/reui-kit' import { ResourcePage } from '@/components/reui-kit'
import { Badge } from '@/components/reui/badge' import { Badge } from '@/components/reui/badge'
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header' import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
import {
DataGridTableRowSelect,
DataGridTableRowSelectAll,
} from '@/components/reui/data-grid/data-grid-table'
import { import {
DataGridMutedCell, DataGridMutedCell,
DataGridPrimaryCell, DataGridPrimaryCell,
@@ -115,6 +119,18 @@ export function AgentFleetDataGrid({
const columns: ColumnDef<Agent>[] = useMemo( const columns: ColumnDef<Agent>[] = useMemo(
() => [ () => [
...(enableRowSelection
? [
{
id: '__select__',
enableSorting: false,
enableResizing: false,
size: 44,
header: () => <DataGridTableRowSelectAll />,
cell: ({ row }) => <DataGridTableRowSelect row={row} />,
} as ColumnDef<Agent>,
]
: []),
{ {
accessorKey: 'name', accessorKey: 'name',
size: 260, size: 260,
@@ -348,7 +364,7 @@ export function AgentFleetDataGrid({
}, },
}, },
], ],
[approvePending, handleCopy, onApprove, onDelete, onSelect], [approvePending, handleCopy, onApprove, onDelete, onSelect, enableRowSelection],
) )
return ( return (
@@ -52,24 +52,24 @@ export function fleetKpiCards(
return [ return [
{ {
id: 'pending', id: 'pending',
label: 'Pending', label: 'Ожидают',
value: counts.pending, value: counts.pending,
hint: 'approve backlog', hint: 'одобрение',
icon: icons.pending, icon: icons.pending,
iconClassName: 'text-warning', iconClassName: 'text-warning',
variant: counts.pending > 0 ? 'warning' : 'default', variant: counts.pending > 0 ? 'warning' : 'default',
}, },
{ {
id: 'invited', id: 'invited',
label: 'Invited', label: 'Приглашённые',
value: counts.invited, value: counts.invited,
hint: 'ожидают install', hint: 'ждут установки',
icon: icons.invited, icon: icons.invited,
iconClassName: 'text-info', iconClassName: 'text-info',
}, },
{ {
id: 'approved', id: 'approved',
label: 'Approved', label: 'Одобренные',
value: counts.approved, value: counts.approved,
hint: 'в парке', hint: 'в парке',
icon: icons.approved, icon: icons.approved,
@@ -77,9 +77,9 @@ export function fleetKpiCards(
}, },
{ {
id: 'stale', id: 'stale',
label: 'Offline / stale', label: 'Не на связи',
value: counts.stale, value: counts.stale,
hint: '>24ч без seen', hint: 'нет seen >24ч',
icon: icons.stale, icon: icons.stale,
iconClassName: 'text-muted-foreground', iconClassName: 'text-muted-foreground',
variant: counts.stale > 0 ? 'warning' : 'default', variant: counts.stale > 0 ? 'warning' : 'default',
+2 -1
View File
@@ -98,13 +98,14 @@ function parseAgentsSearch(search: Record<string, unknown>): AgentsSearch {
? search.agent ? search.agent
: undefined : undefined
const add = searchToFlag(search.add) const add = searchToFlag(search.add)
const f = searchToFilters(search.f)
return { return {
view, view,
agent, agent,
add, add,
tab: searchToTab(search.tab, AGENT_TAB_IDS), tab: searchToTab(search.tab, AGENT_TAB_IDS),
q: searchToQ(search.q), q: searchToQ(search.q),
f: searchToFilters(search.f), f: f.length > 0 ? f : undefined,
} }
} }
+5 -5
View File
@@ -76,7 +76,7 @@ function DashboardPage() {
}, },
{ {
id: 'dropped', id: 'dropped',
label: 'Dropped', label: 'Заблокировано',
value: d?.packets_dropped ?? '—', value: d?.packets_dropped ?? '—',
hint: 'сумма counters', hint: 'сумма counters',
to: '/stats', to: '/stats',
@@ -86,7 +86,7 @@ function DashboardPage() {
}, },
{ {
id: 'accepted', id: 'accepted',
label: 'Accepted', label: 'Пропущено',
value: d?.packets_accepted ?? '—', value: d?.packets_accepted ?? '—',
hint: 'сумма counters', hint: 'сумма counters',
to: '/stats', to: '/stats',
@@ -110,7 +110,7 @@ function DashboardPage() {
{ {
id: 'agents', id: 'agents',
title: 'Агенты', title: 'Агенты',
description: 'Enroll и approve', description: 'Подключение и одобрение агентов',
to: '/agents', to: '/agents',
icon: <ServerIcon aria-hidden />, icon: <ServerIcon aria-hidden />,
iconClassName: 'text-info', iconClassName: 'text-info',
@@ -118,7 +118,7 @@ function DashboardPage() {
{ {
id: 'lists', id: 'lists',
title: 'Списки', title: 'Списки',
description: 'Blocklists / sources', description: 'Блок-листы и источники',
to: '/lists', to: '/lists',
icon: <ListIcon aria-hidden />, icon: <ListIcon aria-hidden />,
iconClassName: 'text-primary', iconClassName: 'text-primary',
@@ -126,7 +126,7 @@ function DashboardPage() {
{ {
id: 'rules', id: 'rules',
title: 'Правила', title: 'Правила',
description: 'Allow / deny policy', description: 'Политика блокировки',
to: '/rules', to: '/rules',
icon: <BanIcon aria-hidden />, icon: <BanIcon aria-hidden />,
iconClassName: 'text-warning', iconClassName: 'text-warning',
+2 -1
View File
@@ -67,11 +67,12 @@ type ListsSearch = {
/** URL-состояние страницы: create/tab/q/f (deep-link и refresh сохраняют вид). */ /** URL-состояние страницы: create/tab/q/f (deep-link и refresh сохраняют вид). */
function parseListsSearch(search: Record<string, unknown>): ListsSearch { function parseListsSearch(search: Record<string, unknown>): ListsSearch {
const f = searchToFilters(search.f)
return { return {
create: searchToFlag(search.create), create: searchToFlag(search.create),
tab: searchToTab(search.tab, LIST_TAB_IDS), tab: searchToTab(search.tab, LIST_TAB_IDS),
q: searchToQ(search.q), q: searchToQ(search.q),
f: searchToFilters(search.f), f: f.length > 0 ? f : undefined,
} }
} }
+2 -1
View File
@@ -37,10 +37,11 @@ type RulesSearch = {
/** URL-состояние страницы: create/q/f (deep-link и refresh сохраняют вид). */ /** URL-состояние страницы: create/q/f (deep-link и refresh сохраняют вид). */
function parseRulesSearch(search: Record<string, unknown>): RulesSearch { function parseRulesSearch(search: Record<string, unknown>): RulesSearch {
const f = searchToFilters(search.f)
return { return {
create: searchToFlag(search.create), create: searchToFlag(search.create),
q: searchToQ(search.q), q: searchToQ(search.q),
f: searchToFilters(search.f), f: f.length > 0 ? f : undefined,
} }
} }
+2 -1
View File
@@ -48,8 +48,9 @@ type StatsSearch = {
/** URL-состояние страницы: f (deep-link и refresh сохраняют вид). */ /** URL-состояние страницы: f (deep-link и refresh сохраняют вид). */
function parseStatsSearch(search: Record<string, unknown>): StatsSearch { function parseStatsSearch(search: Record<string, unknown>): StatsSearch {
const f = searchToFilters(search.f)
return { return {
f: searchToFilters(search.f), f: f.length > 0 ? f : undefined,
} }
} }
+2 -1
View File
@@ -58,7 +58,8 @@ function CommandDialog({
)} )}
showCloseButton={showCloseButton} showCloseButton={showCloseButton}
> >
{children} {/* cmdk-компоненты требуют корень Command — без него useSyncExternalStore падает */}
<Command className="overflow-hidden">{children}</Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )