Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
357ed4ce7b |
@@ -50,7 +50,7 @@ export function DashboardActivityTimeline({
|
|||||||
<DashboardFramePanel
|
<DashboardFramePanel
|
||||||
title="Недавняя активность"
|
title="Недавняя активность"
|
||||||
description="Задачи, ревизии и сетевые события"
|
description="Задачи, ревизии и сетевые события"
|
||||||
className="h-full"
|
className="h-full min-w-0"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-muted-foreground px-4 py-6 text-sm">Загрузка…</p>
|
<p className="text-muted-foreground px-4 py-6 text-sm">Загрузка…</p>
|
||||||
@@ -82,7 +82,7 @@ export function DashboardActivityTimeline({
|
|||||||
</TimelineIndicator>
|
</TimelineIndicator>
|
||||||
</TimelineHeader>
|
</TimelineHeader>
|
||||||
<TimelineContent className="min-w-0 pb-1 text-foreground">
|
<TimelineContent className="min-w-0 pb-1 text-foreground">
|
||||||
<TimelineTitle className="text-sm leading-snug font-normal">
|
<TimelineTitle className="text-sm leading-snug font-normal break-words">
|
||||||
{item.message}
|
{item.message}
|
||||||
</TimelineTitle>
|
</TimelineTitle>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
|
|||||||
@@ -119,9 +119,10 @@ export function DashboardModulesGrid({
|
|||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<BoxesIcon className="text-muted-foreground size-4 shrink-0" aria-hidden />
|
<BoxesIcon className="text-muted-foreground size-4 shrink-0" aria-hidden />
|
||||||
<DataGridPrimaryCell title={row.original.name} accent="primary" className="max-w-[240px]" />
|
<DataGridPrimaryCell title={row.original.name} accent="primary" />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
minSize: 180,
|
||||||
meta: { headerTitle: 'Модуль' },
|
meta: { headerTitle: 'Модуль' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -164,6 +165,7 @@ export function DashboardModulesGrid({
|
|||||||
<DashboardFramePanel
|
<DashboardFramePanel
|
||||||
title="Модули"
|
title="Модули"
|
||||||
description="Поиск, сортировка и быстрый переход к настройке"
|
description="Поиск, сортировка и быстрый переход к настройке"
|
||||||
|
className="min-w-0"
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outline" size="sm" render={<Link to="/modules/new" />}>
|
<Button variant="outline" size="sm" render={<Link to="/modules/new" />}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
@@ -183,14 +185,14 @@ export function DashboardModulesGrid({
|
|||||||
columnsVisibility: false,
|
columnsVisibility: false,
|
||||||
columnsResizable: false,
|
columnsResizable: false,
|
||||||
columnsMovable: false,
|
columnsMovable: false,
|
||||||
width: 'fixed',
|
width: 'auto',
|
||||||
}}
|
}}
|
||||||
tableClassNames={{ bodyRow: 'group/module-row cursor-pointer [&>td]:h-14' }}
|
tableClassNames={{ bodyRow: 'group/module-row cursor-pointer [&>td]:h-14' }}
|
||||||
onRowClick={(row) => void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })}
|
onRowClick={(row) => void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-col gap-3 border-b px-4 py-3 lg:flex-row lg:items-center lg:justify-between">
|
<div className="flex flex-col gap-3 border-b px-4 py-3 @3xl:flex-row @3xl:items-center @3xl:justify-between">
|
||||||
<InputGroup className="w-full min-w-40 sm:max-w-xs">
|
<InputGroup className="w-full min-w-0 @3xl:max-w-xs">
|
||||||
<InputGroupAddon align="inline-start">
|
<InputGroupAddon align="inline-start">
|
||||||
<SearchIcon className="text-muted-foreground size-4" aria-hidden />
|
<SearchIcon className="text-muted-foreground size-4" aria-hidden />
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function PanelCard({
|
|||||||
<Frame
|
<Frame
|
||||||
dense
|
dense
|
||||||
spacing={spacing}
|
spacing={spacing}
|
||||||
className={cn(panelCardClassName, className)}
|
className={cn(panelCardClassName, 'min-w-0', className)}
|
||||||
>
|
>
|
||||||
<FramePanel className="flex flex-col gap-0 p-0 shadow-xs">
|
<FramePanel className="flex flex-col gap-0 p-0 shadow-xs">
|
||||||
{hasHeader ? (
|
{hasHeader ? (
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
/** Shared grid column classes for hybrid KPI / Quick Actions tiles. */
|
/**
|
||||||
|
* Shared grid column classes for hybrid KPI / Quick Actions tiles.
|
||||||
|
* Container queries only — never 5–6 columns on laptop width (tiles overlap).
|
||||||
|
* @see https://reui.io/preview/base/stats-12
|
||||||
|
*/
|
||||||
export function kpiCols(count: number): string {
|
export function kpiCols(count: number): string {
|
||||||
if (count <= 1) return 'grid-cols-1'
|
if (count <= 1) return 'grid-cols-1'
|
||||||
if (count === 2) return 'grid-cols-1 @xl:grid-cols-2'
|
if (count === 2) return 'grid-cols-1 @xl:grid-cols-2'
|
||||||
if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3'
|
if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3'
|
||||||
if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4'
|
if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4'
|
||||||
if (count === 5) return 'grid-cols-2 @3xl:grid-cols-3 xl:grid-cols-5'
|
if (count <= 6) return 'grid-cols-1 @xl:grid-cols-2 @4xl:grid-cols-3'
|
||||||
if (count === 6) return 'grid-cols-2 sm:grid-cols-3 xl:grid-cols-6'
|
return 'grid-cols-1 @xl:grid-cols-2 @4xl:grid-cols-3 @6xl:grid-cols-4'
|
||||||
return 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface OpsDashboardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rootClassName =
|
const rootClassName =
|
||||||
'text-foreground @container flex w-full flex-col gap-2 md:gap-3'
|
'text-foreground @container flex w-full min-w-0 flex-col gap-4 md:gap-6'
|
||||||
|
|
||||||
function OpsDashboardSkeleton() {
|
function OpsDashboardSkeleton() {
|
||||||
return (
|
return (
|
||||||
@@ -37,7 +37,7 @@ function OpsDashboardSkeleton() {
|
|||||||
<Skeleton className="mt-2 h-4 w-80 max-w-full" />
|
<Skeleton className="mt-2 h-4 w-80 max-w-full" />
|
||||||
</header>
|
</header>
|
||||||
<KpiStatGrid cards={[]} isLoading skeletonCount={4} />
|
<KpiStatGrid cards={[]} isLoading skeletonCount={4} />
|
||||||
<div className="grid gap-2 @3xl:grid-cols-2">
|
<div className="flex min-w-0 flex-col gap-4">
|
||||||
<Skeleton className="h-64 w-full rounded-xl" />
|
<Skeleton className="h-64 w-full rounded-xl" />
|
||||||
<Skeleton className="h-64 w-full rounded-xl" />
|
<Skeleton className="h-64 w-full rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
@@ -79,13 +79,13 @@ export function OpsDashboard({
|
|||||||
|
|
||||||
<section
|
<section
|
||||||
aria-label="Аналитика"
|
aria-label="Аналитика"
|
||||||
className="grid min-w-0 items-start gap-2 @3xl:grid-cols-2"
|
className="flex min-w-0 flex-col gap-4"
|
||||||
>
|
>
|
||||||
{charts}
|
{charts}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section aria-label={queueTitle}>
|
<section aria-label={queueTitle}>
|
||||||
<Frame dense spacing="sm" className="w-full">
|
<Frame dense spacing="sm" className="w-full min-w-0">
|
||||||
<FrameHeader>
|
<FrameHeader>
|
||||||
<FrameTitle>{queueTitle}</FrameTitle>
|
<FrameTitle>{queueTitle}</FrameTitle>
|
||||||
<FrameDescription>{queueDescription}</FrameDescription>
|
<FrameDescription>{queueDescription}</FrameDescription>
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ export function AnalyticsDashboardSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={dashboardMainSidebarClassName}>
|
<div className={dashboardMainSidebarClassName}>
|
||||||
<Skeleton className="h-96 w-full rounded-xl xl:col-span-8" />
|
<Skeleton className="h-96 w-full min-w-0 rounded-xl" />
|
||||||
<Skeleton className="h-96 w-full rounded-xl xl:col-span-4" />
|
<Skeleton className="h-96 w-full min-w-0 rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 lg:grid-cols-2">
|
<div className={chartPanelGridClassName}>
|
||||||
<Skeleton className="h-64 w-full rounded-xl" />
|
<Skeleton className="h-64 w-full rounded-xl" />
|
||||||
<Skeleton className="h-64 w-full rounded-xl" />
|
<Skeleton className="h-64 w-full rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const DATA_GRID_MESSAGES_RU = {
|
|||||||
export const DATA_GRID_DENSE_LAYOUT: NonNullable<DataGridProps<object>['tableLayout']> = {
|
export const DATA_GRID_DENSE_LAYOUT: NonNullable<DataGridProps<object>['tableLayout']> = {
|
||||||
...DATA_GRID_TABLE_LAYOUT,
|
...DATA_GRID_TABLE_LAYOUT,
|
||||||
dense: true,
|
dense: true,
|
||||||
|
width: 'auto',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTextGlobalFilter<TData extends object>(
|
export function createTextGlobalFilter<TData extends object>(
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ export const kpiGridClassName =
|
|||||||
export const kpiCardContentClassName = 'flex flex-col items-start gap-4 p-5'
|
export const kpiCardContentClassName = 'flex flex-col items-start gap-4 p-5'
|
||||||
|
|
||||||
/** Two-column chart panel row (monitoring / network overview). */
|
/** Two-column chart panel row (monitoring / network overview). */
|
||||||
export const chartPanelGridClassName = 'grid grid-cols-1 gap-4 @5xl:grid-cols-2'
|
export const chartPanelGridClassName =
|
||||||
|
'grid min-w-0 grid-cols-1 items-start gap-4 @5xl:grid-cols-2'
|
||||||
|
|
||||||
/** Main + sidebar layout (8+4) used on dashboard. */
|
/**
|
||||||
export const dashboardMainSidebarClassName = 'grid gap-4 xl:grid-cols-12 xl:items-start'
|
* Main + sidebar: stack until the dashboard container is wide enough.
|
||||||
|
* Do not nest this inside another 2-col grid — that squeezes modules/timeline.
|
||||||
|
* @see https://reui.io/preview/base/dashboard-1
|
||||||
|
*/
|
||||||
|
export const dashboardMainSidebarClassName =
|
||||||
|
'grid min-w-0 grid-cols-1 items-start gap-4 @5xl:grid-cols-2'
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ function parseShowQuickActions(value: unknown): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dashboard — OpsDashboard kit (KPI → QuickActions → charts → queue).
|
* Dashboard — OpsDashboard kit (KPI → QuickActions → modules → activity/health → queue).
|
||||||
|
* Charts slot is a vertical stack: modules stay full-width; activity shares a row
|
||||||
|
* with BGP widgets only at @5xl (container), never nested 8+4 inside a 2-col parent.
|
||||||
* @see https://reui.io/preview/base/dashboard-1
|
* @see https://reui.io/preview/base/dashboard-1
|
||||||
* @see https://reui.io/preview/base/stats-12
|
* @see https://reui.io/preview/base/stats-12
|
||||||
*/
|
*/
|
||||||
@@ -99,33 +101,31 @@ function DashboardComponent() {
|
|||||||
afterKpi={showQuickActions ? <DashboardQuickLinks /> : null}
|
afterKpi={showQuickActions ? <DashboardQuickLinks /> : null}
|
||||||
charts={
|
charts={
|
||||||
<>
|
<>
|
||||||
<div className={dashboardMainSidebarClassName}>
|
<div className="min-w-0">
|
||||||
<div className="xl:col-span-8">
|
<DashboardModulesGrid modules={modules} isLoading={refreshing} />
|
||||||
<DashboardModulesGrid modules={modules} isLoading={refreshing} />
|
|
||||||
</div>
|
|
||||||
<div className="xl:col-span-4">
|
|
||||||
<DashboardActivityTimeline
|
|
||||||
jobs={jobs}
|
|
||||||
revisions={revisions}
|
|
||||||
peers={peers}
|
|
||||||
speakers={speakers}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={chartPanelGridClassName}>
|
<div className={dashboardMainSidebarClassName}>
|
||||||
<DashboardNetworkHealth peers={peers} speakers={speakers} jobs={jobs} />
|
<DashboardActivityTimeline
|
||||||
<DashboardOperationsBreakdown jobs={jobs} modules={modules} />
|
jobs={jobs}
|
||||||
|
revisions={revisions}
|
||||||
|
peers={peers}
|
||||||
|
speakers={speakers}
|
||||||
|
/>
|
||||||
|
<div className="grid min-w-0 gap-4">
|
||||||
|
<DashboardNetworkHealth peers={peers} speakers={speakers} jobs={jobs} />
|
||||||
|
<DashboardOperationsBreakdown jobs={jobs} modules={modules} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
queueTitle="Недавняя активность"
|
queueTitle="Задачи и ревизии"
|
||||||
queueDescription="Задачи и ревизии плоскости управления"
|
queueDescription="Последние фоновые операции и история конфигураций"
|
||||||
queue={
|
queue={
|
||||||
<div className="grid gap-4 lg:grid-cols-2">
|
<div className={chartPanelGridClassName}>
|
||||||
<DashboardFramePanel
|
<DashboardFramePanel
|
||||||
title="Недавние задачи"
|
title="Недавние задачи"
|
||||||
description="Последние фоновые операции"
|
description="Последние фоновые операции"
|
||||||
className="h-full"
|
className="h-full min-w-0"
|
||||||
>
|
>
|
||||||
{activityLoading ? (
|
{activityLoading ? (
|
||||||
<Skeleton className="m-4 h-24 w-auto" />
|
<Skeleton className="m-4 h-24 w-auto" />
|
||||||
@@ -136,7 +136,7 @@ function DashboardComponent() {
|
|||||||
<DashboardFramePanel
|
<DashboardFramePanel
|
||||||
title="Последние ревизии"
|
title="Последние ревизии"
|
||||||
description="История конфигураций"
|
description="История конфигураций"
|
||||||
className="h-full"
|
className="h-full min-w-0"
|
||||||
>
|
>
|
||||||
{activityLoading ? (
|
{activityLoading ? (
|
||||||
<Skeleton className="m-4 h-24 w-auto" />
|
<Skeleton className="m-4 h-24 w-auto" />
|
||||||
|
|||||||
Reference in New Issue
Block a user