feat(web): enhance certificate management and UI components
Build, Test, and Push CFDM Docker Image / test (push) Successful in 4m0s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m17s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

- Updated the CertificatesPage to include a new CertKpiCards component for improved KPI visualization.
- Refactored filter handling and state management for better user experience.
- Introduced new status options and enhanced filtering capabilities in certificates columns.
- Updated the OpsDashboard to utilize KpiStatGrid for displaying key metrics.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-16 22:53:01 +07:00
co-authored by Cursor
parent d44baab8d4
commit 3e326a4509
10 changed files with 436 additions and 116 deletions
+17 -43
View File
@@ -1,13 +1,13 @@
import { createFileRoute } from '@tanstack/react-router'
import { useMemo, useState } from 'react'
import { useCallback, useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { ShieldCheckIcon } from 'lucide-react'
import { toast } from 'sonner'
import type { Filter } from '@/components/reui/filters'
import { certificatesQueryOptions, certKeys, certSummaryQueryOptions } from '@/queries'
import { api } from '@/lib/api-client'
import { PageShell } from '@/components/page-shell'
import { ResourcePage } from '@/components/reui-kit'
import { CertKpiCards } from '@/components/cert-kpi-cards'
import {
CERT_TABS,
certFilterFieldValue,
@@ -16,14 +16,6 @@ import {
useCertificateColumns,
useCertFilterFields,
} from '@/components/columns/certificates-columns'
import { StatusBadge } from '@/components/status-badge'
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
import { LoadingButton } from '@/components/loading-button'
export const Route = createFileRoute('/_auth/certificates')({
@@ -37,6 +29,7 @@ export const Route = createFileRoute('/_auth/certificates')({
function CertificatesPage() {
const [filters, setFilters] = useState<Filter[]>(createDefaultCertFilters)
const [activeTab, setActiveTab] = useState('all')
const queryClient = useQueryClient()
const {
data: certs,
@@ -45,7 +38,7 @@ function CertificatesPage() {
error,
refetch,
} = useQuery(certificatesQueryOptions())
const { data: summary } = useQuery(certSummaryQueryOptions())
const { data: summary, isLoading: isSummaryLoading } = useQuery(certSummaryQueryOptions())
const checkMutation = useMutation({
mutationFn: () => api.post('/api/v1/certificates/check'),
@@ -59,40 +52,13 @@ function CertificatesPage() {
},
})
const handleSelectTab = useCallback((tabId: string) => {
setActiveTab(tabId)
}, [])
const filterFields = useCertFilterFields()
const columns = useCertificateColumns()
const kpiStrip = useMemo(() => {
const chartData = summary?.map(([status, count]) => ({ status, count })) ?? []
return (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{chartData.length === 0 ? (
<Frame dense spacing="sm" className="sm:col-span-2 lg:col-span-4">
<FramePanel className="flex items-center gap-3 p-4">
<ShieldCheckIcon className="text-muted-foreground size-5" />
<p className="text-muted-foreground text-sm">
Нет данных — запустите проверку сертификатов
</p>
</FramePanel>
</Frame>
) : (
chartData.map((entry) => (
<Frame key={entry.status} dense spacing="sm">
<FrameHeader className="px-3! py-2!">
<FrameTitle className="text-sm font-medium">
<StatusBadge status={entry.status} />
</FrameTitle>
<FrameDescription className="text-2xl font-semibold tabular-nums">
{entry.count}
</FrameDescription>
</FrameHeader>
</Frame>
))
)}
</div>
)
}, [summary])
const primaryAction = (
<LoadingButton
onClick={() => checkMutation.mutate()}
@@ -105,11 +71,19 @@ function CertificatesPage() {
return (
<PageShell className="gap-6">
{kpiStrip}
<CertKpiCards
summary={summary}
total={certs?.length}
activeTab={activeTab}
isLoading={isSummaryLoading && !summary}
onSelectTab={handleSelectTab}
/>
<ResourcePage
title="Сертификаты"
description="Мониторинг SSL: хосты с активными сервисами или режимом «Обязательно»"
tabs={CERT_TABS.map((tab) => ({ ...tab }))}
activeTab={activeTab}
onTabChange={setActiveTab}
tabFilter={certTabFilter}
filterFields={filterFields}
filters={filters}