feat(web): align ops screens with ReUI PRO kit and Frame surface

OpsDashboard+afterKpi на dashboard; FrameDataGrid вместо DataGridCard; KpiStatGrid вместо SectionCards; SettingsShell без Separator. Preview: dashboard-1, stats-12, data-grid-filtering-2, settings-16.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-31 12:15:59 +07:00
co-authored by Cursor
parent 53ce80c9ff
commit c3369059af
20 changed files with 216 additions and 198 deletions
@@ -4,7 +4,7 @@ import { Plus, RefreshCw } from 'lucide-react'
import { Button } from '@evobgp/ui/components/button'
import { AccessApiKeysGrid } from '@/components/access/access-api-keys-grid'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { ApiKeyCreateDialog } from '@/components/access/api-key-create-dialog'
import { ApiKeyTokenDialog } from '@/components/access/api-key-token-dialog'
import { QueryState } from '@/components/query-state'
@@ -47,7 +47,7 @@ export function AccessApiKeysCard({
return (
<>
<DataGridCard
<FrameDataGrid
title="API-ключи"
description="Управление ключами tenant. Полный токен показывается только при создании и ротации."
actions={
@@ -85,7 +85,7 @@ export function AccessApiKeysCard({
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
<ApiKeyCreateDialog
open={createOpen}
@@ -136,6 +136,25 @@ function buildKpis({
]
}
export function buildDashboardKpiCards({
modules,
peers,
speakers,
jobs,
loading,
}: {
modules: ModuleRow[]
peers: PeerRow[]
speakers: SpeakerRow[]
jobs: JobRow[]
loading?: boolean
}) {
return buildKpis({ modules, peers, speakers, jobs, loading }).map((card) => ({
...card,
id: card.id ?? String(card.label),
}))
}
export function DashboardKpiGrid({
modules,
peers,
+15 -11
View File
@@ -5,10 +5,10 @@ import { cn } from '@evobgp/ui/lib/utils'
import { DataGridToolbar } from '@/components/data-grid-toolbar'
import {
PanelCard,
panelCardContentFlushClassName,
panelCardFooterClassName,
} from '@/components/panel-card'
import { FrameDataGrid } from '@/components/reui-kit/frame-data-grid'
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
import { DataGrid, DataGridContainer } from '@/components/reui/data-grid/data-grid'
import { DataGridTable } from '@/components/reui/data-grid/data-grid-table'
@@ -65,25 +65,29 @@ export function DataGridShell<TData extends object>({
)
}
interface DataGridCardProps {
title?: string
description?: string
/** @deprecated Prefer FrameDataGrid from @/components/reui-kit */
export function DataGridCard({
title,
description,
actions,
children,
className,
}: {
title?: ReactNode
description?: ReactNode
actions?: ReactNode
children: ReactNode
className?: string
}
export function DataGridCard({ title, description, actions, children, className }: DataGridCardProps) {
}) {
return (
<PanelCard
<FrameDataGrid
title={title}
description={description}
actions={actions}
className={className}
contentClassName={panelCardContentFlushClassName}
>
{children}
</PanelCard>
<div className={panelCardContentFlushClassName}>{children}</div>
</FrameDataGrid>
)
}
@@ -4,7 +4,8 @@ import { useNavigate } from '@tanstack/react-router'
import { CategoryBadge } from '@/components/category-badge'
import { DataGridPrimaryCell } from '@/components/data-grid-cell'
import { DataGridCard, DataGridSection } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { DataGridSection } from '@/components/data-grid-shell'
import { Badge } from '@/components/reui/badge'
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
import { useClientDataGrid } from '@/hooks/use-client-data-grid'
@@ -112,7 +113,7 @@ export function LookupMatchesGrid({
})
return (
<DataGridCard
<FrameDataGrid
title="Совпадения"
description="Entries и snapshots · клик по строке открывает модуль"
>
@@ -128,6 +129,6 @@ export function LookupMatchesGrid({
void navigate({ to: '/modules/$moduleId', params: { moduleId: row.module_id } })
}
/>
</DataGridCard>
</FrameDataGrid>
)
}
@@ -5,7 +5,7 @@ import { toast } from 'sonner'
import { Button } from '@evobgp/ui/components/button'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { ModuleEntriesGrid, type ModuleEntryDeleteTarget } from '@/components/modules/module-entries-grid'
@@ -128,7 +128,7 @@ export function ModuleEntriesSection({
return (
<>
<DataGridCard
<FrameDataGrid
title={meta.title}
description={meta.description}
actions={
@@ -166,7 +166,7 @@ export function ModuleEntriesSection({
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
{mod.type === 'DOMAINS' ? (
<ModuleDomainEntryDialog
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { OperationsJobsGrid } from '@/components/operations/operations-jobs-grid'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
@@ -51,7 +51,7 @@ export function OperationsJobsCard({
const filtered = useMemo(() => filterJobs(jobs, tab), [jobs, tab])
return (
<DataGridCard title="Задачи" description="Фильтр по статусу · data-grid-filtering pattern">
<FrameDataGrid title="Задачи" description="Фильтр по статусу · data-grid-filtering pattern">
<div className="px-5 pt-3">
<Tabs value={tab} onValueChange={(v) => setTab(v as JobTab)} className="w-full">
<TabsList variant="line" className="w-full justify-start gap-6">
@@ -81,6 +81,6 @@ export function OperationsJobsCard({
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
)
}
@@ -0,0 +1,53 @@
import type { ReactNode } from 'react'
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
import { cn } from '@evobgp/ui/lib/utils'
/**
* Frame shell for list/grid sections (replaces DataGridCard Card-named API).
* @see https://reui.io/preview/base/data-grid-filtering-2
* @see https://reui.io/docs/components/base/frame
*/
export function FrameDataGrid({
title,
description,
actions,
children,
className,
}: {
title?: ReactNode
description?: ReactNode
actions?: ReactNode
children: ReactNode
className?: string
}) {
const hasHeader = Boolean(title || description || actions)
return (
<Frame dense spacing="sm" className={cn('w-full gap-0 p-0', className)}>
<FramePanel className="flex flex-col gap-0 p-0 shadow-xs">
{hasHeader ? (
<FrameHeader className="flex-row items-start justify-between gap-3 border-b">
<div className="flex min-w-0 flex-1 flex-col gap-px">
{title ? <FrameTitle>{title}</FrameTitle> : null}
{description ? (
<FrameDescription>{description}</FrameDescription>
) : null}
</div>
{actions ? (
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
{actions}
</div>
) : null}
</FrameHeader>
) : null}
<div className="p-0">{children}</div>
</FramePanel>
</Frame>
)
}
@@ -22,5 +22,6 @@ export {
} from './kpi-stat-grid'
export { QuickActionGrid, type QuickActionItem } from './quick-action-grid'
export { OpsDashboard } from './ops-dashboard'
export { FrameDataGrid } from './frame-data-grid'
export { DetailPanel, type DetailMetricCard } from './detail-panel'
export { SettingsShell, type SettingsTabConfig } from './settings-shell'
@@ -16,10 +16,13 @@ interface OpsDashboardProps {
title?: string
description?: string
kpiCards: OpsKpiCard[]
/** Slot after KPI (QuickActionGrid). Preview: stats-12 · card-12 */
afterKpi?: ReactNode
charts: ReactNode
queue: ReactNode
queueTitle?: string
queueDescription?: string
headerActions?: ReactNode
isLoading?: boolean
}
@@ -43,14 +46,21 @@ function OpsDashboardSkeleton() {
)
}
/**
* Ops dashboard: KPI → optional Quick Actions → charts → attention queue.
* @see https://reui.io/preview/base/dashboard-1
* @see https://reui.io/preview/base/stats-12
*/
export function OpsDashboard({
title = 'Панель управления',
description = 'Обзор модулей, сети, задач и состояния плоскости управления',
kpiCards,
afterKpi,
charts,
queue,
queueTitle = 'Требуют внимания',
queueDescription = 'Проблемы BGP, сбои задач и офлайн-спикеры',
headerActions,
isLoading = false,
}: OpsDashboardProps) {
if (isLoading) {
@@ -59,12 +69,14 @@ export function OpsDashboard({
return (
<div className={rootClassName}>
<PageHeader title={title} description={description} />
<PageHeader title={title} description={description} actions={headerActions} />
<section aria-label="Ключевые метрики">
<KpiStatGrid cards={kpiCards} skeletonCount={4} />
</section>
{afterKpi ? <section aria-label="Быстрые действия">{afterKpi}</section> : null}
<section
aria-label="Аналитика"
className="grid min-w-0 items-start gap-2 @3xl:grid-cols-2"
@@ -6,7 +6,6 @@ import { useIsMobile } from '@evobgp/ui/hooks/use-mobile'
import { cn } from '@evobgp/ui/lib/utils'
import { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header'
import { Separator } from '@evobgp/ui/components/separator'
export interface SettingsTabConfig {
id: string
@@ -37,6 +36,11 @@ interface SettingsShellProps {
tabs?: SettingsTabConfig[]
}
/**
* Settings shell — Frame surface, no hairline Separator under header.
* @see https://reui.io/preview/base/settings-16
* @see https://reui.io/preview/base/settings-3
*/
export function SettingsShell({
title = 'Настройки',
description = 'Подключение UI и параметры плоскости управления',
@@ -48,10 +52,7 @@ export function SettingsShell({
return (
<PageShell>
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
<div className="flex flex-col gap-3">
<PageHeader title={title} description={description} />
<Separator />
</div>
<PageHeader title={title} description={description} />
<div
className={cn(
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { Tabs, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs'
@@ -45,7 +45,7 @@ export function ScheduleJobsCard({
const filtered = useMemo(() => filterJobs(jobs, tab), [jobs, tab])
return (
<DataGridCard title="Задачи" description="Последние задачи из API">
<FrameDataGrid title="Задачи" description="Последние задачи из API">
<div className="px-5 pt-3">
<Tabs value={tab} onValueChange={(v) => setTab(v as JobTab)} className="w-full">
<TabsList variant="line" className="w-full justify-start gap-6">
@@ -72,6 +72,6 @@ export function ScheduleJobsCard({
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
)
}
-61
View File
@@ -1,61 +0,0 @@
import type { ReactElement, ReactNode } from 'react'
import { Badge } from '@/components/reui/badge'
import { KpiStatGrid, type KpiStatItem } from '@/components/kpi-stat-grid'
export interface SectionCardItem {
label: ReactNode
value: string | number | ReactElement
hint?: ReactNode
icon?: ReactNode
badge?: ReactNode
variant?: 'default' | 'warning' | 'destructive'
active?: boolean
onClick?: () => void
}
const VARIANT_ICON_CLASS: Record<NonNullable<SectionCardItem['variant']>, string> = {
default: 'text-muted-foreground',
warning: 'text-warning',
destructive: 'text-destructive',
}
function hintToFooter(hint: ReactNode) {
if (typeof hint === 'string') {
return (
<Badge variant="outline" size="sm">
{hint}
</Badge>
)
}
return hint
}
function toKpiStatItem(item: SectionCardItem, index: number): KpiStatItem {
const variant = item.variant ?? 'default'
const footer =
item.badge ?? (item.hint ? hintToFooter(item.hint) : undefined)
return {
id: typeof item.label === 'string' ? item.label : `section-${index}`,
icon: item.icon,
iconClassName: VARIANT_ICON_CLASS[variant],
value: item.value,
label: item.label,
variant,
footer,
active: item.active,
onClick: item.onClick,
}
}
/** KPI row for route sections — ReUI stats-12 / dashboard PRO pattern. */
export function SectionCards({ items, className }: { items: SectionCardItem[]; className?: string }) {
return (
<KpiStatGrid
items={items.map(toKpiStatItem)}
className={className}
aria-label="Показатели"
/>
)
}
+6 -6
View File
@@ -9,7 +9,7 @@ import { PanelCard } from '@/components/panel-card'
import { AccessApiKeysCard } from '@/components/access/access-api-keys-card'
import { PageHeader } from '@/components/page-header'
import { Badge } from '@/components/reui/badge'
import { SectionCards, type SectionCardItem } from '@/components/section-cards'
import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
import { SectionCardsSkeleton } from '@/components/skeletons'
import { sessionCanManageApiKeys } from '@/lib/auth'
import { authSessionQueryOptions } from '@/queries/auth'
@@ -35,13 +35,13 @@ function AccessComponent() {
const refreshing = sessionQuery.isFetching || keysQuery.isFetching
const kpiItems: SectionCardItem[] = useMemo(
const kpiItems: KpiStatItem[] = useMemo(
() => [
{
label: 'Всего ключей',
value: keys.length,
icon: <KeyRound className="size-4" />,
badge: (
footer: (
<Badge variant="primary-light" size="sm">
в tenant
</Badge>
@@ -51,7 +51,7 @@ function AccessComponent() {
label: 'Активных',
value: activeCount,
icon: <ShieldCheck className="size-4" />,
badge: (
footer: (
<Badge variant="success-light" size="sm">
не отозваны
</Badge>
@@ -62,7 +62,7 @@ function AccessComponent() {
value: revokedCount,
icon: <ShieldOff className="size-4" />,
variant: revokedCount > 0 ? 'warning' : 'default',
badge: (
footer: (
<Badge variant={revokedCount > 0 ? 'warning-light' : 'outline'} size="sm">
revoked
</Badge>
@@ -156,7 +156,7 @@ function AccessComponent() {
{keysQuery.isLoading ? (
<SectionCardsSkeleton count={3} />
) : (
<SectionCards items={kpiItems} />
<KpiStatGrid items={kpiItems} />
)}
<AccessApiKeysCard
items={keys}
+56 -63
View File
@@ -8,16 +8,14 @@ import { Skeleton } from '@evobgp/ui/components/skeleton'
import { DashboardActivityTimeline } from '@/components/dashboard/dashboard-activity-timeline'
import { DashboardFramePanel } from '@/components/dashboard/dashboard-frame-panel'
import { DashboardKpiGrid } from '@/components/dashboard/dashboard-kpi-grid'
import { buildDashboardKpiCards } from '@/components/dashboard/dashboard-kpi-grid'
import { DashboardModulesGrid } from '@/components/dashboard/dashboard-modules-grid'
import { DashboardNetworkHealth } from '@/components/dashboard/dashboard-network-health'
import { DashboardOperationsBreakdown } from '@/components/dashboard/dashboard-operations-breakdown'
import { DashboardQuickLinks } from '@/components/dashboard/dashboard-quick-links'
import { DashboardRecentJobsGrid } from '@/components/dashboard/dashboard-recent-jobs-grid'
import { DashboardRecentRevisionsGrid } from '@/components/dashboard/dashboard-recent-revisions-grid'
import { PageHeader } from '@/components/page-header'
import { AnalyticsDashboardSkeleton } from '@/components/skeletons'
import { OpsDashboard } from '@/components/reui-kit'
import { chartPanelGridClassName, dashboardMainSidebarClassName } from '@/lib/ui-surface'
import {
moduleNameById,
@@ -38,6 +36,11 @@ function parseShowQuickActions(value: unknown): boolean {
return true
}
/**
* Dashboard — OpsDashboard kit (KPI → QuickActions → charts → queue).
* @see https://reui.io/preview/base/dashboard-1
* @see https://reui.io/preview/base/stats-12
*/
function DashboardComponent() {
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
const settingsQ = useQuery(settingsQueryOptions())
@@ -74,39 +77,28 @@ function DashboardComponent() {
const jobs = jobsQ.data?.items ?? []
const nameById = moduleNameById(modules)
const activityLoading =
refreshing && jobs.length === 0 && revisions.length === 0
const activityLoading = refreshing && jobs.length === 0 && revisions.length === 0
const kpiCards = buildDashboardKpiCards({ modules, peers, speakers, jobs })
return (
<div className="flex flex-col gap-5">
<PageHeader
title="Обзор"
description={
lastUpdated
? `Состояние панели управления EvoBGP. Обновлено: ${lastUpdated.toLocaleTimeString('ru-RU')}`
: 'Состояние панели управления EvoBGP.'
}
actions={
<Button variant="outline" size="sm" onClick={refetchAll} disabled={refreshing}>
<RefreshCw className={refreshing ? 'animate-spin' : ''} />
Обновить
</Button>
}
/>
{initialLoading ? (
<AnalyticsDashboardSkeleton />
) : (
<OpsDashboard
title="Обзор"
description={
lastUpdated
? `Состояние панели управления EvoBGP. Обновлено: ${lastUpdated.toLocaleTimeString('ru-RU')}`
: 'Состояние панели управления EvoBGP.'
}
headerActions={
<Button variant="outline" size="sm" onClick={refetchAll} disabled={refreshing}>
<RefreshCw className={refreshing ? 'animate-spin' : ''} />
Обновить
</Button>
}
kpiCards={kpiCards}
isLoading={initialLoading}
afterKpi={showQuickActions ? <DashboardQuickLinks /> : null}
charts={
<>
<DashboardKpiGrid
modules={modules}
peers={peers}
speakers={speakers}
jobs={jobs}
/>
{showQuickActions ? <DashboardQuickLinks /> : null}
<div className={dashboardMainSidebarClassName}>
<div className="xl:col-span-8">
<DashboardModulesGrid modules={modules} isLoading={refreshing} />
@@ -120,39 +112,40 @@ function DashboardComponent() {
/>
</div>
</div>
<div className={chartPanelGridClassName}>
<DashboardNetworkHealth peers={peers} speakers={speakers} jobs={jobs} />
<DashboardOperationsBreakdown jobs={jobs} modules={modules} />
</div>
</>
)}
<div className="grid gap-4 lg:grid-cols-2">
<DashboardFramePanel
title="Недавние задачи"
description="Последние фоновые операции"
className="h-full"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />
) : (
<DashboardRecentJobsGrid jobs={jobs.slice(0, 10)} nameById={nameById} isLoading={refreshing} />
)}
</DashboardFramePanel>
<DashboardFramePanel
title="Последние ревизии"
description="История конфигураций"
className="h-full"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />
) : (
<DashboardRecentRevisionsGrid revisions={revisions} isLoading={refreshing} />
)}
</DashboardFramePanel>
</div>
</div>
}
queueTitle="Недавняя активность"
queueDescription="Задачи и ревизии плоскости управления"
queue={
<div className="grid gap-4 lg:grid-cols-2">
<DashboardFramePanel
title="Недавние задачи"
description="Последние фоновые операции"
className="h-full"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />
) : (
<DashboardRecentJobsGrid jobs={jobs.slice(0, 10)} nameById={nameById} isLoading={refreshing} />
)}
</DashboardFramePanel>
<DashboardFramePanel
title="Последние ревизии"
description="История конфигураций"
className="h-full"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />
) : (
<DashboardRecentRevisionsGrid revisions={revisions} isLoading={refreshing} />
)}
</DashboardFramePanel>
</div>
}
/>
)
}
+10 -15
View File
@@ -5,12 +5,7 @@ import { BookText, Globe, RefreshCw, Tags } from 'lucide-react'
import { Button } from '@evobgp/ui/components/button'
import { Badge } from '@/components/reui/badge'
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
import { DataGridCard } from '@/components/data-grid-shell'
import { DirectoriesCommunitiesGrid } from '@/components/directories/directories-communities-grid'
import { DirectoriesDohGrid } from '@/components/directories/directories-doh-grid'
import { PageHeader } from '@/components/page-header'
import { QueryState } from '@/components/query-state'
import { SectionCards, type SectionCardItem } from '@/components/section-cards'
import { FrameDataGrid, KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
import { SectionCardsSkeleton, TableSkeleton } from '@/components/skeletons'
import { directoriesCommunitiesQueryOptions, directoriesDohQueryOptions } from '@/queries/directories'
@@ -26,12 +21,12 @@ function DirectoriesComponent() {
const dohProfiles = dohQ.data?.items ?? []
const loading = communitiesQ.isLoading || dohQ.isLoading
const items: SectionCardItem[] = [
const items: KpiStatItem[] = [
{
label: 'Сообщества BGP',
value: communities.length,
icon: <Tags className="size-4" />,
badge: (
footer: (
<Badge variant="primary-light" size="sm">
теги префиксов
</Badge>
@@ -41,7 +36,7 @@ function DirectoriesComponent() {
label: 'DoH профили',
value: dohProfiles.length,
icon: <Globe className="size-4" />,
badge: (
footer: (
<Badge variant="info-light" size="sm">
резолвинг доменов
</Badge>
@@ -51,7 +46,7 @@ function DirectoriesComponent() {
label: 'Справочники',
value: 'Общие',
icon: <BookText className="size-4" />,
badge: (
footer: (
<Badge variant="outline" size="sm">
все модули tenant
</Badge>
@@ -80,7 +75,7 @@ function DirectoriesComponent() {
}
/>
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
{loading ? <SectionCardsSkeleton count={3} /> : <KpiStatGrid items={items} />}
<BadgeTabs
defaultValue="communities"
@@ -90,7 +85,7 @@ function DirectoriesComponent() {
]}
>
<TabsContent value="communities" className="mt-0">
<DataGridCard
<FrameDataGrid
title="Сообщества BGP"
description="Теги для префиксов в фильтрах BIRD"
>
@@ -111,11 +106,11 @@ function DirectoriesComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
</TabsContent>
<TabsContent value="doh" className="mt-0">
<DataGridCard title="DoH профили" description="Резолверы DNS-over-HTTPS для доменных модулей">
<FrameDataGrid title="DoH профили" description="Резолверы DNS-over-HTTPS для доменных модулей">
<QueryState
data={dohProfiles}
isLoading={dohQ.isLoading}
@@ -133,7 +128,7 @@ function DirectoriesComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
</TabsContent>
</BadgeTabs>
</div>
+3 -3
View File
@@ -4,7 +4,7 @@ import { Plus, RefreshCw } from 'lucide-react'
import { Button } from '@evobgp/ui/components/button'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { ProjectsEmptyState } from '@/components/patterns/projects-empty-state'
import { ModulesListGrid } from '@/components/modules/modules-list-grid'
import { PageHeader } from '@/components/page-header'
@@ -39,7 +39,7 @@ function ModulesListComponent() {
}
/>
<DataGridCard
<FrameDataGrid
title="Все модули"
actions={
<Button size="sm" render={<Link to="/modules/new" />}>
@@ -65,7 +65,7 @@ function ModulesListComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
</div>
)
}
+3 -3
View File
@@ -5,7 +5,7 @@ import { AlertTriangle, Bird, Database, FileText, RefreshCw } from 'lucide-react
import { Button } from '@evobgp/ui/components/button'
import { PanelCard } from '@/components/panel-card'
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { StatusBadge } from '@/components/status-badge'
import { jobKindRu } from '@/lib/ui-labels'
import {
@@ -125,7 +125,7 @@ function MonitoringComponent() {
)}
<div className="grid gap-4 lg:grid-cols-2">
<DataGridCard
<FrameDataGrid
title="Доступность и готовность"
description="GET /v1/health · GET /v1/ready"
>
@@ -139,7 +139,7 @@ function MonitoringComponent() {
>
{(ready) => <MonitoringReadyGrid health={healthQ.data} ready={ready} />}
</QueryState>
</DataGridCard>
</FrameDataGrid>
<PanelCard
title={
+3 -3
View File
@@ -7,7 +7,7 @@ import { useState, useMemo } from 'react'
import { Button } from '@evobgp/ui/components/button'
import { PanelCard } from '@/components/panel-card'
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { OperationsAnalyticsCard } from '@/components/analytics'
import { SelectMenu } from '@/components/select-field'
import { OperationsJobsCard } from '@/components/operations/operations-jobs-card'
@@ -136,7 +136,7 @@ function OperationsComponent() {
]}
>
<TabsContent value="revisions" className="mt-0">
<DataGridCard title="История ревизий">
<FrameDataGrid title="История ревизий">
<QueryState
data={revisions}
isLoading={revisionsQ.isLoading}
@@ -154,7 +154,7 @@ function OperationsComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
</TabsContent>
<TabsContent value="diff" className="mt-0">
+9 -9
View File
@@ -6,13 +6,13 @@ import { useState } from 'react'
import { Button } from '@evobgp/ui/components/button'
import { Badge } from '@/components/reui/badge'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { ScheduleAgendaPanel } from '@/components/schedule/schedule-agenda-panel'
import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card'
import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid'
import { PageHeader } from '@/components/page-header'
import { QueryState } from '@/components/query-state'
import { SectionCards, type SectionCardItem } from '@/components/section-cards'
import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
import { SectionCardsSkeleton } from '@/components/skeletons'
import { operationsJobsQueryOptions } from '@/queries/operations'
@@ -37,12 +37,12 @@ function ScheduleComponent() {
['failed', 'error', 'cancelled'].includes(j.status.toLowerCase()),
).length
const items: SectionCardItem[] = [
const items: KpiStatItem[] = [
{
label: 'Всего задач',
value: jobs.length,
icon: <ListTodo className="size-4" />,
badge: (
footer: (
<Badge variant="primary-light" size="sm">
в выборке
</Badge>
@@ -52,7 +52,7 @@ function ScheduleComponent() {
label: 'В работе',
value: running,
icon: <Clock className="size-4" />,
badge: (
footer: (
<Badge variant={running > 0 ? 'info-light' : 'outline'} size="sm">
очередь и выполнение
</Badge>
@@ -63,7 +63,7 @@ function ScheduleComponent() {
value: failed,
icon: <AlertTriangle className="size-4" />,
variant: failed > 0 ? 'warning' : 'default',
badge: (
footer: (
<Badge variant={failed > 0 ? 'warning-light' : 'success-light'} size="sm">
{failed > 0 ? 'требуют внимания' : 'без ошибок'}
</Badge>
@@ -108,11 +108,11 @@ function ScheduleComponent() {
}
/>
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
{loading ? <SectionCardsSkeleton count={3} /> : <KpiStatGrid items={items} />}
<ScheduleAgendaPanel jobs={jobs} isLoading={jobsQ.isLoading} />
<DataGridCard
<FrameDataGrid
title="Модули"
description="Расписание обновления и ручной запуск обновления"
>
@@ -134,7 +134,7 @@ function ScheduleComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
<ScheduleJobsCard
jobs={jobs}
@@ -8,7 +8,7 @@ import { PanelCard } from '@/components/panel-card'
import { Input } from '@evobgp/ui/components/input'
import { Label } from '@evobgp/ui/components/label'
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
import { DataGridCard } from '@/components/data-grid-shell'
import { FrameDataGrid } from '@/components/reui-kit'
import { SelectField } from '@/components/select-field'
import { SettingsKvGrid } from '@/components/settings/settings-kv-grid'
import { SettingsSettingField } from '@/components/settings/settings-setting-field'
@@ -335,7 +335,7 @@ function TenantSettingsComponent() {
</TabsContent>
<TabsContent value="additional" className="mt-0">
<DataGridCard
<FrameDataGrid
title="Дополнительные параметры"
description="Параметры вне стандартных групп (только чтение — изменяются через API)"
>
@@ -356,7 +356,7 @@ function TenantSettingsComponent() {
/>
)}
</QueryState>
</DataGridCard>
</FrameDataGrid>
</TabsContent>
</BadgeTabs>
</div>