| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- import { components } from 'api-types'
- import dayjs from 'dayjs'
- import { motion } from 'framer-motion'
- import {
- CircleArrowDown,
- CircleArrowUp,
- Eye,
- Key,
- MoreVertical,
- ShieldOff,
- Timer,
- Trash2,
- } from 'lucide-react'
- import {
- Badge,
- Button,
- cn,
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- TableCell,
- TableRow,
- } from 'ui'
- import { TimestampInfo } from 'ui-patterns'
- import { AlgorithmHoverCard } from '../algorithm-hover-card'
- import { statusColors, statusLabels } from '../jwt.constants'
- import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
- import { JWTSigningKey } from '@/data/jwt-signing-keys/jwt-signing-keys-query'
- interface SigningKeyRowProps {
- signingKey: components['schemas']['SigningKeyResponse']
- setSelectedKey: (key?: JWTSigningKey) => void
- setShownDialog: (dialog?: 'key-details' | 'revoke' | 'delete') => void
- handlePreviouslyUsedKey: (keyId: string) => void
- handleStandbyKey: (keyId: string) => void
- legacyKey?: JWTSigningKey | null
- standbyKey?: JWTSigningKey | null
- isLoading?: boolean
- }
- const MotionTableRow = motion.create(TableRow)
- export const SigningKeyRow = ({
- signingKey,
- setSelectedKey,
- setShownDialog,
- handlePreviouslyUsedKey,
- handleStandbyKey,
- legacyKey,
- standbyKey,
- isLoading = false,
- }: SigningKeyRowProps) => (
- <MotionTableRow
- key={signingKey.id}
- layout
- initial={{ opacity: 0, height: 0 }}
- animate={{
- opacity: 1,
- height: 'auto',
- transition: { duration: 0.2 },
- }}
- exit={{ opacity: 0, height: 0 }}
- className={cn(
- signingKey.status !== 'in_use' ? 'border-b border-dashed border-border' : 'border-b'
- )}
- >
- <TableCell className="w-[150px] pr-0 py-2">
- <div className="flex -space-x-px items-center">
- <Badge
- className={cn(
- statusColors[signingKey.status],
- 'rounded-r-none',
- 'gap-2 w-full h-6',
- 'uppercase font-mono',
- 'border-r-0'
- )}
- >
- {signingKey.status === 'standby' ? (
- <Timer className="shrink-0" size={14} />
- ) : (
- <Key className="shrink-0" size={14} />
- )}
- <span className="truncate text-xs">{statusLabels[signingKey.status]}</span>
- </Badge>
- </div>
- </TableCell>
- <TableCell className="font-mono truncate w-[315px] pl-0 py-2">
- <div className="min-w-0 flex">
- <Badge
- className={cn(
- 'bg-default-200/100 border-foreground-muted',
- 'rounded-l-none',
- 'gap-2 py-2 h-6 min-w-0 overflow-hidden flex items-center flex-1'
- )}
- >
- <span className="truncate flex-1 text-xs" title={signingKey.id}>
- {signingKey.id}
- </span>
- </Badge>
- </div>
- </TableCell>
- <TableCell className="truncate py-2">
- <AlgorithmHoverCard
- algorithm={signingKey.algorithm}
- legacy={signingKey.id === legacyKey?.id}
- />
- </TableCell>
- {signingKey.status === 'previously_used' || signingKey.status === 'revoked' ? (
- <TableCell className="max-w-[100px] text-right py-2 text-sm text-foreground-light whitespace-nowrap hidden lg:table-cell">
- <TimestampInfo
- className="text-sm"
- utcTimestamp={signingKey.updated_at}
- label={dayjs(signingKey.updated_at).fromNow()}
- />
- </TableCell>
- ) : (
- <TableCell />
- )}
- <TableCell className="text-right py-2">
- {(signingKey.status !== 'in_use' || signingKey.algorithm !== 'HS256') && (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- type="text"
- className="px-1.5"
- loading={isLoading}
- icon={<MoreVertical className="size-4" />}
- />
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" className="w-52">
- {signingKey.algorithm !== 'HS256' && (
- <DropdownMenuItem
- onSelect={() => {
- setSelectedKey(signingKey)
- setShownDialog('key-details')
- }}
- >
- <Eye className="mr-2 size-4" />
- View key details
- </DropdownMenuItem>
- )}
- {signingKey.status === 'standby' && (
- <>
- <DropdownMenuItem
- onSelect={() => handlePreviouslyUsedKey(signingKey.id)}
- className="text-destructive"
- >
- <CircleArrowDown className="mr-2 size-4" />
- Move to previously used
- </DropdownMenuItem>
- </>
- )}
- {signingKey.status === 'previously_used' && (
- <>
- <DropdownMenuItemTooltip
- disabled={!!standbyKey}
- onSelect={() => handleStandbyKey(signingKey.id)}
- tooltip={{
- content: {
- side: 'left',
- text: !!standbyKey
- ? 'You may only have one standby key at a time'
- : undefined,
- },
- }}
- >
- <CircleArrowUp className="mr-2 size-4" />
- Move to standby key
- </DropdownMenuItemTooltip>
- <DropdownMenuItem
- onSelect={() => {
- setSelectedKey(signingKey)
- setShownDialog('revoke')
- }}
- className="text-destructive"
- >
- <ShieldOff className="mr-2 size-4" />
- Revoke key
- </DropdownMenuItem>
- </>
- )}
- {signingKey.status === 'revoked' && (
- <>
- <DropdownMenuItemTooltip
- disabled={!!standbyKey}
- onSelect={() => handleStandbyKey(signingKey.id)}
- tooltip={{
- content: {
- side: 'left',
- text: !!standbyKey
- ? 'You may only have one standby key at a time'
- : undefined,
- },
- }}
- >
- <CircleArrowUp className="mr-2 size-4" />
- Move to standby key
- </DropdownMenuItemTooltip>
- <DropdownMenuItem
- onSelect={() => {
- setSelectedKey(signingKey)
- setShownDialog('delete')
- }}
- className="text-destructive"
- disabled={legacyKey?.id === signingKey.id}
- >
- <Trash2 className="mr-2 size-4" />
- Delete permanently
- </DropdownMenuItem>
- </>
- )}
- </DropdownMenuContent>
- </DropdownMenu>
- )}
- </TableCell>
- </MotionTableRow>
- )
|