@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.
- package/dist/file/file.service.d.ts +1 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +1 -1
- package/dist/file/file.service.js.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
- package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
- package/dist/integration/services/outbox-polling.coordinator.js +14 -4
- package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
- package/dist/integration/services/outbox.processor.d.ts.map +1 -1
- package/dist/integration/services/outbox.processor.js +4 -3
- package/dist/integration/services/outbox.processor.js.map +1 -1
- package/dist/locale/locale.controller.d.ts +1 -1
- package/dist/locale/locale.controller.d.ts.map +1 -1
- package/dist/locale/locale.controller.js +4 -3
- package/dist/locale/locale.controller.js.map +1 -1
- package/dist/locale/locale.service.d.ts +1 -1
- package/dist/locale/locale.service.d.ts.map +1 -1
- package/dist/locale/locale.service.js +6 -6
- package/dist/locale/locale.service.js.map +1 -1
- package/dist/notification/events/notification-stale-progress-check.event.d.ts +6 -0
- package/dist/notification/events/notification-stale-progress-check.event.d.ts.map +1 -0
- package/dist/notification/events/notification-stale-progress-check.event.js +11 -0
- package/dist/notification/events/notification-stale-progress-check.event.js.map +1 -0
- package/dist/notification/notification.service.d.ts +4 -1
- package/dist/notification/notification.service.d.ts.map +1 -1
- package/dist/notification/notification.service.js +9 -26
- package/dist/notification/notification.service.js.map +1 -1
- package/dist/task/task.service.d.ts +19 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +46 -0
- package/dist/task/task.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +23 -5
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +32 -15
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +28 -8
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +120 -8
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +118 -118
- package/hedhog/data/integration_provider.yaml +25 -0
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/locale.yaml +5 -1
- package/hedhog/data/route.yaml +9 -0
- package/hedhog/data/setting_group.yaml +116 -106
- package/hedhog/data/setting_subgroup.yaml +32 -0
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +56 -0
- package/hedhog/frontend/app/roles/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/users/user-logs.tsx.ejs +190 -0
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +15 -1
- package/hedhog/frontend/messages/en.json +17 -0
- package/hedhog/frontend/messages/pt.json +17 -0
- package/hedhog/table/ai_instruction.yaml +1 -1
- package/hedhog/table/event_webhook.yaml +55 -55
- package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
- package/hedhog/table/event_webhook_event.yaml +26 -26
- package/hedhog/table/integration_event_catalog.yaml +41 -41
- package/hedhog/table/mcp_conversation.yaml +12 -12
- package/hedhog/table/mcp_conversation_message.yaml +23 -23
- package/hedhog/table/mcp_tool_call_log.yaml +22 -22
- package/hedhog/table/route.yaml +33 -33
- package/hedhog/table/user_change_log.yaml +40 -0
- package/hedhog/table/webhook_call_log.yaml +46 -46
- package/hedhog/table/webhook_integration.yaml +43 -43
- package/package.json +4 -4
- package/src/file/file.service.ts +2 -1
- package/src/integration/services/outbox-polling.coordinator.ts +16 -4
- package/src/integration/services/outbox.processor.ts +14 -8
- package/src/integration/types/event.types.ts +4 -4
- package/src/locale/locale.controller.ts +2 -1
- package/src/locale/locale.service.ts +7 -6
- package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
- package/src/mcp/guards/mcp-permission.guard.ts +51 -51
- package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
- package/src/mcp/mcp-server.service.ts +87 -87
- package/src/mcp/types/mcp-context.type.ts +6 -6
- package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
- package/src/mcp-chat/dto/send-message.dto.ts +7 -7
- package/src/notification/events/notification-stale-progress-check.event.ts +5 -0
- package/src/notification/notification.service.ts +12 -25
- package/src/route/dto/create.dto.ts +32 -32
- package/src/route/dto/update.dto.ts +31 -31
- package/src/task/task.service.ts +52 -0
- package/src/user/user.controller.ts +23 -9
- package/src/user/user.service.ts +183 -8
- package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
- 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={() =>
|
|
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={() =>
|
|
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",
|