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
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:
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ function CommandDialog({
|
|||||||
)}
|
)}
|
||||||
showCloseButton={showCloseButton}
|
showCloseButton={showCloseButton}
|
||||||
>
|
>
|
||||||
{children}
|
{/* cmdk-компоненты требуют корень Command — без него useSyncExternalStore падает */}
|
||||||
|
<Command className="overflow-hidden">{children}</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user