@hed-hog/core 0.0.366 → 0.0.368

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/file/file.service.d.ts +1 -0
  2. package/dist/file/file.service.d.ts.map +1 -1
  3. package/dist/file/file.service.js +1 -1
  4. package/dist/file/file.service.js.map +1 -1
  5. package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
  6. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
  7. package/dist/integration/services/outbox-polling.coordinator.js +14 -4
  8. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
  9. package/dist/integration/services/outbox.processor.d.ts.map +1 -1
  10. package/dist/integration/services/outbox.processor.js +4 -3
  11. package/dist/integration/services/outbox.processor.js.map +1 -1
  12. package/dist/locale/locale.controller.d.ts +1 -1
  13. package/dist/locale/locale.controller.d.ts.map +1 -1
  14. package/dist/locale/locale.controller.js +4 -3
  15. package/dist/locale/locale.controller.js.map +1 -1
  16. package/dist/locale/locale.service.d.ts +1 -1
  17. package/dist/locale/locale.service.d.ts.map +1 -1
  18. package/dist/locale/locale.service.js +6 -6
  19. package/dist/locale/locale.service.js.map +1 -1
  20. package/dist/notification/events/notification-stale-progress-check.event.d.ts +6 -0
  21. package/dist/notification/events/notification-stale-progress-check.event.d.ts.map +1 -0
  22. package/dist/notification/events/notification-stale-progress-check.event.js +11 -0
  23. package/dist/notification/events/notification-stale-progress-check.event.js.map +1 -0
  24. package/dist/notification/notification.service.d.ts +4 -1
  25. package/dist/notification/notification.service.d.ts.map +1 -1
  26. package/dist/notification/notification.service.js +9 -26
  27. package/dist/notification/notification.service.js.map +1 -1
  28. package/dist/task/task.service.d.ts +19 -0
  29. package/dist/task/task.service.d.ts.map +1 -1
  30. package/dist/task/task.service.js +46 -0
  31. package/dist/task/task.service.js.map +1 -1
  32. package/dist/user/user.controller.d.ts +23 -5
  33. package/dist/user/user.controller.d.ts.map +1 -1
  34. package/dist/user/user.controller.js +32 -15
  35. package/dist/user/user.controller.js.map +1 -1
  36. package/dist/user/user.service.d.ts +28 -8
  37. package/dist/user/user.service.d.ts.map +1 -1
  38. package/dist/user/user.service.js +120 -8
  39. package/dist/user/user.service.js.map +1 -1
  40. package/hedhog/data/integration_event_catalog.yaml +118 -118
  41. package/hedhog/data/integration_provider.yaml +25 -0
  42. package/hedhog/data/integration_type.yaml +6 -0
  43. package/hedhog/data/locale.yaml +5 -1
  44. package/hedhog/data/route.yaml +9 -0
  45. package/hedhog/data/setting_group.yaml +116 -106
  46. package/hedhog/data/setting_subgroup.yaml +32 -0
  47. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +56 -0
  48. package/hedhog/frontend/app/roles/page.tsx.ejs +4 -1
  49. package/hedhog/frontend/app/users/user-logs.tsx.ejs +190 -0
  50. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +15 -1
  51. package/hedhog/frontend/messages/en.json +17 -0
  52. package/hedhog/frontend/messages/pt.json +17 -0
  53. package/hedhog/table/ai_instruction.yaml +1 -1
  54. package/hedhog/table/event_webhook.yaml +55 -55
  55. package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
  56. package/hedhog/table/event_webhook_event.yaml +26 -26
  57. package/hedhog/table/integration_event_catalog.yaml +41 -41
  58. package/hedhog/table/mcp_conversation.yaml +12 -12
  59. package/hedhog/table/mcp_conversation_message.yaml +23 -23
  60. package/hedhog/table/mcp_tool_call_log.yaml +22 -22
  61. package/hedhog/table/route.yaml +33 -33
  62. package/hedhog/table/user_change_log.yaml +40 -0
  63. package/hedhog/table/webhook_call_log.yaml +46 -46
  64. package/hedhog/table/webhook_integration.yaml +43 -43
  65. package/package.json +4 -4
  66. package/src/file/file.service.ts +2 -1
  67. package/src/integration/services/outbox-polling.coordinator.ts +16 -4
  68. package/src/integration/services/outbox.processor.ts +14 -8
  69. package/src/integration/types/event.types.ts +4 -4
  70. package/src/locale/locale.controller.ts +2 -1
  71. package/src/locale/locale.service.ts +7 -6
  72. package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
  73. package/src/mcp/guards/mcp-permission.guard.ts +51 -51
  74. package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
  75. package/src/mcp/mcp-server.service.ts +87 -87
  76. package/src/mcp/types/mcp-context.type.ts +6 -6
  77. package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
  78. package/src/mcp-chat/dto/send-message.dto.ts +7 -7
  79. package/src/notification/events/notification-stale-progress-check.event.ts +5 -0
  80. package/src/notification/notification.service.ts +12 -25
  81. package/src/route/dto/create.dto.ts +32 -32
  82. package/src/route/dto/update.dto.ts +31 -31
  83. package/src/task/task.service.ts +52 -0
  84. package/src/user/user.controller.ts +23 -9
  85. package/src/user/user.service.ts +183 -8
  86. package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
  87. package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
@@ -19,6 +19,9 @@
19
19
  pt: Por quanto tempo os navegadores armazenam em cache as imagens de avatar de usuários (em segundos).
20
20
  value: 3600
21
21
  user_override: false
22
+ subgroup_id:
23
+ where:
24
+ slug: general-system
22
25
  - slug: api-body-size-limit
23
26
  type: number
24
27
  component: input-number
@@ -30,6 +33,9 @@
30
33
  pt: Tamanho máximo em megabytes para corpos de requisição recebidos pela API. Aplica-se dinamicamente sem reiniciar (50 = 50 MB)
31
34
  value: 50
32
35
  user_override: false
36
+ subgroup_id:
37
+ where:
38
+ slug: general-system
33
39
  - slug: url
34
40
  type: string
35
41
  name:
@@ -40,6 +46,9 @@
40
46
  pt: A URL base acessível publicamente do sistema para requisições e redirecionamentos de clientes
41
47
  value: http://localhost:3200
42
48
  user_override: false
49
+ subgroup_id:
50
+ where:
51
+ slug: general-system
43
52
  - slug: api-url
44
53
  type: string
45
54
  name:
@@ -50,6 +59,9 @@
50
59
  pt: A URL do endpoint onde o servidor da API está acessível
51
60
  value: http://localhost:3100
52
61
  user_override: false
62
+ subgroup_id:
63
+ where:
64
+ slug: general-system
53
65
  - slug: installed
54
66
  type: boolean
55
67
  component: switch
@@ -61,6 +73,9 @@
61
73
  pt: Indica se o sistema completou os procedimentos de instalação e configuração inicial
62
74
  value: false
63
75
  user_override: false
76
+ subgroup_id:
77
+ where:
78
+ slug: general-system
64
79
  - slug: pagination-page-sizes
65
80
  type: array
66
81
  component: checkbox
@@ -72,6 +87,9 @@
72
87
  pt: Opções de tamanho de página exibidas no seletor de itens por página nas telas paginadas de todo o sistema
73
88
  value: '["6","12","24","48","96"]'
74
89
  user_override: false
90
+ subgroup_id:
91
+ where:
92
+ slug: general-system
75
93
  relations:
76
94
  setting_list:
77
95
  - value: '6'
@@ -84,16 +102,6 @@
84
102
  order: 3
85
103
  - value: '96'
86
104
  order: 4
87
- - slug: maintenance
88
- icon: database-cog
89
- name:
90
- en: Maintenance
91
- pt: Manutenção
92
- description:
93
- en: Database maintenance, cleanup operations, and retention policies for long-term system health
94
- pt: Manutenção do banco de dados, operações de limpeza e políticas de retenção para a saúde do sistema a longo prazo
95
- relations:
96
- setting:
97
105
  - slug: maintenance-mode-enabled
98
106
  type: boolean
99
107
  component: switch
@@ -164,6 +172,20 @@
164
172
  subgroup_id:
165
173
  where:
166
174
  slug: notification-retention
175
+ - slug: stale-async-notification-hours
176
+ type: number
177
+ component: input-number
178
+ name:
179
+ en: Stale Async Notification Cleanup (hours)
180
+ pt: Limpeza de Notificações Assíncronas Travadas (horas)
181
+ description:
182
+ en: Number of hours an in-progress async notification (progress below 100% and not finished) may run before it is automatically deleted
183
+ pt: Número de horas que uma notificação assíncrona em andamento (progresso abaixo de 100% e não finalizada) pode ficar antes de ser excluída automaticamente
184
+ value: 24
185
+ user_override: false
186
+ subgroup_id:
187
+ where:
188
+ slug: notification-retention
167
189
  - slug: access-log-retention-days
168
190
  type: number
169
191
  component: input-number
@@ -178,7 +200,6 @@
178
200
  subgroup_id:
179
201
  where:
180
202
  slug: access-log-retention
181
-
182
203
  - slug: queue-worker-offline-cleanup-hours
183
204
  type: number
184
205
  component: input-number
@@ -193,6 +214,90 @@
193
214
  subgroup_id:
194
215
  where:
195
216
  slug: worker-retention
217
+ - slug: configured-mail
218
+ type: boolean
219
+ name:
220
+ en: Email Configured
221
+ pt: Email Configurado
222
+ description:
223
+ en: Indicates whether email notifications are fully configured and operational
224
+ pt: Indica se as notificações de email estão totalmente configuradas e operacionais
225
+ value: false
226
+ user_override: false
227
+ component: switch
228
+ subgroup_id:
229
+ where:
230
+ slug: mail-configuration
231
+ - slug: mail-integration-profile-id
232
+ type: number
233
+ component: entity-picker
234
+ name:
235
+ en: Mail Profile
236
+ pt: Perfil de Email
237
+ description:
238
+ en: Select the mail profile to use for sending system notifications
239
+ pt: Selecione o perfil de email a usar para enviar notificações do sistema
240
+ value: '1'
241
+ user_override: false
242
+ subgroup_id:
243
+ where:
244
+ slug: mail-configuration
245
+ - slug: ai-openai-profile-id
246
+ type: number
247
+ component: entity-picker
248
+ name:
249
+ en: OpenAI Profile
250
+ pt: Perfil OpenAI
251
+ description:
252
+ en: Integration profile (type AI / OpenAI) to use for OpenAI features
253
+ pt: Perfil de integração (tipo IA / OpenAI) a usar para recursos de OpenAI
254
+ value: ''
255
+ user_override: false
256
+ subgroup_id:
257
+ where:
258
+ slug: ai-configuration
259
+ - slug: ai-gemini-profile-id
260
+ type: number
261
+ component: entity-picker
262
+ name:
263
+ en: Gemini Profile
264
+ pt: Perfil Gemini
265
+ description:
266
+ en: Integration profile (type AI / Gemini) to use for Gemini features
267
+ pt: Perfil de integração (tipo IA / Gemini) a usar para recursos de Gemini
268
+ value: ''
269
+ user_override: false
270
+ subgroup_id:
271
+ where:
272
+ slug: ai-configuration
273
+ - slug: mcp-enabled
274
+ type: boolean
275
+ component: switch
276
+ name:
277
+ en: Enable MCP Server
278
+ pt: Habilitar Servidor MCP
279
+ description:
280
+ en: Enable or disable the MCP server and AI chat feature
281
+ pt: Habilitar ou desabilitar o servidor MCP e o recurso de chat de IA
282
+ value: true
283
+ user_override: false
284
+ subgroup_id:
285
+ where:
286
+ slug: mcp-configuration
287
+ - slug: mcp-ai-profile-id
288
+ type: number
289
+ component: entity-picker
290
+ name:
291
+ en: AI Profile
292
+ pt: Perfil de IA
293
+ description:
294
+ en: Integration profile (type AI) to use for MCP chat
295
+ pt: Perfil de integração (tipo IA) a usar no chat MCP
296
+ value: ''
297
+ user_override: false
298
+ subgroup_id:
299
+ where:
300
+ slug: mcp-configuration
196
301
  - slug: authentication
197
302
  icon: lock-password
198
303
  name:
@@ -1662,69 +1767,6 @@
1662
1767
  order: 3
1663
1768
  - value: image/tiff
1664
1769
  order: 4
1665
- - slug: 'mail'
1666
- icon: mail
1667
- name:
1668
- en: Mail
1669
- pt: Email
1670
- description:
1671
- en: Configure email service provider, sender address, and credentials for system notifications
1672
- pt: Configure provedor de serviço de email, endereço do remetente e credenciais para notificações do sistema
1673
- relations:
1674
- setting:
1675
- - slug: configured-mail
1676
- type: boolean
1677
- name:
1678
- en: Email Configured
1679
- pt: Email Configurado
1680
- description:
1681
- en: Indicates whether email notifications are fully configured and operational
1682
- pt: Indica se as notificações de email estão totalmente configuradas e operacionais
1683
- value: false
1684
- user_override: false
1685
- component: switch
1686
- - slug: mail-integration-profile-id
1687
- type: number
1688
- component: entity-picker
1689
- name:
1690
- en: Mail Profile
1691
- pt: Perfil de Email
1692
- description:
1693
- en: Select the mail profile to use for sending system notifications
1694
- pt: Selecione o perfil de email a usar para enviar notificações do sistema
1695
- value: '1'
1696
- - slug: ai
1697
- icon: brain
1698
- name:
1699
- en: AI
1700
- pt: IA
1701
- description:
1702
- en: Configure AI service providers and API credentials for intelligent system features
1703
- pt: Configure provedores de serviço de IA e credenciais de API para recursos inteligentes do sistema
1704
- relations:
1705
- setting:
1706
- - slug: ai-openai-profile-id
1707
- type: number
1708
- component: entity-picker
1709
- name:
1710
- en: OpenAI Profile
1711
- pt: Perfil OpenAI
1712
- description:
1713
- en: Integration profile (type AI / OpenAI) to use for OpenAI features
1714
- pt: Perfil de integração (tipo IA / OpenAI) a usar para recursos de OpenAI
1715
- value: ''
1716
- user_override: false
1717
- - slug: ai-gemini-profile-id
1718
- type: number
1719
- component: entity-picker
1720
- name:
1721
- en: Gemini Profile
1722
- pt: Perfil Gemini
1723
- description:
1724
- en: Integration profile (type AI / Gemini) to use for Gemini features
1725
- pt: Perfil de integração (tipo IA / Gemini) a usar para recursos de Gemini
1726
- value: ''
1727
- user_override: false
1728
1770
  - slug: integration
1729
1771
  icon: webhook
1730
1772
  name:
@@ -1890,38 +1932,6 @@
1890
1932
  where:
1891
1933
  slug: retry-config
1892
1934
 
1893
- - slug: mcp
1894
- icon: terminal
1895
- name:
1896
- en: MCP
1897
- pt: MCP
1898
- description:
1899
- en: Model Context Protocol server and AI chat settings
1900
- pt: Configurações do servidor MCP e chat de IA
1901
- relations:
1902
- setting:
1903
- - slug: mcp-enabled
1904
- type: boolean
1905
- component: switch
1906
- name:
1907
- en: Enable MCP Server
1908
- pt: Habilitar Servidor MCP
1909
- description:
1910
- en: Enable or disable the MCP server and AI chat feature
1911
- pt: Habilitar ou desabilitar o servidor MCP e o recurso de chat de IA
1912
- value: true
1913
- user_override: false
1914
- - slug: mcp-ai-profile-id
1915
- type: number
1916
- component: entity-picker
1917
- name:
1918
- en: AI Profile
1919
- pt: Perfil de IA
1920
- description:
1921
- en: Integration profile (type AI) to use for MCP chat
1922
- pt: Perfil de integração (tipo IA) a usar no chat MCP
1923
- value: ''
1924
- user_override: false
1925
1935
  - slug: pdf
1926
1936
  icon: file-text
1927
1937
  name:
@@ -325,3 +325,35 @@
325
325
  description:
326
326
  en: Rules for retaining and automatically removing offline workers
327
327
  pt: Regras para retenção e remoção automática de workers offline
328
+
329
+ - slug: general-system
330
+ name:
331
+ en: System
332
+ pt: Sistema
333
+ description:
334
+ en: General system configuration settings
335
+ pt: Configurações gerais do sistema
336
+
337
+ - slug: mail-configuration
338
+ name:
339
+ en: Email
340
+ pt: Email
341
+ description:
342
+ en: Email service configuration
343
+ pt: Configuração do serviço de email
344
+
345
+ - slug: ai-configuration
346
+ name:
347
+ en: AI
348
+ pt: IA
349
+ description:
350
+ en: AI provider configuration
351
+ pt: Configuração de provedores de IA
352
+
353
+ - slug: mcp-configuration
354
+ name:
355
+ en: MCP
356
+ pt: MCP
357
+ description:
358
+ en: Model Context Protocol configuration
359
+ pt: Configuração do servidor MCP
@@ -597,6 +597,62 @@ const PROVIDER_FIELDS: Record<string, FieldDef[]> = {
597
597
  ],
598
598
  },
599
599
  ],
600
+ recaptcha: [
601
+ {
602
+ key: 'site_key',
603
+ labelKey: 'fieldSiteKey',
604
+ type: 'text',
605
+ required: true,
606
+ colSpan: true,
607
+ },
608
+ {
609
+ key: 'secret_key',
610
+ labelKey: 'fieldSecretKey',
611
+ type: 'password',
612
+ required: true,
613
+ colSpan: true,
614
+ },
615
+ {
616
+ key: 'score_threshold',
617
+ labelKey: 'fieldScoreThreshold',
618
+ type: 'number',
619
+ required: false,
620
+ placeholder: '0.5',
621
+ },
622
+ ],
623
+ 'cloudflare-turnstile': [
624
+ {
625
+ key: 'site_key',
626
+ labelKey: 'fieldSiteKey',
627
+ type: 'text',
628
+ required: true,
629
+ colSpan: true,
630
+ },
631
+ {
632
+ key: 'secret_key',
633
+ labelKey: 'fieldSecretKey',
634
+ type: 'password',
635
+ required: true,
636
+ colSpan: true,
637
+ },
638
+ ],
639
+ altcha: [
640
+ {
641
+ key: 'hmac_key',
642
+ labelKey: 'fieldHmacKey',
643
+ type: 'password',
644
+ required: true,
645
+ colSpan: true,
646
+ },
647
+ {
648
+ key: 'api_url',
649
+ labelKey: 'fieldApiUrl',
650
+ type: 'url',
651
+ required: false,
652
+ colSpan: true,
653
+ placeholder: 'https://eu.altcha.org/api/v1',
654
+ },
655
+ ],
600
656
  };
601
657
 
602
658
  // ─── Helpers ─────────────────────────────────────────────────────────────────
@@ -948,7 +948,10 @@ export default function RolePage() {
948
948
  className="w-full"
949
949
  type="button"
950
950
  variant="outline"
951
- onClick={() => setEditingRole(null)}
951
+ onClick={() => {
952
+ clearEditDraft();
953
+ setEditingRole(null);
954
+ }}
952
955
  >
953
956
  {t('cancel')}
954
957
  </Button>
@@ -0,0 +1,190 @@
1
+ 'use client';
2
+
3
+ import { EmptyState } from '@/components/entity-list';
4
+ import { formatDateTime } from '@/lib/format-date';
5
+ import { User } from '@hed-hog/api-types';
6
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
7
+ import {
8
+ Image as ImageIcon,
9
+ KeyRound,
10
+ Loader2,
11
+ Pencil,
12
+ ScrollText,
13
+ ShieldMinus,
14
+ ShieldPlus,
15
+ } from 'lucide-react';
16
+ import { useTranslations } from 'next-intl';
17
+
18
+ type UserChangeLog = {
19
+ id: string;
20
+ actorUserId: string | null;
21
+ actorName: string | null;
22
+ actorEmail: string | null;
23
+ action: string;
24
+ summary: string;
25
+ beforeData: string;
26
+ afterData: string;
27
+ ipAddress: string;
28
+ createdAt: string | null;
29
+ };
30
+
31
+ function getActionIcon(action: string) {
32
+ switch (action) {
33
+ case 'update':
34
+ return Pencil;
35
+ case 'reset_password':
36
+ return KeyRound;
37
+ case 'role_assigned':
38
+ return ShieldPlus;
39
+ case 'role_removed':
40
+ return ShieldMinus;
41
+ case 'avatar_changed':
42
+ return ImageIcon;
43
+ default:
44
+ return ScrollText;
45
+ }
46
+ }
47
+
48
+ function safeParse(raw: string): Record<string, unknown> | null {
49
+ if (!raw) return null;
50
+ try {
51
+ const parsed = JSON.parse(raw);
52
+ return parsed && typeof parsed === 'object' ? parsed : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function formatValue(value: unknown) {
59
+ if (value === null || value === undefined || value === '') return '—';
60
+ return String(value);
61
+ }
62
+
63
+ function DiffList({ before, after }: { before: string; after: string }) {
64
+ const beforeObj = safeParse(before);
65
+ const afterObj = safeParse(after);
66
+
67
+ if (!beforeObj && !afterObj) return null;
68
+
69
+ const keys = Array.from(
70
+ new Set([
71
+ ...Object.keys(beforeObj ?? {}),
72
+ ...Object.keys(afterObj ?? {}),
73
+ ]),
74
+ );
75
+
76
+ if (!keys.length) return null;
77
+
78
+ return (
79
+ <div className="mt-2 space-y-1 rounded-md bg-muted/40 p-2">
80
+ {keys.map((key) => (
81
+ <div key={key} className="flex flex-wrap items-center gap-1 text-[11px]">
82
+ <span className="font-medium text-muted-foreground">{key}:</span>
83
+ {beforeObj && key in beforeObj && (
84
+ <span className="rounded bg-rose-50 px-1 py-0.5 text-rose-700 line-through">
85
+ {formatValue(beforeObj[key])}
86
+ </span>
87
+ )}
88
+ {afterObj && key in afterObj && (
89
+ <span className="rounded bg-emerald-50 px-1 py-0.5 text-emerald-700">
90
+ {formatValue(afterObj[key])}
91
+ </span>
92
+ )}
93
+ </div>
94
+ ))}
95
+ </div>
96
+ );
97
+ }
98
+
99
+ export function UserLogs({ editingUser }: { editingUser: User }) {
100
+ const { request, currentLocaleCode, getSettingValue } = useApp();
101
+ const t = useTranslations('core.UserChangeLog');
102
+ const userId = editingUser?.id;
103
+
104
+ const { data: logs = [], isLoading } = useQuery<UserChangeLog[]>({
105
+ queryKey: ['user-change-logs', userId, currentLocaleCode],
106
+ queryFn: async () => {
107
+ const response = await request<{ data: UserChangeLog[] }>({
108
+ url: `/user/${userId}/log`,
109
+ method: 'GET',
110
+ });
111
+ return response.data?.data || [];
112
+ },
113
+ enabled: !!userId,
114
+ });
115
+
116
+ const knownActions = [
117
+ 'update',
118
+ 'reset_password',
119
+ 'role_assigned',
120
+ 'role_removed',
121
+ 'avatar_changed',
122
+ ];
123
+ const getActionLabel = (action: string) =>
124
+ knownActions.includes(action) ? t(action as never) : action;
125
+
126
+ if (isLoading) {
127
+ return (
128
+ <div className="flex items-center justify-center py-8 text-muted-foreground">
129
+ <Loader2 className="h-5 w-5 animate-spin" />
130
+ </div>
131
+ );
132
+ }
133
+
134
+ return (
135
+ <div className="space-y-3">
136
+ <h4 className="text-sm font-medium">{t('title')}</h4>
137
+
138
+ <div className="space-y-2">
139
+ {logs.length ? (
140
+ logs.map((log) => {
141
+ const Icon = getActionIcon(log.action);
142
+ const actor =
143
+ log.actorName || log.actorEmail || t('systemActor');
144
+
145
+ return (
146
+ <div key={log.id} className="rounded-lg border p-3">
147
+ <div className="flex items-start gap-3">
148
+ <div className="rounded-md bg-slate-50 p-2">
149
+ <Icon className="h-5 w-5 text-muted-foreground" />
150
+ </div>
151
+ <div className="flex-1">
152
+ <div className="flex flex-wrap items-center justify-between gap-2">
153
+ <p className="text-sm font-medium">
154
+ {getActionLabel(log.action)}
155
+ </p>
156
+ {log.createdAt && (
157
+ <span className="text-[11px] text-muted-foreground">
158
+ {formatDateTime(
159
+ log.createdAt,
160
+ getSettingValue,
161
+ currentLocaleCode,
162
+ )}
163
+ </span>
164
+ )}
165
+ </div>
166
+ <p className="text-xs text-muted-foreground">
167
+ {t('byActor')} {actor}
168
+ </p>
169
+ {log.summary && (
170
+ <p className="mt-1 text-xs text-muted-foreground">
171
+ {log.summary}
172
+ </p>
173
+ )}
174
+ <DiffList before={log.beforeData} after={log.afterData} />
175
+ </div>
176
+ </div>
177
+ </div>
178
+ );
179
+ })
180
+ ) : (
181
+ <EmptyState
182
+ icon={<ScrollText className="h-6 w-6" />}
183
+ title={t('emptyTitle')}
184
+ description={t('emptyDescription')}
185
+ />
186
+ )}
187
+ </div>
188
+ </div>
189
+ );
190
+ }
@@ -66,6 +66,7 @@ import {
66
66
  LucideIcon,
67
67
  Mail,
68
68
  Monitor,
69
+ ScrollText,
69
70
  RefreshCcw,
70
71
  Save,
71
72
  Shield,
@@ -84,6 +85,7 @@ import { z } from 'zod';
84
85
  import { ActiveSessions } from './active-session';
85
86
  import { UserIdentifiersSection } from './identifiers';
86
87
  import { PermissionsSection } from './permissions';
88
+ import { UserLogs } from './user-logs';
87
89
 
88
90
  export type UserSheetProps = {
89
91
  open: boolean;
@@ -765,6 +767,10 @@ function UserEditSheet({
765
767
  <Monitor className="h-4 w-4" />
766
768
  <span>{t('tabSessions')}</span>
767
769
  </TabsTrigger>
770
+ <TabsTrigger value="logs">
771
+ <ScrollText className="h-4 w-4" />
772
+ <span>{t('tabLogs')}</span>
773
+ </TabsTrigger>
768
774
  </TabsList>
769
775
  </div>
770
776
 
@@ -1110,7 +1116,10 @@ function UserEditSheet({
1110
1116
  <FormActions
1111
1117
  statusContent={editDraftStatusContent}
1112
1118
  cancelLabel={t('cancel')}
1113
- onCancel={() => onOpenChange(false)}
1119
+ onCancel={() => {
1120
+ clearEditDraft();
1121
+ onOpenChange(false);
1122
+ }}
1114
1123
  submitLabel={t('saveChanges')}
1115
1124
  submitType="submit"
1116
1125
  className="pt-2"
@@ -1478,6 +1487,11 @@ function UserEditSheet({
1478
1487
  refetch={handleRefreshUser}
1479
1488
  />
1480
1489
  </TabsContent>
1490
+
1491
+ {/* Logs Tab */}
1492
+ <TabsContent value="logs" className="space-y-4 mt-4 p-4 pt-0">
1493
+ <UserLogs editingUser={editingUser} />
1494
+ </TabsContent>
1481
1495
  </Tabs>
1482
1496
  </ResizableSheetContent>
1483
1497
  </Sheet>
@@ -244,6 +244,7 @@
244
244
  "tabIdentifiers": "Identifiers",
245
245
  "tabMfa": "MFA",
246
246
  "tabSessions": "Sessions",
247
+ "tabLogs": "Logs",
247
248
  "cardLastLoginDescription": "Last Login",
248
249
  "cardLastLoginTitle": "Never",
249
250
  "securityTitle": "Security Overview",
@@ -944,6 +945,18 @@
944
945
  "revokeAllSuccess": "Sessions revoked successfully!",
945
946
  "revokeAllFailure": "Failed to revoke sessions"
946
947
  },
948
+ "UserChangeLog": {
949
+ "title": "Change History",
950
+ "byActor": "by",
951
+ "systemActor": "System",
952
+ "update": "Profile updated",
953
+ "reset_password": "Password reset",
954
+ "role_assigned": "Role assigned",
955
+ "role_removed": "Role removed",
956
+ "avatar_changed": "Avatar changed",
957
+ "emptyTitle": "No changes recorded",
958
+ "emptyDescription": "Administrative changes made to this user will appear here."
959
+ },
947
960
  "UserActiveSessions": {
948
961
  "title": "Active Sessions",
949
962
  "revokeAll": "Revoke All",
@@ -2355,6 +2368,10 @@
2355
2368
  "fieldMode": "Mode",
2356
2369
  "fieldModeSandbox": "Sandbox",
2357
2370
  "fieldModeProduction": "Production",
2371
+ "fieldSiteKey": "Site Key",
2372
+ "fieldHmacKey": "HMAC Key",
2373
+ "fieldScoreThreshold": "Score Threshold",
2374
+ "fieldApiUrl": "API URL",
2358
2375
  "callbackUrlsSectionTitle": "Callback URLs",
2359
2376
  "callbackUrlsDescription": "Register these URLs in your OAuth application settings.",
2360
2377
  "addCallbackUrl": "Add URL",