diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 703dee6046..1519733ead 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -472,6 +472,7 @@ export type BillingEntitlement = { }; export enum BillingEntitlementKey { + AUDIT_LOGS = 'AUDIT_LOGS', CUSTOM_DOMAIN = 'CUSTOM_DOMAIN', RLS = 'RLS', SSO = 'SSO' @@ -1384,6 +1385,56 @@ export type EmailsConfiguration = { configurationType: WidgetConfigurationType; }; +export type EventLogDateRangeInput = { + end?: InputMaybe; + start?: InputMaybe; +}; + +export type EventLogFiltersInput = { + dateRange?: InputMaybe; + eventType?: InputMaybe; + objectMetadataId?: InputMaybe; + recordId?: InputMaybe; + userWorkspaceId?: InputMaybe; +}; + +export type EventLogPageInfo = { + __typename?: 'EventLogPageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']; +}; + +export type EventLogQueryInput = { + after?: InputMaybe; + filters?: InputMaybe; + first?: InputMaybe; + table: EventLogTable; +}; + +export type EventLogQueryResult = { + __typename?: 'EventLogQueryResult'; + pageInfo: EventLogPageInfo; + records: Array; + totalCount: Scalars['Int']; +}; + +export type EventLogRecord = { + __typename?: 'EventLogRecord'; + event: Scalars['String']; + isCustom?: Maybe; + objectMetadataId?: Maybe; + properties?: Maybe; + recordId?: Maybe; + timestamp: Scalars['DateTime']; + userWorkspaceId?: Maybe; +}; + +export enum EventLogTable { + OBJECT_EVENT = 'OBJECT_EVENT', + PAGEVIEW = 'PAGEVIEW', + WORKSPACE_EVENT = 'WORKSPACE_EVENT' +} + export type EventSubscription = { __typename?: 'EventSubscription'; eventStreamId: Scalars['String']; @@ -3581,6 +3632,7 @@ export type Query = { commandMenuItems: Array; currentUser: User; currentWorkspace: Workspace; + eventLogs: EventLogQueryResult; field: Field; fields: FieldConnection; findManyAgents: Array; @@ -3709,6 +3761,11 @@ export type QueryCommandMenuItemArgs = { }; +export type QueryEventLogsArgs = { + input: EventLogQueryInput; +}; + + export type QueryFieldArgs = { id: Scalars['UUID']; }; @@ -4895,6 +4952,7 @@ export type UpdateWorkspaceInput = { defaultRoleId?: InputMaybe; displayName?: InputMaybe; editableProfileFields?: InputMaybe>; + eventLogRetentionDays?: InputMaybe; fastModel?: InputMaybe; inviteHash?: InputMaybe; isGoogleAuthBypassEnabled?: InputMaybe; @@ -5288,6 +5346,7 @@ export type Workspace = { deletedAt?: Maybe; displayName?: Maybe; editableProfileFields?: Maybe>; + eventLogRetentionDays: Scalars['Float']; fastModel: Scalars['String']; featureFlags?: Maybe>; hasValidEnterpriseKey: Scalars['Boolean']; @@ -6542,7 +6601,7 @@ export type BillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscri export type CurrentBillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, billingEntitlements: Array<{ __typename?: 'BillingEntitlement', key: BillingEntitlementKey, value: boolean }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, eventLogRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, billingEntitlements: Array<{ __typename?: 'BillingEntitlement', key: BillingEntitlementKey, value: boolean }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; @@ -6561,7 +6620,7 @@ export type DeleteUserWorkspaceMutation = { __typename?: 'Mutation', deleteUserF export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, billingEntitlements: Array<{ __typename?: 'BillingEntitlement', key: BillingEntitlementKey, value: boolean }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null, rowLevelPermissionPredicates?: Array<{ __typename?: 'RowLevelPermissionPredicate', id: string, fieldMetadataId: string, objectMetadataId: string, operand: RowLevelPermissionPredicateOperand, subFieldName?: string | null, workspaceMemberFieldMetadataId?: string | null, workspaceMemberSubFieldName?: string | null, rowLevelPermissionPredicateGroupId?: string | null, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, value?: any | null }> | null, rowLevelPermissionPredicateGroups?: Array<{ __typename?: 'RowLevelPermissionPredicateGroup', id: string, parentRowLevelPermissionPredicateGroupId?: string | null, logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator, positionInRowLevelPermissionPredicateGroup?: number | null, roleId: string, objectMetadataId: string }> | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, eventLogRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, billingEntitlements: Array<{ __typename?: 'BillingEntitlement', key: BillingEntitlementKey, value: boolean }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }; @@ -7629,6 +7688,7 @@ export const UserQueryFragmentFragmentDoc = gql` smartModel isTwoFactorAuthenticationEnforced trashRetentionDays + eventLogRetentionDays editableProfileFields } availableWorkspaces { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 6646b5d788..c7f4761edf 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -472,6 +472,7 @@ export type BillingEntitlement = { }; export enum BillingEntitlementKey { + AUDIT_LOGS = 'AUDIT_LOGS', CUSTOM_DOMAIN = 'CUSTOM_DOMAIN', RLS = 'RLS', SSO = 'SSO' @@ -1356,6 +1357,56 @@ export type EmailsConfiguration = { configurationType: WidgetConfigurationType; }; +export type EventLogDateRangeInput = { + end?: InputMaybe; + start?: InputMaybe; +}; + +export type EventLogFiltersInput = { + dateRange?: InputMaybe; + eventType?: InputMaybe; + objectMetadataId?: InputMaybe; + recordId?: InputMaybe; + userWorkspaceId?: InputMaybe; +}; + +export type EventLogPageInfo = { + __typename?: 'EventLogPageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']; +}; + +export type EventLogQueryInput = { + after?: InputMaybe; + filters?: InputMaybe; + first?: InputMaybe; + table: EventLogTable; +}; + +export type EventLogQueryResult = { + __typename?: 'EventLogQueryResult'; + pageInfo: EventLogPageInfo; + records: Array; + totalCount: Scalars['Int']; +}; + +export type EventLogRecord = { + __typename?: 'EventLogRecord'; + event: Scalars['String']; + isCustom?: Maybe; + objectMetadataId?: Maybe; + properties?: Maybe; + recordId?: Maybe; + timestamp: Scalars['DateTime']; + userWorkspaceId?: Maybe; +}; + +export enum EventLogTable { + OBJECT_EVENT = 'OBJECT_EVENT', + PAGEVIEW = 'PAGEVIEW', + WORKSPACE_EVENT = 'WORKSPACE_EVENT' +} + export type EventSubscription = { __typename?: 'EventSubscription'; eventStreamId: Scalars['String']; @@ -3482,6 +3533,7 @@ export type Query = { commandMenuItems: Array; currentUser: User; currentWorkspace: Workspace; + eventLogs: EventLogQueryResult; field: Field; fields: FieldConnection; findManyAgents: Array; @@ -3591,6 +3643,11 @@ export type QueryCommandMenuItemArgs = { }; +export type QueryEventLogsArgs = { + input: EventLogQueryInput; +}; + + export type QueryFindOneAgentArgs = { input: AgentIdInput; }; @@ -4711,6 +4768,7 @@ export type UpdateWorkspaceInput = { defaultRoleId?: InputMaybe; displayName?: InputMaybe; editableProfileFields?: InputMaybe>; + eventLogRetentionDays?: InputMaybe; fastModel?: InputMaybe; inviteHash?: InputMaybe; isGoogleAuthBypassEnabled?: InputMaybe; @@ -5104,6 +5162,7 @@ export type Workspace = { deletedAt?: Maybe; displayName?: Maybe; editableProfileFields?: Maybe>; + eventLogRetentionDays: Scalars['Float']; fastModel: Scalars['String']; featureFlags?: Maybe>; hasValidEnterpriseKey: Scalars['Boolean']; diff --git a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts index 33f49dc695..ab15fdb2da 100644 --- a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts +++ b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts @@ -65,6 +65,7 @@ const mockWorkspace = { }, isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, + eventLogRetentionDays: 365 * 3, fastModel: DEFAULT_FAST_MODEL, smartModel: DEFAULT_SMART_MODEL, routerModel: 'auto', diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 93dd547827..4fe5b69a32 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -300,6 +300,14 @@ const SettingsSecurityApprovedAccessDomain = lazy(() => ), ); +const SettingsEventLogs = lazy(() => + import('~/pages/settings/security/event-logs/SettingsEventLogs').then( + (module) => ({ + default: module.SettingsEventLogs, + }), + ), +); + const SettingsNewEmailingDomain = lazy(() => import('~/pages/settings/emailing-domains/SettingsNewEmailingDomain').then( (module) => ({ @@ -615,6 +623,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.NewApprovedAccessDomain} element={} /> + } /> {isAdminPageEnabled && ( diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts index b3d5dd0879..d13197b758 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts @@ -33,6 +33,7 @@ export type CurrentWorkspace = Pick< | 'metadataVersion' | 'isTwoFactorAuthenticationEnforced' | 'trashRetentionDays' + | 'eventLogRetentionDays' | 'fastModel' | 'smartModel' | 'editableProfileFields' diff --git a/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts b/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts index d19753dcc6..65f01bc83b 100644 --- a/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts @@ -11,6 +11,7 @@ import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabl import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState'; import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; +import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouseConfiguredState'; import { isCloudflareIntegrationEnabledState } from '@/client-config/states/isCloudflareIntegrationEnabledState'; import { isEmailingDomainsEnabledState } from '@/client-config/states/isEmailingDomainsEnabledState'; import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState'; @@ -120,6 +121,10 @@ export const useClientConfig = (): UseClientConfigResult => { isCloudflareIntegrationEnabledState, ); + const setIsClickHouseConfigured = useSetRecoilState( + isClickHouseConfiguredState, + ); + const setAppVersion = useSetRecoilState(appVersionState); const fetchClientConfig = useCallback(async () => { @@ -198,6 +203,7 @@ export const useClientConfig = (): UseClientConfigResult => { setIsCloudflareIntegrationEnabled( clientConfig?.isCloudflareIntegrationEnabled, ); + setIsClickHouseConfigured(clientConfig?.isClickHouseConfigured ?? false); } catch (err) { const error = err instanceof Error ? err : new Error('Failed to fetch client config'); @@ -231,6 +237,7 @@ export const useClientConfig = (): UseClientConfigResult => { setIsImapSmtpCaldavEnabled, setIsMultiWorkspaceEnabled, setIsEmailingDomainsEnabled, + setIsClickHouseConfigured, setIsCloudflareIntegrationEnabled, setLabPublicFeatureFlags, setMicrosoftCalendarEnabled, diff --git a/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts b/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts new file mode 100644 index 0000000000..84df93f4a3 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui/utilities'; + +export const isClickHouseConfiguredState = createState({ + key: 'isClickHouseConfigured', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/client-config/types/ClientConfig.ts b/packages/twenty-front/src/modules/client-config/types/ClientConfig.ts index 8c8c71de53..3e589dcae9 100644 --- a/packages/twenty-front/src/modules/client-config/types/ClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/types/ClientConfig.ts @@ -33,6 +33,7 @@ export type ClientConfig = { isImapSmtpCaldavEnabled: boolean; isEmailingDomainsEnabled: boolean; isCloudflareIntegrationEnabled: boolean; + isClickHouseConfigured: boolean; publicFeatureFlags: Array; sentry: Sentry; signInPrefilled: boolean; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts index 01eba54882..9ca1755059 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts @@ -61,6 +61,7 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ ], isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, + eventLogRetentionDays: 365 * 3, fastModel: DEFAULT_FAST_MODEL, smartModel: DEFAULT_SMART_MODEL, }); diff --git a/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts b/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts index daa3ffc9d9..273e279355 100644 --- a/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts +++ b/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts @@ -13,7 +13,11 @@ export const useShowFullscreen = () => { location, 'settings/' + SettingsPath.RestPlayground + '/*', ) || - isMatchingLocation(location, 'settings/' + SettingsPath.GraphQLPlayground) + isMatchingLocation( + location, + 'settings/' + SettingsPath.GraphQLPlayground, + ) || + isMatchingLocation(location, 'settings/' + SettingsPath.EventLogs) ) { return true; } diff --git a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts index 6360ab35e4..6c446f28f6 100644 --- a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts +++ b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts @@ -90,6 +90,7 @@ export const USER_QUERY_FRAGMENT = gql` smartModel isTwoFactorAuthenticationEnforced trashRetentionDays + eventLogRetentionDays editableProfileFields } availableWorkspaces { diff --git a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx index 2f4feb91c2..5abdaf99b9 100644 --- a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx +++ b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx @@ -1,10 +1,14 @@ +import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; +import { Link } from 'react-router-dom'; import { useDebouncedCallback } from 'use-debounce'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { authProvidersState } from '@/client-config/states/authProvidersState'; +import { isClickHouseConfiguredState } from '@/client-config/states/isClickHouseConfiguredState'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; +import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton'; import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsSSOIdentitiesProvidersListCard } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard'; @@ -20,7 +24,14 @@ import { useRecoilState, useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { Tag } from 'twenty-ui/components'; -import { H2Title, IconLock, IconTrash } from 'twenty-ui/display'; +import { + H2Title, + IconClockHour8, + IconHistory, + IconLock, + IconTrash, +} from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; import { Card, Section } from 'twenty-ui/layout'; import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; @@ -39,11 +50,19 @@ const StyledSection = styled(Section)` flex-shrink: 0; `; +const StyledLink = styled(Link, { + shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'isDisabled', +})<{ isDisabled: boolean }>` + pointer-events: ${({ isDisabled }) => (isDisabled ? 'none' : 'auto')}; + text-decoration: none; +`; + export const SettingsSecurity = () => { const { t } = useLingui(); const { enqueueErrorSnackBar } = useSnackBar(); const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isClickHouseConfigured = useRecoilValue(isClickHouseConfiguredState); const authProviders = useRecoilValue(authProvidersState); const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState); const [currentWorkspace, setCurrentWorkspace] = useRecoilState( @@ -51,12 +70,8 @@ export const SettingsSecurity = () => { ); const [updateWorkspace] = useUpdateWorkspaceMutation(); - const saveWorkspace = useDebouncedCallback(async (value: number) => { + const saveTrashRetention = useDebouncedCallback(async (value: number) => { try { - if (!currentWorkspace?.id) { - throw new Error('User is not logged in'); - } - await updateWorkspace({ variables: { input: { @@ -71,6 +86,22 @@ export const SettingsSecurity = () => { } }, 500); + const saveEventLogRetention = useDebouncedCallback(async (value: number) => { + try { + await updateWorkspace({ + variables: { + input: { + eventLogRetentionDays: value, + }, + }, + }); + } catch (err) { + enqueueErrorSnackBar({ + apolloError: err instanceof ApolloError ? err : undefined, + }); + } + }, 500); + const handleTrashRetentionDaysChange = (value: number) => { if (!currentWorkspace) { return; @@ -85,7 +116,24 @@ export const SettingsSecurity = () => { trashRetentionDays: value, }); - saveWorkspace(value); + saveTrashRetention(value); + }; + + const handleEventLogRetentionDaysChange = (value: number) => { + if (!currentWorkspace) { + return; + } + + if (value === currentWorkspace.eventLogRetentionDays) { + return; + } + + setCurrentWorkspace({ + ...currentWorkspace, + eventLogRetentionDays: value, + }); + + saveEventLogRetention(value); }; const hasSsoIdentityProviders = SSOIdentitiesProviders.length > 0; @@ -102,6 +150,9 @@ export const SettingsSecurity = () => { !hasDirectAuthEnabled && hasBypassProviderAvailable; + const hasEnterpriseAccess = currentWorkspace?.hasValidEnterpriseKey === true; + const isEventLogsEnabled = hasEnterpriseAccess && isClickHouseConfigured; + return ( { )} +
+ + } + /> + + +
theme.background.primary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.md}; + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +`; + +const StyledHeader = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(4)}; + padding: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledHeaderRow = styled.div` + align-items: center; + display: flex; + justify-content: space-between; +`; + +const StyledRecordCount = styled.span` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.sm}; +`; + +const StyledTableWrapper = styled.div` + flex: 1; + min-height: 0; + overflow: hidden; +`; + +const StyledErrorContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(4)}; + justify-content: center; + padding: ${({ theme }) => theme.spacing(8)}; + text-align: center; +`; + +const StyledErrorIcon = styled.div` + color: ${({ theme }) => theme.color.orange}; +`; + +const StyledErrorTitle = styled.h3` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.lg}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin: 0; +`; + +const StyledErrorMessage = styled.p` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin: 0; + max-width: 400px; +`; + +export type EventLogFiltersState = { + eventType?: string; + userWorkspaceId?: string; + dateRange?: { + start?: Date; + end?: Date; + }; + recordId?: string; + objectMetadataId?: string; +}; + +const RECORDS_PER_PAGE = 100; + +export const SettingsEventLogs = () => { + const { t } = useLingui(); + const navigateSettings = useNavigateSettings(); + const [selectedTable, setSelectedTable] = useState( + EventLogTable.PAGEVIEW, + ); + const [filters, setFilters] = useState({}); + + const { + records, + totalCount, + hasNextPage, + loading, + error, + refetch, + loadMore, + } = useEventLogs({ + table: selectedTable, + filters: { + eventType: filters.eventType, + userWorkspaceId: filters.userWorkspaceId, + dateRange: filters.dateRange + ? { + start: filters.dateRange.start?.toISOString(), + end: filters.dateRange.end?.toISOString(), + } + : undefined, + recordId: filters.recordId, + objectMetadataId: filters.objectMetadataId, + }, + first: RECORDS_PER_PAGE, + }); + + const handleTableChange = (table: EventLogTable) => { + setSelectedTable(table); + setFilters({}); + }; + + const handleFiltersChange = (newFilters: EventLogFiltersState) => { + setFilters(newFilters); + }; + + const handleExitFullScreen = () => { + navigateSettings(SettingsPath.Security); + }; + + const recordCount = records.length; + + const getErrorContent = () => { + if (!isDefined(error)) { + return null; + } + + const errorMessage = error.message || ''; + const isClickHouseError = errorMessage.includes('ClickHouse'); + const isEntitlementError = + errorMessage.includes('Enterprise') || + errorMessage.includes('entitlement'); + + if (isClickHouseError) { + return { + title: t`ClickHouse Not Configured`, + message: t`Audit logs require ClickHouse to be configured. Please contact your administrator to set up ClickHouse.`, + }; + } + + if (isEntitlementError) { + return { + title: t`Enterprise Feature`, + message: t`Audit logs are available with an Enterprise subscription. Please upgrade to access this feature.`, + }; + } + + return { + title: t`Error Loading Audit Logs`, + message: + errorMessage || t`An unexpected error occurred. Please try again.`, + }; + }; + + const errorContent = getErrorContent(); + + return ( + Workspace, + href: getSettingsPath(SettingsPath.Workspace), + }, + { + children: Security, + href: getSettingsPath(SettingsPath.Security), + }, + { children: Audit Logs }, + ]} + > + + {isDefined(errorContent) ? ( + + + + + {errorContent.title} + {errorContent.message} +