| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- import dayjs from 'dayjs'
- import { useMemo } from 'react'
- import {
- Card,
- CardContent,
- cn,
- ScrollArea,
- Sheet,
- SheetContent,
- SheetHeader,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
- } from 'ui'
- import { TimestampInfo } from 'ui-patterns/TimestampInfo'
- import { ACCESS_TOKEN_RESOURCES } from '../AccessToken.constants'
- import { formatAccessText, getRealAccess } from '../AccessToken.utils'
- import { useOrgAndProjectData } from '../hooks/useOrgAndProjectData'
- import { DocsButton } from '@/components/ui/DocsButton'
- import { useScopedAccessTokenQuery } from '@/data/scoped-access-tokens/scoped-access-token-query'
- interface ViewTokenSheetProps {
- visible: boolean
- tokenId: string | undefined
- onClose: () => void
- }
- export function ViewTokenSheet({ visible, tokenId, onClose }: ViewTokenSheetProps) {
- const { organizations, projects } = useOrgAndProjectData()
- const {
- data: token,
- isLoading: isTokenLoading,
- error: tokenError,
- } = useScopedAccessTokenQuery(
- { id: tokenId! },
- {
- enabled: visible && !!tokenId,
- retry: 1,
- retryDelay: 1000,
- }
- )
- const groupedResourcesByAccess = useMemo(() => {
- const grouped: Record<string, string[]> = {}
- if (!token?.permissions) {
- return grouped
- }
- ACCESS_TOKEN_RESOURCES.forEach((resource) => {
- const access = getRealAccess(resource.resource, token.permissions)
- if (access !== 'no access') {
- const formattedAccess = formatAccessText(access)
- if (!grouped[formattedAccess]) {
- grouped[formattedAccess] = []
- }
- grouped[formattedAccess].push(resource.title)
- }
- })
- return grouped
- }, [token?.permissions])
- const getResourceAccessInfo = () => {
- const resources: Array<{ name: string; type: string; identifier: string }> = []
- const organizationSlugs = token?.organization_slugs
- if (organizationSlugs && Array.isArray(organizationSlugs) && organizationSlugs.length > 0) {
- organizationSlugs.forEach((orgSlug: string) => {
- const org = organizations.find((o) => o.slug === orgSlug)
- resources.push({
- name: org?.name || orgSlug,
- type: 'Organization',
- identifier: orgSlug,
- })
- })
- }
- const projectRefs = token?.project_refs
- if (projectRefs && Array.isArray(projectRefs) && projectRefs.length > 0) {
- projectRefs.forEach((projectRef: string) => {
- const project = projects.find((p) => p.ref === projectRef)
- resources.push({
- name: project?.name || projectRef,
- type: 'Project',
- identifier: projectRef,
- })
- })
- }
- return resources
- }
- return (
- <>
- <Sheet open={visible} onOpenChange={() => onClose()}>
- <SheetContent
- showClose={false}
- size="default"
- className="min-w-[600px]! flex flex-col h-full gap-0"
- >
- <SheetHeader
- className={cn('flex flex-row justify-between gap-x-4 items-center border-b')}
- >
- <p className="truncate" title={`Manage access for ${token?.name}`}>
- View access for {token?.name}
- </p>
- <DocsButton href="https://supabase.com/docs/reference/api/introduction" />
- </SheetHeader>
- <ScrollArea className="flex-1 max-h-[calc(100vh-60px)]">
- <div className="space-y-8 px-5 sm:px-6 py-6">
- {isTokenLoading && (
- <div className="flex items-center justify-center py-8">
- <p className="text-foreground-light">Loading token information...</p>
- </div>
- )}
- {tokenError && (
- <div className="flex items-center justify-center py-8">
- <p className="text-foreground-light text-red-500">
- Error loading token information. Please try again.
- </p>
- </div>
- )}
- {token && (
- <>
- <div className="space-y-3">
- <h3 className="text-sm font-medium text-foreground">Token Information</h3>
- <Card className="w-full overflow-hidden bg-surface-100">
- <CardContent className="p-0">
- <Table className="p-5 table-auto">
- <TableHeader>
- <TableRow className="bg-200">
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2 w-[60%]">
- Info
- </TableHead>
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2">
- Date
- </TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- <TableRow>
- <TableCell>
- <p className="truncate text-foreground-light">Created</p>
- </TableCell>
- <TableCell>
- {token?.created_at ? (
- <TimestampInfo
- utcTimestamp={token.created_at}
- label={dayjs(token.created_at).format('DD MMM YYYY')}
- className="text-sm"
- />
- ) : (
- <span className="text-foreground">Unknown</span>
- )}
- </TableCell>
- </TableRow>
- <TableRow>
- <TableCell>
- <p className="truncate text-foreground-light">Last used</p>
- </TableCell>
- <TableCell>
- {token?.last_used_at ? (
- <TimestampInfo
- utcTimestamp={token.last_used_at}
- label={dayjs(token.last_used_at).fromNow()}
- className="text-sm"
- />
- ) : (
- <span className="text-foreground">Never</span>
- )}
- </TableCell>
- </TableRow>
- <TableRow>
- <TableCell>
- <p className="truncate text-foreground-light">Expires</p>
- </TableCell>
- <TableCell>
- {token?.expires_at ? (
- <TimestampInfo
- utcTimestamp={token.expires_at}
- label={dayjs(token.expires_at).format('DD MMM YYYY')}
- className="text-sm"
- />
- ) : (
- <span className="text-foreground">Never</span>
- )}
- </TableCell>
- </TableRow>
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- </div>
- <div className="space-y-3">
- <h3 className="text-sm font-medium text-foreground">Resource Access</h3>
- <Card className="w-full overflow-hidden bg-surface-100">
- <CardContent className="p-0">
- <Table className="p-5 table-auto">
- <TableHeader>
- <TableRow className="bg-200">
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2 w-[60%]">
- Resource
- </TableHead>
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2">
- Type
- </TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {getResourceAccessInfo().length > 0 ? (
- getResourceAccessInfo().map((resource, index) => (
- <TableRow key={`${resource.type}-${resource.identifier}-${index}`}>
- <TableCell>
- <p className="truncate text-foreground">{resource.name}</p>
- </TableCell>
- <TableCell>
- <span className="text-foreground-light">{resource.type}</span>
- </TableCell>
- </TableRow>
- ))
- ) : (
- <TableRow>
- <TableCell colSpan={2}>
- <p className="text-foreground-light text-center py-4">
- {(token?.organization_slugs &&
- token.organization_slugs.length > 0) ||
- (token?.project_refs && token.project_refs.length > 0)
- ? 'This token has access to specific organizations and projects.'
- : 'This token has access to all resources.'}
- </p>
- </TableCell>
- </TableRow>
- )}
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- </div>
- <div className="space-y-3">
- <h3 className="text-sm font-medium text-foreground">Permissions</h3>
- <Card className="w-full overflow-hidden bg-surface-100">
- <CardContent className="p-0">
- <Table className="p-5 table-auto">
- <TableHeader>
- <TableRow className="bg-200">
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2 w-[60%]">
- Permission
- </TableHead>
- <TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2">
- Access
- </TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {Object.keys(groupedResourcesByAccess).length === 0 ? (
- <TableRow>
- <TableCell colSpan={2}>
- <p className="text-foreground-light text-center py-4">
- No permissions configured for this token.
- </p>
- </TableCell>
- </TableRow>
- ) : (
- Object.entries(groupedResourcesByAccess).map(
- ([accessLevel, resources]) => {
- return resources.map((resource) => (
- <TableRow key={`${accessLevel}-${resource}`}>
- <TableCell>
- <p className="truncate text-foreground capitalize">
- {resource}
- </p>
- </TableCell>
- <TableCell>
- <span className="text-foreground-light">
- {formatAccessText(accessLevel)}
- </span>
- </TableCell>
- </TableRow>
- ))
- }
- )
- )}
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- </div>
- </>
- )}
- </div>
- </ScrollArea>
- </SheetContent>
- </Sheet>
- </>
- )
- }
|