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
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:
@@ -2,15 +2,17 @@ import { useMemo } from 'react'
|
||||
import type { ColumnDef } from '@tanstack/react-table'
|
||||
import { GlobeIcon, SearchIcon } from 'lucide-react'
|
||||
|
||||
import { Badge } from '@/components/reui/badge'
|
||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||
import { createFilter, type FilterFieldConfig } from '@/components/reui/filters'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { renderSingleSelectedLabel } from '@/components/reui-kit/filter-utils'
|
||||
import type { Certificate } from '@/lib/schemas'
|
||||
import { formatDate, formatRelative } from '@/lib/format'
|
||||
|
||||
export const CERT_TABS = [
|
||||
{ id: 'all', label: 'Все' },
|
||||
{ id: 'active', label: 'Активен' },
|
||||
{ id: 'active', label: 'OK' },
|
||||
{ id: 'warning', label: 'Предупреждение' },
|
||||
{ id: 'expired', label: 'Истёк' },
|
||||
] as const
|
||||
@@ -18,6 +20,17 @@ export const CERT_TABS = [
|
||||
const ACTIVE_STATUSES = new Set(['active', 'ok', 'synced'])
|
||||
const EXPIRED_STATUSES = new Set(['expired', 'error', 'conflict'])
|
||||
|
||||
const CERT_STATUS_OPTIONS = [
|
||||
{ value: 'ok', label: 'OK' },
|
||||
{ value: 'active', label: 'Активен' },
|
||||
{ value: 'synced', label: 'Синхронизировано' },
|
||||
{ value: 'warning', label: 'Предупреждение' },
|
||||
{ value: 'expired', label: 'Истёк' },
|
||||
{ value: 'error', label: 'Ошибка' },
|
||||
{ value: 'conflict', label: 'Конфликт' },
|
||||
{ value: 'unknown', label: 'Неизвестно' },
|
||||
]
|
||||
|
||||
export function certTabFilter(item: Certificate, tabId: string) {
|
||||
if (tabId === 'active') return ACTIVE_STATUSES.has(item.status)
|
||||
if (tabId === 'warning') return item.status === 'warning'
|
||||
@@ -26,7 +39,10 @@ export function certTabFilter(item: Certificate, tabId: string) {
|
||||
}
|
||||
|
||||
export function createDefaultCertFilters() {
|
||||
return [createFilter('hostname', 'contains', [''])]
|
||||
return [
|
||||
createFilter('hostname', 'contains', ['']),
|
||||
createFilter('status', 'is', ['']),
|
||||
]
|
||||
}
|
||||
|
||||
export function useCertFilterFields() {
|
||||
@@ -40,16 +56,47 @@ export function useCertFilterFields() {
|
||||
className: 'w-52',
|
||||
placeholder: 'Поиск по хосту…',
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Статус',
|
||||
type: 'select',
|
||||
searchable: true,
|
||||
className: 'w-[168px]',
|
||||
options: CERT_STATUS_OPTIONS,
|
||||
customValueRenderer: (values) =>
|
||||
renderSingleSelectedLabel(values, CERT_STATUS_OPTIONS),
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
export function certFilterFieldValue(item: Certificate, field: string) {
|
||||
if (field === 'hostname') {
|
||||
return `${item.hostname} ${item.status}`.toLowerCase()
|
||||
switch (field) {
|
||||
case 'hostname':
|
||||
return `${item.hostname} ${item.status}`.toLowerCase()
|
||||
case 'status':
|
||||
return item.status
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function certRelativeBadge(status: string, expiresAt: string | null) {
|
||||
const relative = formatRelative(expiresAt)
|
||||
if (!expiresAt) {
|
||||
return <span className="text-muted-foreground tabular-nums">—</span>
|
||||
}
|
||||
if (EXPIRED_STATUSES.has(status)) {
|
||||
return <Badge variant="destructive-light">{relative}</Badge>
|
||||
}
|
||||
if (status === 'warning') {
|
||||
return <Badge variant="warning-light">{relative}</Badge>
|
||||
}
|
||||
if (ACTIVE_STATUSES.has(status)) {
|
||||
return <Badge variant="success-light">{relative}</Badge>
|
||||
}
|
||||
return <span className="text-muted-foreground tabular-nums">{relative}</span>
|
||||
}
|
||||
|
||||
export function useCertificateColumns() {
|
||||
@@ -85,11 +132,8 @@ export function useCertificateColumns() {
|
||||
{
|
||||
id: 'relative',
|
||||
header: 'Срок',
|
||||
cell: ({ row }) => (
|
||||
<span className="text-muted-foreground tabular-nums">
|
||||
{formatRelative(row.original.expires_at)}
|
||||
</span>
|
||||
),
|
||||
cell: ({ row }) =>
|
||||
certRelativeBadge(row.original.status, row.original.expires_at),
|
||||
},
|
||||
{
|
||||
id: 'last_checked_at',
|
||||
|
||||
Reference in New Issue
Block a user