feat(settings): add Logs as a dedicated tab in General settings (#21180)

## What & why

The audit-log viewer lived as a full-screen page reachable only via a
"View Logs" button buried in the **Security** tab. This surfaces it as
the **third tab in General settings** (`General | Security | Logs`),
consistent with the other tabs.

## Changes

- **Relocated** the event-logs module
`pages/settings/security/event-logs/` → `modules/settings/event-logs/`
and render it as tab content instead of a `FullScreenContainer` page.
Dropped `SettingsPath.EventLogs`, its route, and the fullscreen handling
in favor of the `general#logs` hash tab.
- **Security tab:** removed the "View Logs" entry; kept the
log-retention setting there.
- **In-tab gating** (shown to users with the Security permission):
Enterprise upgrade card when not entitled, a clear "ClickHouse not
configured" placeholder otherwise (derived from client config), and the
query is skipped when disabled. Replaces a bespoke error component that
string-matched error messages with the shared `SettingsEmptyPlaceholder`
/ `SettingsEnterpriseFeatureGateCard`.
- **Layout:** boxed content column with the table selector + filters
grouped in a `Card` and the results table below, matching settings
conventions. Kept the existing fixed filters (page/event name, member,
period) rather than recreating the record-view filter chips (those are
tightly coupled to record/view context).

Frontend + `twenty-shared` only — no changes to the log query or data.

## Test plan

- [x] `npx nx typecheck twenty-front` and `npx nx lint twenty-front`
pass
- [x] Settings → General shows three tabs; Logs is the third; breadcrumb
stays "Workspace / General"
- [x] With Enterprise + ClickHouse: table selector, filters, refresh,
and the paginated table work
- [x] Non-Enterprise: Enterprise upgrade card shown; no failing query
fires
- [ ] Enterprise without ClickHouse: shows the "ClickHouse not
configured" placeholder
- [ ] Security tab still shows the log-retention setting and the "View
Logs" button is gone
- [ ] A user without the Security permission sees neither the Security
nor Logs tab
This commit is contained in:
Félix Malfait
2026-06-04 08:47:23 +02:00
committed by GitHub
parent e6614299c6
commit 2ac515894b
21 changed files with 358 additions and 408 deletions
@@ -7,7 +7,6 @@ import { PermissionFlagType } from 'twenty-shared/constants';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { EnterpriseFeaturesEnabledGuard } from 'src/engine/core-modules/auth/guards/enterprise-features-enabled.guard';
import { EventLogsGraphqlApiExceptionFilter } from 'src/engine/core-modules/event-logs/filters/event-logs-graphql-api-exception.filter';
import { ForbiddenExceptionGraphqlFilter } from 'src/engine/core-modules/event-logs/filters/forbidden-exception-graphql.filter';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
@@ -37,7 +36,6 @@ export class EventLogsResolver {
@UseGuards(
WorkspaceAuthGuard,
EnterpriseFeaturesEnabledGuard,
SettingsPermissionGuard(PermissionFlagType.SECURITY),
)
@Query(() => EventLogQueryResult)
@@ -11,6 +11,7 @@ import { ClickHouseService } from 'src/database/clickHouse/clickHouse.service';
import { formatDateTimeForClickHouse } from 'src/database/clickHouse/clickHouse.util';
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { EnterprisePlanService } from 'src/engine/core-modules/enterprise/services/enterprise-plan.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import {
@@ -76,6 +77,7 @@ export class EventLogsService {
constructor(
private readonly clickHouseService: ClickHouseService,
private readonly billingService: BillingService,
private readonly enterprisePlanService: EnterprisePlanService,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
) {}
@@ -84,7 +86,7 @@ export class EventLogsService {
workspaceId: string,
input: EventLogQueryInput,
): Promise<EventLogQueryResult> {
await this.validateAccess(workspaceId);
await this.validateAccess(workspaceId, input.table);
if (!ALLOWED_TABLES.includes(input.table)) {
throw new BadRequestException(`Invalid table: ${input.table}`);
@@ -171,7 +173,10 @@ export class EventLogsService {
};
}
private async validateAccess(workspaceId: string): Promise<void> {
private async validateAccess(
workspaceId: string,
table: EventLogTable,
): Promise<void> {
if (!this.clickHouseService.getMainClient()) {
throw new EventLogsException(
'Audit logs require ClickHouse to be configured. Please set the CLICKHOUSE_URL environment variable.',
@@ -179,6 +184,17 @@ export class EventLogsService {
);
}
if (table === EventLogTable.APPLICATION_LOG) {
return;
}
if (!this.enterprisePlanService.isValid()) {
throw new EventLogsException(
'Audit logs require an Enterprise subscription.',
EventLogsExceptionCode.NO_ENTITLEMENT,
);
}
const hasEntitlement = await this.billingService.hasEntitlement(
workspaceId,
BillingEntitlementKey.AUDIT_LOGS,