mirror of
https://github.com/supabase/supabase.git
synced 2026-06-16 02:26:42 +08:00
## Problem
Log drains were only available per project. Organizations had no way to
export their platform audit logs to a third party destination, which had
to be set up manually through the API.
## Fix
Add a self-serve "Audit Log Drains" page under Organization Settings
(Compliance section) that reuses the existing log drains destination UI
at the org scope.
- Extract a presentational `LogDrainsList` shared by the project and org
containers, with no behavior change to the project page.
- Make `LogDrainDestinationSheetForm` presentational via
`existingDrainNames` and `onSaveClick` props, removing its project-only
data and telemetry coupling.
- Add org-scoped data hooks (list, create, update, delete, test
connection) calling
`/platform/organizations/{slug}/analytics/audit-log-drains`, gated by
the `audit_log_drains` entitlement.
- Add the page, nav entry and a keyboard shortcut, all gated behind the
`auditLogsLogDrain` feature flag and `IS_PLATFORM`.
The org audit log drain endpoints are not yet present in the generated
API types, so the new hooks use a localized `// @ts-ignore` (matching
the existing project log drain hooks) until the types are regenerated.
## How to test
- Open `/org/{slug}/audit-log-drains` on an org with the
`audit_log_drains` entitlement.
- Create an S3 and a webhook destination, confirm the cost dialog, then
delete one and test a connection.
- Confirm the list refreshes and that the existing project Log Drains
page is unchanged.
- Confirm the page and nav entry are hidden when the flag is off.
## Notes
- Verified locally: org data hook tests and the org settings nav
shortcut tests pass. Full typecheck, lint and the component test suite
should be run in CI, since this sandbox has an incomplete dependency
install.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Audit Log Drains management in organization settings: add, update,
test, and delete destinations; new Audit Log Drains page and navigation
shortcut.
* **Improvements**
* New consolidated list view with clearer loading, error, empty and
populated states.
* Feature-flag driven display of available drain types.
* Form validation prevents duplicate names and supports save callbacks
with telemetry on save.
* **Tests**
* Added tests covering listing, create/update/delete, testing, and form
validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
283 lines
7.3 KiB
TypeScript
283 lines
7.3 KiB
TypeScript
import { useFlag, useParams } from 'common'
|
|
import { PropsWithChildren, useMemo } from 'react'
|
|
|
|
import { useIsPlatformWebhooksEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
|
|
import type { SidebarSection } from '@/components/layouts/AccountLayout/AccountLayout.types'
|
|
import { toSubMenuSections } from '@/components/layouts/AccountLayout/AccountLayout.utils'
|
|
import { WithSidebar } from '@/components/layouts/AccountLayout/WithSidebar'
|
|
import { ProductMenuShortcuts } from '@/components/ui/ProductMenu/ProductMenuShortcuts'
|
|
import { convertSectionsToProductMenu } from '@/components/ui/ProductMenu/SubMenu.utils'
|
|
import { useCurrentPath } from '@/hooks/misc/useCurrentPath'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
|
|
interface OrganizationSettingsMenuItemsProps {
|
|
slug?: string
|
|
showSecuritySettings?: boolean
|
|
showSsoSettings?: boolean
|
|
showLegalDocuments?: boolean
|
|
showPlatformWebhooks?: boolean
|
|
showPrivateApps?: boolean
|
|
showAuditLogDrains?: boolean
|
|
}
|
|
|
|
interface OrganizationSettingsSectionsProps extends OrganizationSettingsMenuItemsProps {
|
|
currentPath: string
|
|
}
|
|
|
|
export const normalizeOrganizationSettingsPath = (path: string) => path.split('#')[0]
|
|
|
|
export const generateOrganizationSettingsMenuItems = ({
|
|
slug,
|
|
showSecuritySettings = true,
|
|
showSsoSettings = true,
|
|
showLegalDocuments = true,
|
|
showPlatformWebhooks = true,
|
|
showPrivateApps: _showPrivateApps = false,
|
|
showAuditLogDrains = false,
|
|
}: OrganizationSettingsMenuItemsProps) => [
|
|
{
|
|
key: 'general',
|
|
label: 'General',
|
|
href: `/org/${slug}/general`,
|
|
},
|
|
...(showSecuritySettings
|
|
? [
|
|
{
|
|
key: 'security',
|
|
label: 'Security',
|
|
href: `/org/${slug}/security`,
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
key: 'apps',
|
|
label: 'OAuth Apps',
|
|
href: `/org/${slug}/apps`,
|
|
},
|
|
...(showSsoSettings
|
|
? [
|
|
{
|
|
key: 'sso',
|
|
label: 'SSO',
|
|
href: `/org/${slug}/sso`,
|
|
},
|
|
]
|
|
: []),
|
|
...(showPlatformWebhooks
|
|
? [
|
|
{
|
|
key: 'webhooks',
|
|
label: 'Webhooks',
|
|
href: `/org/${slug}/webhooks`,
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
key: 'audit',
|
|
label: 'Audit Logs',
|
|
href: `/org/${slug}/audit`,
|
|
},
|
|
...(showAuditLogDrains
|
|
? [
|
|
{
|
|
key: 'audit-log-drains',
|
|
label: 'Audit Log Drains',
|
|
href: `/org/${slug}/audit-log-drains`,
|
|
},
|
|
]
|
|
: []),
|
|
...(showLegalDocuments
|
|
? [
|
|
{
|
|
key: 'documents',
|
|
label: 'Legal Documents',
|
|
href: `/org/${slug}/documents`,
|
|
},
|
|
]
|
|
: []),
|
|
]
|
|
|
|
export const generateOrganizationSettingsSections = ({
|
|
currentPath,
|
|
slug,
|
|
showSecuritySettings = true,
|
|
showSsoSettings = true,
|
|
showLegalDocuments = true,
|
|
showPlatformWebhooks = true,
|
|
showPrivateApps = false,
|
|
showAuditLogDrains = false,
|
|
}: OrganizationSettingsSectionsProps): SidebarSection[] => {
|
|
const isLinkActive = (key: string, href: string) =>
|
|
key === 'webhooks'
|
|
? currentPath === href || currentPath.startsWith(`${href}/`)
|
|
: currentPath === href
|
|
|
|
const configurationLinks = [
|
|
{
|
|
key: 'general',
|
|
label: 'General',
|
|
href: `/org/${slug}/general`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_GENERAL,
|
|
},
|
|
...(showSecuritySettings
|
|
? [
|
|
{
|
|
key: 'security',
|
|
label: 'Security',
|
|
href: `/org/${slug}/security`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_SECURITY,
|
|
},
|
|
]
|
|
: []),
|
|
...(showSsoSettings
|
|
? [
|
|
{
|
|
key: 'sso',
|
|
label: 'SSO',
|
|
href: `/org/${slug}/sso`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_SSO,
|
|
},
|
|
]
|
|
: []),
|
|
]
|
|
|
|
const connectionsLinks = [
|
|
{
|
|
key: 'apps',
|
|
label: 'OAuth Apps',
|
|
href: `/org/${slug}/apps`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_APPS,
|
|
},
|
|
...(showPrivateApps
|
|
? [
|
|
{
|
|
key: 'private-apps',
|
|
label: 'Private Apps',
|
|
href: `/org/${slug}/private-apps`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_PRIVATE_APPS,
|
|
},
|
|
]
|
|
: []),
|
|
...(showPlatformWebhooks
|
|
? [
|
|
{
|
|
key: 'webhooks',
|
|
label: 'Webhooks',
|
|
href: `/org/${slug}/webhooks`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_WEBHOOKS,
|
|
},
|
|
]
|
|
: []),
|
|
]
|
|
|
|
const complianceLinks = [
|
|
{
|
|
key: 'audit',
|
|
label: 'Audit Logs',
|
|
href: `/org/${slug}/audit`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_AUDIT,
|
|
},
|
|
...(showAuditLogDrains
|
|
? [
|
|
{
|
|
key: 'audit-log-drains',
|
|
label: 'Audit Log Drains',
|
|
href: `/org/${slug}/audit-log-drains`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_AUDIT_LOG_DRAINS,
|
|
},
|
|
]
|
|
: []),
|
|
...(showLegalDocuments
|
|
? [
|
|
{
|
|
key: 'documents',
|
|
label: 'Legal Documents',
|
|
href: `/org/${slug}/documents`,
|
|
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS_DOCUMENTS,
|
|
},
|
|
]
|
|
: []),
|
|
]
|
|
|
|
return [
|
|
{
|
|
key: 'configuration',
|
|
heading: 'Configuration',
|
|
links: configurationLinks.map((item) => ({
|
|
...item,
|
|
isActive: isLinkActive(item.key, item.href),
|
|
})),
|
|
},
|
|
{
|
|
key: 'connections',
|
|
heading: 'Connections',
|
|
links: connectionsLinks.map((item) => ({
|
|
...item,
|
|
isActive: isLinkActive(item.key, item.href),
|
|
})),
|
|
},
|
|
{
|
|
key: 'compliance',
|
|
heading: 'Compliance',
|
|
links: complianceLinks.map((item) => ({
|
|
...item,
|
|
isActive: isLinkActive(item.key, item.href),
|
|
})),
|
|
},
|
|
]
|
|
}
|
|
|
|
export function OrganizationSettingsLayout({ children }: PropsWithChildren) {
|
|
const { slug } = useParams()
|
|
const showPlatformWebhooks = useIsPlatformWebhooksEnabled()
|
|
const showPrivateApps = useFlag('privateApps')
|
|
const showAuditLogDrains = useFlag('auditLogsLogDrain')
|
|
const fullCurrentPath = useCurrentPath()
|
|
const currentPath = normalizeOrganizationSettingsPath(fullCurrentPath)
|
|
|
|
const {
|
|
organizationShowSsoSettings: showSsoSettings,
|
|
organizationShowSecuritySettings: showSecuritySettings,
|
|
organizationShowLegalDocuments: showLegalDocuments,
|
|
} = useIsFeatureEnabled([
|
|
'organization:show_sso_settings',
|
|
'organization:show_security_settings',
|
|
'organization:show_legal_documents',
|
|
])
|
|
|
|
const sections = generateOrganizationSettingsSections({
|
|
currentPath,
|
|
slug,
|
|
showSecuritySettings,
|
|
showSsoSettings,
|
|
showLegalDocuments,
|
|
showPlatformWebhooks,
|
|
showPrivateApps,
|
|
showAuditLogDrains,
|
|
})
|
|
|
|
const orgSettingsMenu = useMemo(
|
|
() => convertSectionsToProductMenu(toSubMenuSections(sections)),
|
|
[sections]
|
|
)
|
|
|
|
// Browser titles for org settings routes are set by OrganizationLayout.
|
|
return (
|
|
<>
|
|
<ProductMenuShortcuts menu={orgSettingsMenu} />
|
|
<WithSidebar
|
|
title="Organization Settings"
|
|
sections={sections}
|
|
header={
|
|
<div className="border-default flex min-h-(--header-height) items-center border-b px-6">
|
|
<h4 className="text-lg">Settings</h4>
|
|
</div>
|
|
}
|
|
>
|
|
{children}
|
|
</WithSidebar>
|
|
</>
|
|
)
|
|
}
|