AuditLogsForm.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { PermissionAction } from '@supabase/shared-types/out/constants'
  3. import { useParams } from 'common'
  4. import { useEffect } from 'react'
  5. import { useForm } from 'react-hook-form'
  6. import { toast } from 'sonner'
  7. import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, Switch } from 'ui'
  8. import { Admonition, GenericSkeletonLoader } from 'ui-patterns'
  9. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  10. import {
  11. PageSection,
  12. PageSectionContent,
  13. PageSectionMeta,
  14. PageSectionSummary,
  15. PageSectionTitle,
  16. } from 'ui-patterns/PageSection'
  17. import * as z from 'zod'
  18. import { AlertError } from '@/components/ui/AlertError'
  19. import { InlineLink } from '@/components/ui/InlineLink'
  20. import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
  21. import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
  22. import { useTablesQuery } from '@/data/tables/tables-query'
  23. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  24. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  25. const schema = z.object({
  26. AUDIT_LOG_DISABLE_POSTGRES: z.boolean(),
  27. })
  28. const AUDIT_LOG_ENTRIES_TABLE = 'audit_log_entries'
  29. export const AuditLogsForm = () => {
  30. const { ref: projectRef } = useParams()
  31. const { data: project } = useSelectedProjectQuery()
  32. const { can: canUpdateConfig } = useAsyncCheckPermissions(
  33. PermissionAction.UPDATE,
  34. 'custom_config_gotrue'
  35. )
  36. const { data: tables = [] } = useTablesQuery({
  37. projectRef: project?.ref,
  38. connectionString: project?.connectionString,
  39. includeColumns: false,
  40. schema: 'auth',
  41. })
  42. const auditLogTable = tables.find((x) => x.name === AUDIT_LOG_ENTRIES_TABLE)
  43. const {
  44. data: authConfig,
  45. error: authConfigError,
  46. isError,
  47. isPending: isLoading,
  48. } = useAuthConfigQuery({ projectRef })
  49. const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation({
  50. onError: (error) => {
  51. toast.error(`Failed to update audit logs: ${error?.message}`)
  52. },
  53. onSuccess: () => {
  54. toast.success('Successfully updated audit logs settings')
  55. },
  56. })
  57. const form = useForm({
  58. resolver: zodResolver(schema as any),
  59. defaultValues: { AUDIT_LOG_DISABLE_POSTGRES: false },
  60. })
  61. const { isDirty } = form.formState
  62. const { AUDIT_LOG_DISABLE_POSTGRES: formValueDisablePostgres } = form.watch()
  63. const currentlyDisabled = authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false
  64. const isDisabling = !currentlyDisabled && formValueDisablePostgres
  65. const onSubmitAuditLogs = (values: any) => {
  66. if (!projectRef) return console.error('Project ref is required')
  67. updateAuthConfig({ projectRef: projectRef, config: values })
  68. }
  69. useEffect(() => {
  70. if (authConfig) {
  71. form.reset({ AUDIT_LOG_DISABLE_POSTGRES: authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false })
  72. }
  73. }, [authConfig])
  74. if (isError) {
  75. return (
  76. <PageSection>
  77. <PageSectionContent>
  78. <AlertError
  79. error={authConfigError}
  80. subject="Failed to retrieve auth configuration for hooks"
  81. />
  82. </PageSectionContent>
  83. </PageSection>
  84. )
  85. }
  86. if (isLoading) {
  87. return (
  88. <PageSection>
  89. <PageSectionContent>
  90. <GenericSkeletonLoader />
  91. </PageSectionContent>
  92. </PageSection>
  93. )
  94. }
  95. return (
  96. <PageSection>
  97. <PageSectionMeta>
  98. <PageSectionSummary>
  99. <PageSectionTitle>Settings</PageSectionTitle>
  100. </PageSectionSummary>
  101. </PageSectionMeta>
  102. <PageSectionContent>
  103. <Form {...form}>
  104. <form onSubmit={form.handleSubmit(onSubmitAuditLogs)} className="space-y-4">
  105. <Card>
  106. <CardContent>
  107. <FormField
  108. control={form.control}
  109. name="AUDIT_LOG_DISABLE_POSTGRES"
  110. render={({ field }) => (
  111. <FormItemLayout
  112. layout="flex-row-reverse"
  113. label="Write audit logs to the database"
  114. description={
  115. <p className="text-sm prose text-foreground-lighter max-w-full">
  116. When enabled, audit logs are written to the{' '}
  117. <InlineLink
  118. target="_blank"
  119. rel="noopener noreferrer"
  120. href={`/project/${projectRef}/editor/${auditLogTable?.id}`}
  121. >
  122. <code className="text-code-inline">{AUDIT_LOG_ENTRIES_TABLE}</code>
  123. </InlineLink>{' '}
  124. table.
  125. <br />
  126. You can disable this to reduce disk usage while still accessing logs
  127. through the{' '}
  128. <InlineLink
  129. href={`/project/${projectRef}/logs/explorer?q=select%0A++cast(timestamp+as+datetime)+as+timestamp%2C%0A++event_message%2C+metadata+%0Afrom+auth_audit_logs+%0Alimit+10%0A`}
  130. >
  131. Auth logs
  132. </InlineLink>
  133. .
  134. </p>
  135. }
  136. >
  137. <FormControl>
  138. <Switch
  139. checked={!field.value}
  140. onCheckedChange={(value) => field.onChange(!value)}
  141. disabled={!canUpdateConfig}
  142. />
  143. </FormControl>
  144. </FormItemLayout>
  145. )}
  146. />
  147. {isDisabling && (
  148. <Admonition
  149. type="warning"
  150. className="mt-4"
  151. title="Disabling PostgreSQL storage will not automatically migrate or transfer existing audit log data"
  152. description={
  153. <p>
  154. Future audit logs will only appear in the project’s{' '}
  155. <InlineLink
  156. href={`/project/${projectRef}/logs/explorer?q=select%0A++cast(timestamp+as+datetime)+as+timestamp%2C%0A++event_message%2C+metadata+%0Afrom+auth_audit_logs+%0Alimit+10%0A`}
  157. >
  158. auth logs
  159. </InlineLink>
  160. . You are responsible for backing up, copying, or migrating existing data
  161. from the{' '}
  162. <code className="text-code-inline break-keep!">
  163. {AUDIT_LOG_ENTRIES_TABLE}
  164. </code>{' '}
  165. table if needed.
  166. </p>
  167. }
  168. />
  169. )}
  170. </CardContent>
  171. <CardFooter className="justify-end space-x-2">
  172. {isDirty && (
  173. <Button type="default" onClick={() => form.reset()}>
  174. Cancel
  175. </Button>
  176. )}
  177. <Button
  178. type="primary"
  179. htmlType="submit"
  180. disabled={!canUpdateConfig || isUpdatingConfig || !isDirty}
  181. loading={isUpdatingConfig}
  182. >
  183. Save changes
  184. </Button>
  185. </CardFooter>
  186. </Card>
  187. </form>
  188. </Form>
  189. </PageSectionContent>
  190. </PageSection>
  191. )
  192. }