CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 57s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m27s
Updated various components including AccessApiKeysCard, FirewallClientsGrid, FirewallRulesGrid, ModuleEntriesSection, and DirectoriesComponent to replace traditional card and table structures with the new DataGridCard component. This change enhances the user interface by providing a more consistent layout and improved loading states. Additionally, integrated QueryState for better handling of loading and error scenarios across these components, streamlining the overall user experience.
159 lines
5.2 KiB
TypeScript
159 lines
5.2 KiB
TypeScript
import { ColumnDef, useReactTable } from '@tanstack/react-table'
|
|
import { RefreshCw, Trash2 } from 'lucide-react'
|
|
import { useMemo } from 'react'
|
|
|
|
import { Button } from '@evobgp/ui/components/button'
|
|
|
|
import { DataGridShell } from '@/components/data-grid-shell'
|
|
import { Badge } from '@/components/reui/badge'
|
|
import { ConfirmDialog } from '@/components/confirm-dialog'
|
|
import { StatusBadge } from '@/components/status-badge'
|
|
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
|
import { formatApiKeyDate } from '@/lib/access/api-key-labels'
|
|
import { createClientDataGridOptions } from '@/lib/data-grid-defaults'
|
|
import type { ApiKey } from '@/types/api'
|
|
|
|
export function AccessApiKeysGrid({
|
|
items,
|
|
isLoading = false,
|
|
onRotate,
|
|
onRevoke,
|
|
rotatePending = false,
|
|
revokePending = false,
|
|
}: {
|
|
items: ApiKey[]
|
|
isLoading?: boolean
|
|
onRotate: (id: string) => void
|
|
onRevoke: (id: string) => void
|
|
rotatePending?: boolean
|
|
revokePending?: boolean
|
|
}) {
|
|
const columns = useMemo<ColumnDef<ApiKey>[]>(
|
|
() => [
|
|
{
|
|
accessorKey: 'name',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Имя" />,
|
|
cell: ({ row }) => <span className="font-medium">{row.original.name}</span>,
|
|
meta: { headerTitle: 'Имя' },
|
|
},
|
|
{
|
|
accessorKey: 'role',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Роль" />,
|
|
cell: ({ row }) => (
|
|
<Badge variant="outline" className="font-mono text-xs">
|
|
{row.original.role}
|
|
</Badge>
|
|
),
|
|
meta: { headerTitle: 'Роль' },
|
|
},
|
|
{
|
|
accessorKey: 'prefix',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Префикс" />,
|
|
cell: ({ row }) => (
|
|
<span className="font-mono text-xs text-muted-foreground">{row.original.prefix}…</span>
|
|
),
|
|
meta: { headerTitle: 'Префикс' },
|
|
},
|
|
{
|
|
id: 'status',
|
|
enableSorting: false,
|
|
header: 'Статус',
|
|
cell: ({ row }) =>
|
|
row.original.revoked_at ? (
|
|
<StatusBadge status="error" label="отозван" />
|
|
) : (
|
|
<StatusBadge status="active" label="активен" />
|
|
),
|
|
meta: { headerTitle: 'Статус' },
|
|
},
|
|
{
|
|
id: 'expires_at',
|
|
accessorFn: (row) => row.expires_at ?? '',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Истекает" />,
|
|
cell: ({ row }) => (
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatApiKeyDate(row.original.expires_at)}
|
|
</span>
|
|
),
|
|
meta: { headerTitle: 'Истекает' },
|
|
},
|
|
{
|
|
id: 'last_used_at',
|
|
accessorFn: (row) => row.last_used_at ?? '',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Последнее использование" />,
|
|
cell: ({ row }) => (
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatApiKeyDate(row.original.last_used_at)}
|
|
</span>
|
|
),
|
|
meta: { headerTitle: 'Последнее использование' },
|
|
},
|
|
{
|
|
id: 'actions',
|
|
enableSorting: false,
|
|
header: () => null,
|
|
cell: ({ row }) => {
|
|
const k = row.original
|
|
return (
|
|
<div className="flex gap-1">
|
|
<ConfirmDialog
|
|
trigger={
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
type="button"
|
|
title="Ротировать"
|
|
disabled={!!k.revoked_at || rotatePending}
|
|
>
|
|
<RefreshCw className="size-3.5" />
|
|
</Button>
|
|
}
|
|
title="Ротировать ключ?"
|
|
description="Старый токен перестанет работать сразу."
|
|
confirmLabel="Ротировать"
|
|
onConfirm={() => onRotate(k.id)}
|
|
/>
|
|
<ConfirmDialog
|
|
trigger={
|
|
<Button
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
type="button"
|
|
className="text-destructive"
|
|
disabled={!!k.revoked_at || revokePending}
|
|
title="Отозвать"
|
|
>
|
|
<Trash2 className="size-3.5" />
|
|
</Button>
|
|
}
|
|
title="Отозвать API-ключ?"
|
|
description={`${k.name} (${k.prefix}…)`}
|
|
confirmLabel="Отозвать"
|
|
destructive
|
|
onConfirm={() => onRevoke(k.id)}
|
|
/>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
],
|
|
[onRevoke, onRotate, revokePending, rotatePending],
|
|
)
|
|
|
|
const table = useReactTable({
|
|
data: items,
|
|
columns,
|
|
...createClientDataGridOptions<ApiKey>(),
|
|
getRowId: (row) => row.id,
|
|
})
|
|
|
|
return (
|
|
<DataGridShell
|
|
table={table}
|
|
recordCount={items.length}
|
|
isLoading={isLoading}
|
|
emptyMessage="Нет ключей"
|
|
/>
|
|
)
|
|
}
|