mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
## Summary This is the final step in merging compute and disk with infrastructure to become a single place to manage everything. This moves everything we've done in compute and disk over to infrastructure along with redirects. - Makes Infrastructure canonical for the completed compute and disk configuration and usage charts. - Moves Service Versions to General Project Settings. - Removes the legacy Infrastructure activity implementation and constants. - Updates settings navigation, shortcuts, banners, billing links, warning CTAs, usage pages, support suggestions, and other internal entry points. - Adds the permanent `/settings/compute-and-disk` redirect, removes its Next and TanStack routes, regenerates the route tree, and updates the migration checklist. - Preserves query parameters and legacy metric anchors, including `#cpu`. ## Stack 1. #48368 2. #48369 3. #48370 (this PR) ## How to test 1. Check out `chore/infra-compute-3-cutover`. 2. Test the Next implementation with `pnpm dev:studio`, then stop it and test TanStack with `STUDIO_FRAMEWORK=tanstack pnpm dev:studio`. 3. In each implementation, open `/project/<ref>/settings/infrastructure`. Confirm the page contains the usage charts and the Scaling, Compute, Disk, and Advanced configuration sections. 4. Open `/project/<ref>/settings/general`. Confirm Service Versions appears there with its existing name, content, and styling, and no longer appears on Infrastructure. 5. Open `/project/<ref>/settings/compute-and-disk?upgrade=micro#disk`. Confirm it permanently redirects to `/project/<ref>/settings/infrastructure?upgrade=micro#disk`, preserving the query string and hash. 6. Confirm the settings menu exposes Infrastructure and no longer exposes Compute and Disk. Repeat with platform and self-hosted settings. 7. Follow representative entry points from billing usage, resource warning CTAs, upgrade banners, shortcuts, and support suggestions. Confirm they land on Infrastructure and preserve any query parameters or metric anchors such as `#cpu`. 8. Smoke-test compute and disk updates from Infrastructure, including validation, the sticky review footer, and warning/critical chart states. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Consolidated compute and disk management under the **Infrastructure** project settings page. * Added a **Service versions** section to **General** project settings. * **Bug Fixes** * Updated links and upgrade CTAs across the product to route to the correct **Infrastructure** or **Service versions** destinations. * Added permanent redirects from legacy **Compute and Disk** to **Infrastructure**, preserving query/hash. * Improved resource warning upgrade routing for compute scenarios. * **Tests** * Expanded automated coverage for **Infrastructure**, **Service versions**, redirects, and warning-link routing. * **Chores** * Updated ESLint rule baseline configuration for the studio app. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
550 lines
20 KiB
TypeScript
550 lines
20 KiB
TypeScript
import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
|
||
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
||
import { Loader2 } from 'lucide-react'
|
||
import Link from 'next/link'
|
||
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
|
||
import { useEffect, useMemo, useState } from 'react'
|
||
import { toast } from 'sonner'
|
||
import {
|
||
AlertDialog,
|
||
AlertDialogAction,
|
||
AlertDialogBody,
|
||
AlertDialogCancel,
|
||
AlertDialogContent,
|
||
AlertDialogDescription,
|
||
AlertDialogFooter,
|
||
AlertDialogHeader,
|
||
AlertDialogTitle,
|
||
Button,
|
||
Card,
|
||
CardContent,
|
||
Switch,
|
||
Tooltip,
|
||
TooltipContent,
|
||
TooltipTrigger,
|
||
} from 'ui'
|
||
import { Admonition } from 'ui-patterns/Admonition'
|
||
import { FormLayout } from 'ui-patterns/form/Layout/FormLayout'
|
||
import {
|
||
PageSection,
|
||
PageSectionContent,
|
||
PageSectionMeta,
|
||
PageSectionSummary,
|
||
PageSectionTitle,
|
||
} from 'ui-patterns/PageSection'
|
||
|
||
import type { JitUserRule, SheetMode } from './JitDbAccess.types'
|
||
import {
|
||
getAssignableJitRoleOptions,
|
||
getJitMemberOptions,
|
||
mapJitMembersToUserRules,
|
||
} from './JitDbAccess.utils'
|
||
import { JitDbAccessDeleteDialog } from './JitDbAccessDeleteDialog'
|
||
import { JitDbAccessRuleSheet } from './JitDbAccessRuleSheet'
|
||
import { JitDbAccessRulesTable } from './JitDbAccessRulesTable'
|
||
import { getServiceVersionsPath } from '@/components/interfaces/Settings/General/ServiceVersions/ServiceVersions.utils'
|
||
import { SupportLink } from '@/components/interfaces/Support/SupportLink'
|
||
import { AlertError } from '@/components/ui/AlertError'
|
||
import { DocsButton } from '@/components/ui/DocsButton'
|
||
import { FeaturePreviewBadge } from '@/components/ui/FeaturePreviewBadge'
|
||
import { InlineLink, InlineLinkClassName } from '@/components/ui/InlineLink'
|
||
import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query'
|
||
import { useJitDbAccessMembersQuery } from '@/data/jit-db-access/jit-db-access-members-query'
|
||
import { useJitDbAccessQuery } from '@/data/jit-db-access/jit-db-access-query'
|
||
import { useJitDbAccessRevokeMutation } from '@/data/jit-db-access/jit-db-access-revoke-mutation'
|
||
import { useJitDbAccessUpdateMutation } from '@/data/jit-db-access/jit-db-access-update-mutation'
|
||
import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
|
||
import { useProjectMembersQuery } from '@/data/projects/project-members-query'
|
||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||
import { DOCS_URL } from '@/lib/constants'
|
||
|
||
export const JitDbAccessConfiguration = () => {
|
||
const { ref } = useParams()
|
||
const { data: project } = useSelectedProjectQuery()
|
||
const { data: organization } = useSelectedOrganizationQuery()
|
||
|
||
const parentProjectRef = project?.parent_project_ref
|
||
|
||
const [enabled, setEnabled] = useState(false)
|
||
const [, setShowCreateRuleSheet] = useQueryState('jit_new', parseAsBoolean.withDefault(false))
|
||
const [ruleIdToEdit, setRuleIdToEdit] = useQueryState('jit_edit', parseAsString)
|
||
const [showEnableJitDialog, setShowEnableJitDialog] = useState(false)
|
||
const [enableJitError, setEnableJitError] = useState<string | null>(null)
|
||
const [selectedUserToDelete, setSelectedUserToDelete] = useState<JitUserRule | null>(null)
|
||
const [deleteRuleError, setDeleteRuleError] = useState<string | null>(null)
|
||
|
||
const {
|
||
data: jitDbAccessConfiguration,
|
||
error: jitDbAccessConfigurationError,
|
||
isError: isErrorJitDbAccessConfiguration,
|
||
isLoading: isLoadingConfiguration,
|
||
isSuccess: isSuccessConfiguration,
|
||
} = useJitDbAccessQuery({ projectRef: ref })
|
||
|
||
const {
|
||
data: jitMembers,
|
||
error: jitMembersError,
|
||
isError: isErrorJitMembers,
|
||
isLoading: isLoadingJitMembers,
|
||
} = useJitDbAccessMembersQuery({ projectRef: ref })
|
||
|
||
const { data: projectMembers, isLoading: isLoadingProjectMembers } = useProjectMembersQuery({
|
||
projectRef: ref,
|
||
})
|
||
|
||
const { data: organizationMembers, isLoading: isLoadingOrganizationMembers } =
|
||
useOrganizationMembersQuery({ slug: organization?.slug })
|
||
|
||
const { data: databaseRoles, isLoading: isLoadingDatabaseRoles } = useDatabaseRolesQuery({
|
||
projectRef: ref,
|
||
connectionString: project?.connectionString,
|
||
})
|
||
|
||
const { can: canUpdateJitDbAccess } = useAsyncCheckPermissions(
|
||
PermissionAction.UPDATE,
|
||
'projects',
|
||
{ resource: { project_id: project?.id } }
|
||
)
|
||
|
||
const { mutateAsync: updateJitDbAccess, isPending: isUpdatingJitDbAccess } =
|
||
useJitDbAccessUpdateMutation({
|
||
onSuccess: (_, variables) => {
|
||
const nextEnabled = variables.requestedConfig.state === 'enabled'
|
||
|
||
if (nextEnabled) {
|
||
toast.success('Temporary access enabled')
|
||
} else {
|
||
toast.success(
|
||
activeRuleCount > 0
|
||
? `Temporary access disabled. ${activeRuleCount} configured member${activeRuleCount === 1 ? '' : 's'} can no longer request temporary database access.`
|
||
: 'Temporary access disabled'
|
||
)
|
||
}
|
||
},
|
||
onError: () => {},
|
||
})
|
||
|
||
const { mutateAsync: revokeUserAccess, isPending: isRevokingAccess } =
|
||
useJitDbAccessRevokeMutation({
|
||
onSuccess: (_, variables) => {
|
||
toast.success('Successfully revoked user access')
|
||
setSelectedUserToDelete(null)
|
||
if (ruleIdToEdit === variables.userId) resetSheetState()
|
||
},
|
||
onError: () => {},
|
||
})
|
||
|
||
const isMutating = isUpdatingJitDbAccess || isRevokingAccess
|
||
const disableRuleActions = isMutating || isLoadingDatabaseRoles || isLoadingOrganizationMembers
|
||
const isRulesLoading = isLoadingJitMembers || isLoadingProjectMembers
|
||
|
||
const initialIsEnabled =
|
||
jitDbAccessConfiguration?.state === 'enabled'
|
||
? jitDbAccessConfiguration?.appliedSuccessfully
|
||
: false
|
||
const isJitDbAccessUnavailable = jitDbAccessConfiguration?.state === 'unavailable'
|
||
const unavailableReason = isJitDbAccessUnavailable
|
||
? jitDbAccessConfiguration.unavailableReason
|
||
: undefined
|
||
|
||
const roleOptions = useMemo(() => getAssignableJitRoleOptions(databaseRoles), [databaseRoles])
|
||
|
||
const users = useMemo(
|
||
() => mapJitMembersToUserRules(jitMembers, projectMembers, roleOptions),
|
||
[jitMembers, projectMembers, roleOptions]
|
||
)
|
||
|
||
const allMembers = useMemo(
|
||
() => getJitMemberOptions(organizationMembers, projectMembers),
|
||
[organizationMembers, projectMembers]
|
||
)
|
||
|
||
const editingUser = useMemo(
|
||
() => users.find((user) => user.id === ruleIdToEdit) ?? null,
|
||
[users, ruleIdToEdit]
|
||
)
|
||
|
||
const sheetMode: SheetMode = ruleIdToEdit ? 'edit' : 'add'
|
||
|
||
const membersWithRules = useMemo(() => new Set(users.map((user) => user.memberId)), [users])
|
||
|
||
const availableMembersForAdd = useMemo(
|
||
() => allMembers.filter((member) => !membersWithRules.has(member.id)),
|
||
[allMembers, membersWithRules]
|
||
)
|
||
|
||
const memberOptionsForSheet = useMemo(() => {
|
||
if (sheetMode !== 'edit') return availableMembersForAdd
|
||
if (!editingUser) return allMembers
|
||
|
||
if (allMembers.some((member) => member.id === editingUser.memberId)) return allMembers
|
||
|
||
return [
|
||
{
|
||
id: editingUser.memberId,
|
||
email: editingUser.email,
|
||
name: editingUser.name,
|
||
},
|
||
...allMembers,
|
||
]
|
||
}, [sheetMode, availableMembersForAdd, allMembers, editingUser])
|
||
|
||
const activeRuleCount = useMemo(
|
||
() => users.filter((user) => user.status.active > 0).length,
|
||
[users]
|
||
)
|
||
|
||
const resetSheetState = () => {
|
||
setShowCreateRuleSheet(false)
|
||
setRuleIdToEdit(null)
|
||
}
|
||
|
||
const getErrorMessage = (error: unknown) =>
|
||
error instanceof Error ? error.message : 'An unknown error occurred'
|
||
|
||
const submitJitToggle = async (nextEnabled: boolean) => {
|
||
if (!ref) throw new Error('Project ref is required')
|
||
|
||
setEnabled(nextEnabled)
|
||
|
||
try {
|
||
await updateJitDbAccess({
|
||
projectRef: ref,
|
||
requestedConfig: { state: nextEnabled ? 'enabled' : 'disabled' },
|
||
})
|
||
} catch (error) {
|
||
setEnabled(initialIsEnabled ?? false)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
const handleJitToggleChange = (checked: boolean) => {
|
||
if (isJitDbAccessUnavailable || !canUpdateJitDbAccess) return
|
||
|
||
if (checked && !enabled) {
|
||
if (activeRuleCount > 0) {
|
||
setEnableJitError(null)
|
||
return setShowEnableJitDialog(true)
|
||
}
|
||
return void submitJitToggle(true).catch((error) => {
|
||
toast.error(`Failed to update temporary access: ${getErrorMessage(error)}`)
|
||
})
|
||
}
|
||
|
||
if (!checked && enabled) {
|
||
void submitJitToggle(false).catch((error) => {
|
||
toast.error(`Failed to update temporary access: ${getErrorMessage(error)}`)
|
||
})
|
||
}
|
||
}
|
||
|
||
const handleConfirmEnableJit = async () => {
|
||
setEnableJitError(null)
|
||
|
||
try {
|
||
await submitJitToggle(true)
|
||
} catch (error) {
|
||
setEnableJitError(getErrorMessage(error))
|
||
throw error
|
||
}
|
||
}
|
||
|
||
const openAddRuleSheet = () => {
|
||
if (!canUpdateJitDbAccess) return
|
||
setRuleIdToEdit(null)
|
||
setShowCreateRuleSheet(true)
|
||
}
|
||
|
||
const openEditRuleSheet = (user: JitUserRule) => {
|
||
if (!canUpdateJitDbAccess) return
|
||
setShowCreateRuleSheet(false)
|
||
setRuleIdToEdit(user.id)
|
||
}
|
||
|
||
const openDeleteDialog = (user: JitUserRule) => {
|
||
if (!canUpdateJitDbAccess) return
|
||
setDeleteRuleError(null)
|
||
setSelectedUserToDelete(user)
|
||
}
|
||
|
||
const handleConfirmDelete = async () => {
|
||
setDeleteRuleError(null)
|
||
|
||
try {
|
||
if (!ref) throw new Error('Project ref is required')
|
||
if (!selectedUserToDelete) throw new Error('User is required')
|
||
|
||
await revokeUserAccess({ projectRef: ref, userId: selectedUserToDelete.memberId })
|
||
} catch (error) {
|
||
setDeleteRuleError(getErrorMessage(error))
|
||
throw error
|
||
}
|
||
}
|
||
|
||
const switchDisabled = isLoadingConfiguration || isUpdatingJitDbAccess || !canUpdateJitDbAccess
|
||
const switchTooltipText = !canUpdateJitDbAccess ? 'Additional permissions required' : undefined
|
||
|
||
const showToggleFailedWarning =
|
||
isSuccessConfiguration &&
|
||
jitDbAccessConfiguration?.state !== 'unavailable' &&
|
||
!jitDbAccessConfiguration.appliedSuccessfully
|
||
|
||
const projectReference = ref ? (
|
||
<>
|
||
This project <code className="text-code-inline">{ref}</code>
|
||
</>
|
||
) : (
|
||
'This project'
|
||
)
|
||
const unavailableTitle =
|
||
unavailableReason === 'postgres_upgrade_required'
|
||
? 'Postgres upgrade required'
|
||
: unavailableReason === 'manual_migration_required'
|
||
? 'Migration required'
|
||
: unavailableReason === 'ssl_enforcement_required'
|
||
? 'SSL enforcement required'
|
||
: 'Temporary access unavailable'
|
||
const unavailableDescription =
|
||
unavailableReason === 'postgres_upgrade_required'
|
||
? 'must be upgraded to Postgres 17 or later before temporary access can be enabled.'
|
||
: unavailableReason === 'manual_migration_required'
|
||
? 'must be migrated before temporary access can be enabled. Contact support to migrate this project.'
|
||
: unavailableReason === 'ssl_enforcement_required'
|
||
? 'must have SSL enforcement enabled before temporary access can be enabled.'
|
||
: 'This feature is currently unavailable for this project. Contact support if you need help enabling it.'
|
||
|
||
useEffect(() => {
|
||
if (!isLoadingConfiguration && jitDbAccessConfiguration) {
|
||
setEnabled(initialIsEnabled ?? false)
|
||
}
|
||
}, [initialIsEnabled, isLoadingConfiguration, jitDbAccessConfiguration])
|
||
|
||
return (
|
||
<>
|
||
<PageSection id="jit-db-access-configuration">
|
||
<PageSectionMeta>
|
||
<PageSectionSummary>
|
||
<PageSectionTitle>
|
||
<span className="flex items-center gap-x-4">
|
||
Temporary access
|
||
<FeaturePreviewBadge featureKey={LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS} />
|
||
</span>
|
||
</PageSectionTitle>
|
||
</PageSectionSummary>
|
||
<DocsButton href={`${DOCS_URL}/guides/platform/temporary-access`} />
|
||
</PageSectionMeta>
|
||
|
||
<PageSectionContent className="space-y-4">
|
||
{parentProjectRef && (
|
||
<Admonition
|
||
type="note"
|
||
title="Managed in the main branch"
|
||
description={
|
||
<>
|
||
Temporary access rules are configured in the main branch and apply across all
|
||
preview branches. Return to the{' '}
|
||
<InlineLink href={`/project/${parentProjectRef}/settings/database`}>
|
||
main branch
|
||
</InlineLink>{' '}
|
||
to manage your access rules.
|
||
</>
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{!parentProjectRef && isErrorJitDbAccessConfiguration && (
|
||
<AlertError
|
||
projectRef={ref}
|
||
subject="Failed to load temporary access"
|
||
error={jitDbAccessConfigurationError as { message: string } | null}
|
||
showInstructions={false}
|
||
/>
|
||
)}
|
||
|
||
{!parentProjectRef && !isErrorJitDbAccessConfiguration && isJitDbAccessUnavailable && (
|
||
<Admonition
|
||
type="note"
|
||
layout="responsive"
|
||
title={unavailableTitle}
|
||
description={
|
||
unavailableReason === 'temporarily_unavailable' ? (
|
||
unavailableDescription
|
||
) : (
|
||
<>
|
||
{projectReference} {unavailableDescription}
|
||
</>
|
||
)
|
||
}
|
||
actions={
|
||
unavailableReason === 'postgres_upgrade_required' && ref ? (
|
||
<Button variant="default" asChild>
|
||
<Link href={getServiceVersionsPath(ref)}>Upgrade Postgres</Link>
|
||
</Button>
|
||
) : unavailableReason === 'ssl_enforcement_required' && ref ? (
|
||
<Button variant="default" asChild>
|
||
<Link href={`/project/${ref}/settings/database#ssl-configuration`}>
|
||
Enable SSL enforcement
|
||
</Link>
|
||
</Button>
|
||
) : (
|
||
<Button variant="default" asChild>
|
||
<SupportLink
|
||
queryParams={{
|
||
category: SupportCategories.PROBLEM,
|
||
projectRef: ref,
|
||
subject: unavailableTitle,
|
||
}}
|
||
>
|
||
Contact support
|
||
</SupportLink>
|
||
</Button>
|
||
)
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{!parentProjectRef && !isErrorJitDbAccessConfiguration && !isJitDbAccessUnavailable && (
|
||
<Card>
|
||
<CardContent className="space-y-4">
|
||
<FormLayout
|
||
layout="flex-row-reverse"
|
||
label="Allow temporary access"
|
||
description="Let project members request temporary database access."
|
||
>
|
||
<div className="flex w-fit shrink-0 items-center justify-end gap-2">
|
||
{(isLoadingConfiguration || isUpdatingJitDbAccess) && (
|
||
<Loader2
|
||
className="animate-spin text-foreground-muted/50"
|
||
strokeWidth={2}
|
||
size={16}
|
||
/>
|
||
)}
|
||
<Tooltip>
|
||
<TooltipTrigger asChild>
|
||
{/* [Joshen] Added div as tooltip is messing with data state property of toggle */}
|
||
<div>
|
||
<Switch
|
||
size="large"
|
||
checked={enabled}
|
||
onCheckedChange={handleJitToggleChange}
|
||
disabled={switchDisabled}
|
||
/>
|
||
</div>
|
||
</TooltipTrigger>
|
||
{switchTooltipText && (
|
||
<TooltipContent side="bottom">{switchTooltipText}</TooltipContent>
|
||
)}
|
||
</Tooltip>
|
||
</div>
|
||
</FormLayout>
|
||
</CardContent>
|
||
{showToggleFailedWarning && (
|
||
<Admonition
|
||
type="warning"
|
||
layout="horizontal"
|
||
title="Temporary access update didn’t apply"
|
||
description={
|
||
<>
|
||
The change didn’t apply. Try enabling or disabling temporary access again, or{' '}
|
||
<SupportLink
|
||
queryParams={{
|
||
category: SupportCategories.DASHBOARD_BUG,
|
||
subject: 'Temporary access was not updated successfully',
|
||
}}
|
||
className={InlineLinkClassName}
|
||
>
|
||
contact support
|
||
</SupportLink>{' '}
|
||
if the issue persists.
|
||
</>
|
||
}
|
||
className="mb-0 rounded-none border-0"
|
||
/>
|
||
)}
|
||
</Card>
|
||
)}
|
||
|
||
{!parentProjectRef && enabled && !isJitDbAccessUnavailable && !isUpdatingJitDbAccess && (
|
||
<>
|
||
{isErrorJitMembers && (
|
||
<AlertError
|
||
projectRef={ref}
|
||
subject="Failed to load temporary access rules"
|
||
error={jitMembersError as { message: string } | null}
|
||
showInstructions={false}
|
||
/>
|
||
)}
|
||
|
||
<JitDbAccessRulesTable
|
||
users={users}
|
||
isLoading={isRulesLoading}
|
||
canUpdate={!!canUpdateJitDbAccess}
|
||
disableActions={disableRuleActions}
|
||
allProjectMembersHaveRules={availableMembersForAdd.length === 0}
|
||
onAddRule={openAddRuleSheet}
|
||
onEditRule={openEditRuleSheet}
|
||
onDeleteRule={openDeleteDialog}
|
||
/>
|
||
</>
|
||
)}
|
||
</PageSectionContent>
|
||
</PageSection>
|
||
|
||
<JitDbAccessRuleSheet
|
||
memberOptions={memberOptionsForSheet}
|
||
membersWithRules={membersWithRules}
|
||
availableMembersForAddCount={availableMembersForAdd.length}
|
||
/>
|
||
|
||
<JitDbAccessDeleteDialog
|
||
user={selectedUserToDelete}
|
||
isDeleting={isRevokingAccess}
|
||
error={deleteRuleError}
|
||
onClose={() => {
|
||
setDeleteRuleError(null)
|
||
setSelectedUserToDelete(null)
|
||
}}
|
||
onConfirm={handleConfirmDelete}
|
||
/>
|
||
|
||
<AlertDialog open={showEnableJitDialog} onOpenChange={setShowEnableJitDialog}>
|
||
<AlertDialogContent size="small">
|
||
<AlertDialogHeader>
|
||
<AlertDialogTitle>This will activate existing rules</AlertDialogTitle>
|
||
<AlertDialogDescription asChild>
|
||
<div className="text-sm">
|
||
<p>
|
||
Enabling temporary access will allow {activeRuleCount} pre-configured member
|
||
{activeRuleCount === 1 ? '' : 's'} to request temporary database access
|
||
immediately.
|
||
</p>
|
||
</div>
|
||
</AlertDialogDescription>
|
||
</AlertDialogHeader>
|
||
{enableJitError && (
|
||
<AlertDialogBody>
|
||
<Admonition
|
||
type="destructive"
|
||
title="Unable to enable temporary access"
|
||
description={enableJitError}
|
||
/>
|
||
</AlertDialogBody>
|
||
)}
|
||
<AlertDialogFooter>
|
||
<AlertDialogCancel disabled={isUpdatingJitDbAccess}>Cancel</AlertDialogCancel>
|
||
<AlertDialogAction
|
||
variant="warning"
|
||
loading={isUpdatingJitDbAccess}
|
||
onClick={handleConfirmEnableJit}
|
||
>
|
||
Enable temporary access
|
||
</AlertDialogAction>
|
||
</AlertDialogFooter>
|
||
</AlertDialogContent>
|
||
</AlertDialog>
|
||
</>
|
||
)
|
||
}
|