Enhanced various settings components by adding 'min-w-0' class for better overflow handling and adjusting flex properties for improved alignment. Updated card and grid structures to ensure a cohesive user experience across the settings interface. Refined descriptions and layout in several settings tabs for better clarity and usability.
159 lines
5.3 KiB
TypeScript
159 lines
5.3 KiB
TypeScript
import { ColumnDef } from '@tanstack/react-table'
|
|
import { RefreshCw, Trash2 } from 'lucide-react'
|
|
import { useMemo } from 'react'
|
|
|
|
import { Button } from '@evobgp/ui/components/button'
|
|
|
|
import { CategoryBadge } from '@/components/category-badge'
|
|
import { DataGridMutedCell, DataGridPrimaryCell } from '@/components/data-grid-cell'
|
|
import { DataGridSection } from '@/components/data-grid-shell'
|
|
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 { DATA_GRID_DENSE_LAYOUT } from '@/lib/data-grid-defaults'
|
|
import { useClientDataGrid } from '@/hooks/use-client-data-grid'
|
|
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 }) => (
|
|
<DataGridPrimaryCell
|
|
title={row.original.name}
|
|
accent="primary"
|
|
subtitle={`${row.original.prefix}…`}
|
|
/>
|
|
),
|
|
minSize: 160,
|
|
meta: { headerTitle: 'Имя' },
|
|
},
|
|
{
|
|
accessorKey: 'role',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Роль" />,
|
|
cell: ({ row }) => (
|
|
<CategoryBadge className="font-mono text-xs">{row.original.role}</CategoryBadge>
|
|
),
|
|
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 }) => (
|
|
<DataGridMutedCell>{formatApiKeyDate(row.original.expires_at)}</DataGridMutedCell>
|
|
),
|
|
meta: { headerTitle: 'Истекает' },
|
|
},
|
|
{
|
|
id: 'last_used_at',
|
|
accessorFn: (row) => row.last_used_at ?? '',
|
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Использован" />,
|
|
cell: ({ row }) => (
|
|
<DataGridMutedCell>{formatApiKeyDate(row.original.last_used_at)}</DataGridMutedCell>
|
|
),
|
|
meta: { headerTitle: 'Использован' },
|
|
},
|
|
{
|
|
id: 'actions',
|
|
enableSorting: false,
|
|
header: () => null,
|
|
cell: ({ row }) => {
|
|
const k = row.original
|
|
return (
|
|
<div className="flex shrink-0 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, globalFilter, setGlobalFilter, filteredCount } = useClientDataGrid({
|
|
data: items,
|
|
columns,
|
|
getSearchText: (row) =>
|
|
`${row.name} ${row.role} ${row.prefix} ${row.revoked_at ? 'отозван' : 'активен'}`,
|
|
getRowId: (row) => row.id,
|
|
})
|
|
|
|
return (
|
|
<DataGridSection
|
|
table={table}
|
|
recordCount={filteredCount}
|
|
isLoading={isLoading}
|
|
emptyMessage="Нет ключей"
|
|
tableLayout={DATA_GRID_DENSE_LAYOUT}
|
|
searchValue={globalFilter}
|
|
onSearchChange={setGlobalFilter}
|
|
searchPlaceholder="Поиск API-ключей…"
|
|
/>
|
|
)
|
|
}
|