@hed-hog/core 0.0.368 → 0.0.371

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 (271) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/dist/ai/ai.service.d.ts.map +1 -1
  4. package/dist/ai/ai.service.js +10 -7
  5. package/dist/ai/ai.service.js.map +1 -1
  6. package/dist/app-command/app-command.module.d.ts +3 -0
  7. package/dist/app-command/app-command.module.d.ts.map +1 -0
  8. package/dist/app-command/app-command.module.js +22 -0
  9. package/dist/app-command/app-command.module.js.map +1 -0
  10. package/dist/app-command/app-command.registry.d.ts +22 -0
  11. package/dist/app-command/app-command.registry.d.ts.map +1 -0
  12. package/dist/{webhook-integration/webhook-command.registry.js → app-command/app-command.registry.js} +6 -6
  13. package/dist/app-command/app-command.registry.js.map +1 -0
  14. package/dist/auth/auth.controller.d.ts +2 -2
  15. package/dist/auth/auth.controller.d.ts.map +1 -1
  16. package/dist/auth/auth.controller.js +20 -7
  17. package/dist/auth/auth.controller.js.map +1 -1
  18. package/dist/auth/auth.service.d.ts +3 -3
  19. package/dist/auth/auth.service.d.ts.map +1 -1
  20. package/dist/auth/auth.service.js +10 -36
  21. package/dist/auth/auth.service.js.map +1 -1
  22. package/dist/auth/dto/forget.dto.d.ts +1 -0
  23. package/dist/auth/dto/forget.dto.d.ts.map +1 -1
  24. package/dist/auth/dto/forget.dto.js +5 -0
  25. package/dist/auth/dto/forget.dto.js.map +1 -1
  26. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  27. package/dist/auth/guards/auth.guard.js +4 -1
  28. package/dist/auth/guards/auth.guard.js.map +1 -1
  29. package/dist/challenge/challenge.service.d.ts +8 -7
  30. package/dist/challenge/challenge.service.d.ts.map +1 -1
  31. package/dist/challenge/challenge.service.js +27 -19
  32. package/dist/challenge/challenge.service.js.map +1 -1
  33. package/dist/file/dto/upload.dto.d.ts +3 -0
  34. package/dist/file/dto/upload.dto.d.ts.map +1 -1
  35. package/dist/file/dto/upload.dto.js +22 -0
  36. package/dist/file/dto/upload.dto.js.map +1 -1
  37. package/dist/file/file.controller.d.ts +2 -1
  38. package/dist/file/file.controller.d.ts.map +1 -1
  39. package/dist/file/file.controller.js +29 -16
  40. package/dist/file/file.controller.js.map +1 -1
  41. package/dist/file/file.service.d.ts +51 -1
  42. package/dist/file/file.service.d.ts.map +1 -1
  43. package/dist/file/file.service.js +250 -18
  44. package/dist/file/file.service.js.map +1 -1
  45. package/dist/file/image-optimization.service.d.ts +17 -4
  46. package/dist/file/image-optimization.service.d.ts.map +1 -1
  47. package/dist/file/image-optimization.service.js +101 -46
  48. package/dist/file/image-optimization.service.js.map +1 -1
  49. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  50. package/dist/file/provider/s3.provider.js +15 -7
  51. package/dist/file/provider/s3.provider.js.map +1 -1
  52. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  53. package/dist/file/provider/spaces.provider.js +18 -8
  54. package/dist/file/provider/spaces.provider.js.map +1 -1
  55. package/dist/index.d.ts +4 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration/services/event-subscriber.registry.d.ts +5 -0
  60. package/dist/integration/services/event-subscriber.registry.d.ts.map +1 -1
  61. package/dist/integration/services/event-subscriber.registry.js +15 -0
  62. package/dist/integration/services/event-subscriber.registry.js.map +1 -1
  63. package/dist/integration/services/outbox.processor.d.ts.map +1 -1
  64. package/dist/integration/services/outbox.processor.js +2 -4
  65. package/dist/integration/services/outbox.processor.js.map +1 -1
  66. package/dist/integration/services/outbox.service.d.ts +4 -2
  67. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  68. package/dist/integration/services/outbox.service.js +26 -15
  69. package/dist/integration/services/outbox.service.js.map +1 -1
  70. package/dist/integration-profile/integration-credential-crypto.service.d.ts +43 -0
  71. package/dist/integration-profile/integration-credential-crypto.service.d.ts.map +1 -0
  72. package/dist/integration-profile/integration-credential-crypto.service.js +142 -0
  73. package/dist/integration-profile/integration-credential-crypto.service.js.map +1 -0
  74. package/dist/integration-profile/integration-profile.controller.d.ts +6 -7
  75. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  76. package/dist/integration-profile/integration-profile.controller.js +4 -3
  77. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  78. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  79. package/dist/integration-profile/integration-profile.module.js +5 -2
  80. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  81. package/dist/integration-profile/integration-profile.secrets.d.ts +16 -0
  82. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -0
  83. package/dist/integration-profile/integration-profile.secrets.js +56 -0
  84. package/dist/integration-profile/integration-profile.secrets.js.map +1 -0
  85. package/dist/integration-profile/integration-profile.service.d.ts +125 -8
  86. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  87. package/dist/integration-profile/integration-profile.service.js +257 -68
  88. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  89. package/dist/integration-profile/integration-profile.utils.d.ts +17 -0
  90. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  91. package/dist/integration-profile/integration-profile.utils.js +31 -0
  92. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  93. package/dist/language/en.json +178 -0
  94. package/dist/language/pt.json +178 -0
  95. package/dist/mail/mail.controller.d.ts.map +1 -1
  96. package/dist/mail/mail.controller.js +5 -28
  97. package/dist/mail/mail.controller.js.map +1 -1
  98. package/dist/mail/mail.module.js +18 -18
  99. package/dist/mail/mail.module.js.map +1 -1
  100. package/dist/mail/mail.service.d.ts +13 -2
  101. package/dist/mail/mail.service.d.ts.map +1 -1
  102. package/dist/mail/mail.service.js +25 -15
  103. package/dist/mail/mail.service.js.map +1 -1
  104. package/dist/mcp-chat/mcp-chat.service.js +6 -5
  105. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  106. package/dist/oauth/providers/facebook.provider.js +5 -5
  107. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  108. package/dist/oauth/providers/github.provider.js +5 -5
  109. package/dist/oauth/providers/github.provider.js.map +1 -1
  110. package/dist/oauth/providers/google.provider.js +5 -5
  111. package/dist/oauth/providers/google.provider.js.map +1 -1
  112. package/dist/oauth/providers/microsoft-entra-id.provider.js +5 -5
  113. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  114. package/dist/oauth/providers/microsoft.provider.js +5 -5
  115. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  116. package/dist/pdf/pdf.service.d.ts +4 -0
  117. package/dist/pdf/pdf.service.d.ts.map +1 -1
  118. package/dist/pdf/pdf.service.js +16 -9
  119. package/dist/pdf/pdf.service.js.map +1 -1
  120. package/dist/profile/profile.controller.d.ts +7 -7
  121. package/dist/profile/profile.controller.d.ts.map +1 -1
  122. package/dist/profile/profile.controller.js +28 -21
  123. package/dist/profile/profile.controller.js.map +1 -1
  124. package/dist/profile/profile.service.d.ts +7 -7
  125. package/dist/profile/profile.service.d.ts.map +1 -1
  126. package/dist/profile/profile.service.js +21 -25
  127. package/dist/profile/profile.service.js.map +1 -1
  128. package/dist/setting/setting.service.d.ts +1 -0
  129. package/dist/setting/setting.service.d.ts.map +1 -1
  130. package/dist/setting/setting.service.js +15 -2
  131. package/dist/setting/setting.service.js.map +1 -1
  132. package/dist/token/token.service.d.ts.map +1 -1
  133. package/dist/token/token.service.js +2 -1
  134. package/dist/token/token.service.js.map +1 -1
  135. package/dist/user/user.controller.d.ts +9 -0
  136. package/dist/user/user.controller.d.ts.map +1 -1
  137. package/dist/user/user.controller.js +13 -0
  138. package/dist/user/user.controller.js.map +1 -1
  139. package/dist/user/user.service.d.ts +7 -0
  140. package/dist/user/user.service.d.ts.map +1 -1
  141. package/dist/user/user.service.js +19 -0
  142. package/dist/user/user.service.js.map +1 -1
  143. package/dist/utils/webauthn-origin.d.ts +11 -0
  144. package/dist/utils/webauthn-origin.d.ts.map +1 -0
  145. package/dist/utils/webauthn-origin.js +37 -0
  146. package/dist/utils/webauthn-origin.js.map +1 -0
  147. package/dist/webhook-integration/webhook-integration.controller.d.ts +13 -0
  148. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  149. package/dist/webhook-integration/webhook-integration.controller.js +29 -0
  150. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  151. package/dist/webhook-integration/webhook-integration.module.js +3 -3
  152. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  153. package/dist/webhook-integration/webhook-integration.service.d.ts +18 -2
  154. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  155. package/dist/webhook-integration/webhook-integration.service.js +110 -6
  156. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  157. package/hedhog/data/integration_event_catalog.yaml +118 -118
  158. package/hedhog/data/integration_provider.yaml +9 -0
  159. package/hedhog/data/integration_type.yaml +6 -0
  160. package/hedhog/data/menu.yaml +6 -0
  161. package/hedhog/data/role.yaml +7 -0
  162. package/hedhog/data/route.yaml +192 -207
  163. package/hedhog/data/setting_group.yaml +119 -13
  164. package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
  165. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
  166. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
  167. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
  168. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
  169. package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
  170. package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
  171. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
  172. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
  173. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
  174. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
  175. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
  176. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
  177. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
  178. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +141 -29
  179. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
  180. package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
  181. package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
  182. package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
  183. package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
  184. package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
  185. package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
  186. package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
  187. package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
  188. package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
  189. package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
  190. package/hedhog/frontend/messages/en.json +69 -4
  191. package/hedhog/frontend/messages/pt.json +69 -4
  192. package/hedhog/table/event_webhook.yaml +55 -55
  193. package/hedhog/table/event_webhook_delivery_log.yaml +66 -66
  194. package/hedhog/table/event_webhook_event.yaml +26 -26
  195. package/hedhog/table/integration_event_catalog.yaml +41 -41
  196. package/hedhog/table/mcp_conversation.yaml +12 -12
  197. package/hedhog/table/mcp_conversation_message.yaml +23 -23
  198. package/hedhog/table/mcp_tool_call_log.yaml +22 -22
  199. package/hedhog/table/route.yaml +33 -33
  200. package/hedhog/table/setting.yaml +2 -0
  201. package/hedhog/table/webhook_call_log.yaml +3 -0
  202. package/hedhog/table/webhook_integration.yaml +43 -43
  203. package/package.json +9 -4
  204. package/src/ai/ai.service.ts +9 -6
  205. package/src/app-command/app-command.module.ts +9 -0
  206. package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
  207. package/src/auth/auth.controller.ts +20 -4
  208. package/src/auth/auth.service.ts +10 -36
  209. package/src/auth/dto/forget.dto.ts +5 -1
  210. package/src/auth/guards/auth.guard.ts +4 -1
  211. package/src/challenge/challenge.service.ts +30 -19
  212. package/src/file/dto/upload.dto.ts +25 -1
  213. package/src/file/file.controller.ts +31 -18
  214. package/src/file/file.service.ts +341 -16
  215. package/src/file/image-optimization.service.ts +111 -46
  216. package/src/file/provider/s3.provider.ts +16 -7
  217. package/src/file/provider/spaces.provider.ts +15 -5
  218. package/src/index.ts +4 -1
  219. package/src/integration/services/event-subscriber.registry.ts +15 -0
  220. package/src/integration/services/outbox.processor.ts +2 -5
  221. package/src/integration/services/outbox.service.ts +35 -17
  222. package/src/integration/types/event.types.ts +4 -4
  223. package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
  224. package/src/integration-profile/integration-profile.controller.ts +2 -0
  225. package/src/integration-profile/integration-profile.module.ts +5 -2
  226. package/src/integration-profile/integration-profile.secrets.ts +51 -0
  227. package/src/integration-profile/integration-profile.service.ts +335 -54
  228. package/src/integration-profile/integration-profile.utils.ts +56 -0
  229. package/src/language/en.json +5 -1
  230. package/src/language/pt.json +5 -1
  231. package/src/mail/mail.controller.ts +5 -29
  232. package/src/mail/mail.module.ts +5 -5
  233. package/src/mail/mail.service.ts +37 -13
  234. package/src/mcp/decorators/mcp-tool.decorator.ts +7 -7
  235. package/src/mcp/guards/mcp-permission.guard.ts +51 -51
  236. package/src/mcp/interfaces/mcp-tool-definition.interface.ts +6 -6
  237. package/src/mcp/mcp-server.service.ts +87 -87
  238. package/src/mcp/types/mcp-context.type.ts +6 -6
  239. package/src/mcp-chat/dto/create-conversation.dto.ts +7 -7
  240. package/src/mcp-chat/dto/send-message.dto.ts +7 -7
  241. package/src/mcp-chat/mcp-chat.service.ts +5 -5
  242. package/src/notification/events/notification-stale-progress-check.event.ts +5 -5
  243. package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
  244. package/src/oauth/providers/facebook.provider.ts +3 -3
  245. package/src/oauth/providers/github.provider.ts +3 -3
  246. package/src/oauth/providers/google.provider.ts +3 -3
  247. package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
  248. package/src/oauth/providers/microsoft.provider.ts +3 -3
  249. package/src/pdf/pdf.service.ts +19 -6
  250. package/src/profile/profile.controller.ts +21 -14
  251. package/src/profile/profile.service.ts +23 -20
  252. package/src/route/dto/create.dto.ts +32 -32
  253. package/src/route/dto/update.dto.ts +31 -31
  254. package/src/security/security.service.spec.ts +222 -0
  255. package/src/setting/setting.service.ts +15 -2
  256. package/src/token/token.service.ts +2 -1
  257. package/src/user/user.controller.ts +26 -16
  258. package/src/user/user.service.ts +25 -0
  259. package/src/utils/webauthn-origin.ts +38 -0
  260. package/src/webhook-integration/integration-event-catalog.controller.ts +50 -50
  261. package/src/webhook-integration/integration-event-catalog.service.ts +117 -117
  262. package/src/webhook-integration/webhook-integration.controller.ts +23 -0
  263. package/src/webhook-integration/webhook-integration.module.ts +3 -3
  264. package/src/webhook-integration/webhook-integration.service.ts +115 -5
  265. package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
  266. package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
  267. package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
  268. package/src/menu/menu.service.spec.ts +0 -247
  269. package/src/route/route.service.spec.ts +0 -300
  270. package/src/screen/screen.service.spec.ts +0 -298
  271. package/src/user/user.service.spec.ts +0 -294
@@ -505,12 +505,43 @@ function getPublicWebhookUrl(uuid: string, publicUrl?: string | null) {
505
505
  return `${baseUrl.replace(/\/+$/, '')}/webhook/${uuid}`;
506
506
  }
507
507
 
508
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
509
+
508
510
  export default function IntegrationsWebhooksPage() {
509
511
  const t = useTranslations('core.IntegrationsWebhooksPage');
510
512
  const { request, currentLocaleCode, getSettingValue, locales, accessToken } =
511
513
  useApp();
512
514
  const queryClient = useQueryClient();
513
- const pageSizeOptions = [6, 12, 24, 48] as const;
515
+
516
+ const { data: generalSettings } = useQuery<{
517
+ data: Array<{ slug: string; value: string }>;
518
+ }>({
519
+ queryKey: ['setting-group-general'],
520
+ queryFn: async () => {
521
+ const response = await request<{
522
+ data: Array<{ slug: string; value: string }>;
523
+ }>({ url: '/setting/group/general', method: 'GET' });
524
+ return response.data;
525
+ },
526
+ staleTime: 5 * 60 * 1000,
527
+ });
528
+
529
+ const pageSizeOptions = useMemo(() => {
530
+ const setting = generalSettings?.data?.find(
531
+ (s) => s.slug === 'pagination-page-sizes'
532
+ );
533
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
534
+ try {
535
+ const parsed = JSON.parse(setting.value) as string[];
536
+ const sizes = parsed
537
+ .map(Number)
538
+ .filter((n) => !isNaN(n) && n > 0)
539
+ .sort((a, b) => a - b);
540
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
541
+ } catch {
542
+ return DEFAULT_PAGE_SIZES;
543
+ }
544
+ }, [generalSettings]);
514
545
 
515
546
  const catalog = usePagination({
516
547
  url: '/integration-event-catalog',
@@ -566,6 +597,8 @@ export default function IntegrationsWebhooksPage() {
566
597
  const [incomingLogsPageSize, setIncomingLogsPageSize] = useState(12);
567
598
  const [logOrder, setLogOrder] = useState<'desc' | 'asc'>('desc');
568
599
  const [selectedLog, setSelectedLog] = useState<IncomingCallLog | null>(null);
600
+ const [retryTarget, setRetryTarget] = useState<IncomingCallLog | null>(null);
601
+ const [isRetryingLog, setIsRetryingLog] = useState(false);
569
602
  const sseAbortRef = useRef<AbortController | null>(null);
570
603
 
571
604
  const [eventWebhookOpen, setEventWebhookOpen] = useState(false);
@@ -1467,6 +1500,32 @@ export default function IntegrationsWebhooksPage() {
1467
1500
  await incoming.refetch();
1468
1501
  };
1469
1502
 
1503
+ const retryIncomingLog = async (log: IncomingCallLog) => {
1504
+ if (!incomingEdit) return;
1505
+ setIsRetryingLog(true);
1506
+ try {
1507
+ const response = await request<{ success?: boolean }>({
1508
+ url: `/webhook-integration/${incomingEdit.id}/log/${log.id}/retry`,
1509
+ method: 'POST',
1510
+ });
1511
+ // A new call log is always created by the retry, so refresh the list.
1512
+ await queryClient.invalidateQueries({
1513
+ queryKey: ['incoming-webhook-logs', incomingEdit.id],
1514
+ });
1515
+ if (response.data?.success) {
1516
+ toast.success(t('logRetrySuccess'));
1517
+ } else {
1518
+ toast.error(t('logRetryFailedAgain'));
1519
+ }
1520
+ setSelectedLog(null);
1521
+ setRetryTarget(null);
1522
+ } catch {
1523
+ toast.error(t('logRetryError'));
1524
+ } finally {
1525
+ setIsRetryingLog(false);
1526
+ }
1527
+ };
1528
+
1470
1529
  const regenerateToken = async () => {
1471
1530
  if (!incomingEdit) return;
1472
1531
  const response = await request<IncomingIntegration>({
@@ -2011,7 +2070,7 @@ export default function IntegrationsWebhooksPage() {
2011
2070
  </SheetTitle>
2012
2071
  <SheetDescription>{t('eventFormDescription')}</SheetDescription>
2013
2072
  </SheetHeader>
2014
- <div className="space-y-4 px-3 pb-8 sm:px-4">
2073
+ <div className="min-h-0 flex-1 space-y-4 overflow-y-auto px-3 pb-8 sm:px-4">
2015
2074
  <Field label={t('eventName')}>
2016
2075
  <Input
2017
2076
  value={catalogForm.name}
@@ -2092,7 +2151,7 @@ export default function IntegrationsWebhooksPage() {
2092
2151
  <Tabs
2093
2152
  value={incomingSheetTab}
2094
2153
  onValueChange={setIncomingSheetTab}
2095
- className="mt-4"
2154
+ className="mt-4 flex min-h-0 flex-1 flex-col"
2096
2155
  >
2097
2156
  <div className="px-3 sm:px-4">
2098
2157
  <TabsList className="grid w-full grid-cols-3">
@@ -2110,7 +2169,10 @@ export default function IntegrationsWebhooksPage() {
2110
2169
  </TabsTrigger>
2111
2170
  </TabsList>
2112
2171
  </div>
2113
- <TabsContent value="settings" className="mt-0">
2172
+ <TabsContent
2173
+ value="settings"
2174
+ className="mt-0 min-h-0 flex-1 overflow-y-auto"
2175
+ >
2114
2176
  <div className="space-y-6 px-3 pb-8 sm:px-4">
2115
2177
  <div className="grid gap-4 md:grid-cols-2">
2116
2178
  <Field label={t('name')}>
@@ -2352,7 +2414,7 @@ export default function IntegrationsWebhooksPage() {
2352
2414
 
2353
2415
  <TabsContent
2354
2416
  value="actions"
2355
- className="mt-0 space-y-4 px-3 pb-8 sm:px-4"
2417
+ className="mt-0 min-h-0 flex-1 space-y-4 overflow-y-auto px-3 pb-8 sm:px-4"
2356
2418
  >
2357
2419
  {!incomingEdit ? (
2358
2420
  <EmptyState
@@ -2433,7 +2495,7 @@ export default function IntegrationsWebhooksPage() {
2433
2495
 
2434
2496
  <TabsContent
2435
2497
  value="logs"
2436
- className="mt-0 space-y-4 px-3 pb-8 sm:px-4"
2498
+ className="mt-0 min-h-0 flex-1 space-y-4 overflow-y-auto px-3 pb-8 sm:px-4"
2437
2499
  >
2438
2500
  {!incomingEdit ? (
2439
2501
  <EmptyState
@@ -2583,7 +2645,7 @@ export default function IntegrationsWebhooksPage() {
2583
2645
  </SheetTitle>
2584
2646
  <SheetDescription>{t('actionSection')}</SheetDescription>
2585
2647
  </SheetHeader>
2586
- <div className="space-y-5 px-3 pb-8 sm:px-4">
2648
+ <div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-3 pb-8 sm:px-4">
2587
2649
  <div className="grid gap-3 md:grid-cols-3">
2588
2650
  <Field label={t('actionType')}>
2589
2651
  <Select
@@ -3358,7 +3420,7 @@ export default function IntegrationsWebhooksPage() {
3358
3420
  {t('eventWebhookFormDescription')}
3359
3421
  </SheetDescription>
3360
3422
  </SheetHeader>
3361
- <div className="space-y-5 px-3 pb-8 sm:px-4">
3423
+ <div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-3 pb-8 sm:px-4">
3362
3424
  <div className="grid gap-4 md:grid-cols-2">
3363
3425
  <Field label={t('name')}>
3364
3426
  <Input
@@ -3554,7 +3616,7 @@ export default function IntegrationsWebhooksPage() {
3554
3616
  </SheetDescription>
3555
3617
  </SheetHeader>
3556
3618
 
3557
- <div className="space-y-6 px-3 pb-8 sm:px-4">
3619
+ <div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-3 pb-8 sm:px-4">
3558
3620
  <div className="grid gap-6 md:grid-cols-2">
3559
3621
  <Field label={t('name')}>
3560
3622
  <Input
@@ -3841,7 +3903,7 @@ export default function IntegrationsWebhooksPage() {
3841
3903
  </SheetDescription>
3842
3904
  </SheetHeader>
3843
3905
 
3844
- <div className="space-y-6 px-3 pb-8 sm:px-4">
3906
+ <div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-3 pb-8 sm:px-4">
3845
3907
  <div className="grid gap-6 md:grid-cols-2">
3846
3908
  <Field label={t('technicalName')}>
3847
3909
  <Input
@@ -3942,7 +4004,7 @@ export default function IntegrationsWebhooksPage() {
3942
4004
  </SheetHeader>
3943
4005
 
3944
4006
  {selectedLog ? (
3945
- <div className="space-y-6 px-3 pb-8 sm:px-4">
4007
+ <div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-3 pb-8 sm:px-4">
3946
4008
  <div className="flex flex-wrap gap-3 pt-2">
3947
4009
  <Badge
3948
4010
  variant="outline"
@@ -3964,6 +4026,21 @@ export default function IntegrationsWebhooksPage() {
3964
4026
  </span>
3965
4027
  </div>
3966
4028
 
4029
+ {selectedLog.status === 'failed' ? (
4030
+ <div>
4031
+ <Button
4032
+ type="button"
4033
+ variant="outline"
4034
+ size="sm"
4035
+ onClick={() => setRetryTarget(selectedLog)}
4036
+ disabled={isRetryingLog}
4037
+ >
4038
+ <RotateCcw className="mr-2 h-4 w-4" />
4039
+ {t('logRetry')}
4040
+ </Button>
4041
+ </div>
4042
+ ) : null}
4043
+
3967
4044
  {selectedLog.error_message ? (
3968
4045
  <div>
3969
4046
  <p className="mb-1 text-xs font-medium text-destructive">
@@ -4127,6 +4204,36 @@ export default function IntegrationsWebhooksPage() {
4127
4204
  </AlertDialogFooter>
4128
4205
  </AlertDialogContent>
4129
4206
  </AlertDialog>
4207
+
4208
+ <AlertDialog
4209
+ open={Boolean(retryTarget)}
4210
+ onOpenChange={(open) => {
4211
+ if (!open && !isRetryingLog) setRetryTarget(null);
4212
+ }}
4213
+ >
4214
+ <AlertDialogContent>
4215
+ <AlertDialogHeader>
4216
+ <AlertDialogTitle>{t('logRetryConfirmTitle')}</AlertDialogTitle>
4217
+ <AlertDialogDescription>
4218
+ {t('logRetryConfirmDescription')}
4219
+ </AlertDialogDescription>
4220
+ </AlertDialogHeader>
4221
+ <AlertDialogFooter>
4222
+ <AlertDialogCancel disabled={isRetryingLog}>
4223
+ {t('cancel')}
4224
+ </AlertDialogCancel>
4225
+ <AlertDialogAction
4226
+ onClick={(event) => {
4227
+ event.preventDefault();
4228
+ if (retryTarget) retryIncomingLog(retryTarget);
4229
+ }}
4230
+ disabled={isRetryingLog}
4231
+ >
4232
+ {t('logRetry')}
4233
+ </AlertDialogAction>
4234
+ </AlertDialogFooter>
4235
+ </AlertDialogContent>
4236
+ </AlertDialog>
4130
4237
  </Page>
4131
4238
  );
4132
4239
  }
@@ -97,6 +97,8 @@ type DeliveryStatusFilter = 'all' | 'received' | 'read' | 'error';
97
97
  type HasErrorFilter = 'all' | 'true' | 'false';
98
98
  type ViewMode = 'cards' | 'table';
99
99
 
100
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
101
+
100
102
  export default function MailLogPage() {
101
103
  const t = useTranslations('core.MailLog');
102
104
  const [selectedLog, setSelectedLog] = useState<MailSent | null>(null);
@@ -110,13 +112,44 @@ export default function MailLogPage() {
110
112
  const [viewMode, setViewMode] = useState<ViewMode>('cards');
111
113
  const debouncedSearch = useDebounce(searchTerm);
112
114
  const debouncedRecipientEmail = useDebounce(recipientEmail);
115
+ const { request, getSettingValue } = useApp();
116
+
117
+ const { data: generalSettings } = useQuery<{
118
+ data: Array<{ slug: string; value: string }>;
119
+ }>({
120
+ queryKey: ['setting-group-general'],
121
+ queryFn: async () => {
122
+ const response = await request<{
123
+ data: Array<{ slug: string; value: string }>;
124
+ }>({ url: '/setting/group/general', method: 'GET' });
125
+ return response.data;
126
+ },
127
+ staleTime: 5 * 60 * 1000,
128
+ });
129
+
130
+ const pageSizeOptions = useMemo(() => {
131
+ const setting = generalSettings?.data?.find(
132
+ (s) => s.slug === 'pagination-page-sizes'
133
+ );
134
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
135
+ try {
136
+ const parsed = JSON.parse(setting.value) as string[];
137
+ const sizes = parsed
138
+ .map(Number)
139
+ .filter((n) => !isNaN(n) && n > 0)
140
+ .sort((a, b) => a - b);
141
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
142
+ } catch {
143
+ return DEFAULT_PAGE_SIZES;
144
+ }
145
+ }, [generalSettings]);
146
+
113
147
  const [page, setPage] = useState(1);
114
148
  const [pageSize, setPageSize] = usePersistedPageSize({
115
149
  storageKey: 'pagination:global:pageSize',
116
- defaultValue: 10,
117
- allowedValues: [10, 20, 30, 40, 50],
150
+ defaultValue: 12,
151
+ allowedValues: pageSizeOptions,
118
152
  });
119
- const { request, getSettingValue } = useApp();
120
153
 
121
154
  const {
122
155
  data: logsResult,
@@ -559,7 +592,7 @@ export default function MailLogPage() {
559
592
  setPageSize(newSize);
560
593
  setPage(1);
561
594
  }}
562
- pageSizeOptions={[10, 20, 30, 40, 50]}
595
+ pageSizeOptions={pageSizeOptions}
563
596
  />
564
597
 
565
598
  {selectedLog && (
@@ -1283,6 +1283,8 @@ const NO_PARENT = '__none__';
1283
1283
  const MENU_CREATE_DRAFT_STORAGE_KEY = 'core-menu-create-draft';
1284
1284
  const MENU_EDIT_DRAFT_STORAGE_KEY = 'core-menu-edit-draft';
1285
1285
 
1286
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
1287
+
1286
1288
  export default function MenuPage() {
1287
1289
  const t = useTranslations('core.MenuPage');
1288
1290
  const { request, currentLocaleCode, locales, getSettingValue } = useApp();
@@ -1298,10 +1300,40 @@ export default function MenuPage() {
1298
1300
  const [isRefreshingMenu, setIsRefreshingMenu] = useState(false);
1299
1301
 
1300
1302
  const [page, setPage] = useState(1);
1303
+ const { data: generalSettings } = useQuery<{
1304
+ data: Array<{ slug: string; value: string }>;
1305
+ }>({
1306
+ queryKey: ['setting-group-general'],
1307
+ queryFn: async () => {
1308
+ const response = await request<{
1309
+ data: Array<{ slug: string; value: string }>;
1310
+ }>({ url: '/setting/group/general', method: 'GET' });
1311
+ return response.data;
1312
+ },
1313
+ staleTime: 5 * 60 * 1000,
1314
+ });
1315
+
1316
+ const pageSizeOptions = useMemo(() => {
1317
+ const setting = generalSettings?.data?.find(
1318
+ (s) => s.slug === 'pagination-page-sizes'
1319
+ );
1320
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
1321
+ try {
1322
+ const parsed = JSON.parse(setting.value) as string[];
1323
+ const sizes = parsed
1324
+ .map(Number)
1325
+ .filter((n) => !isNaN(n) && n > 0)
1326
+ .sort((a, b) => a - b);
1327
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
1328
+ } catch {
1329
+ return DEFAULT_PAGE_SIZES;
1330
+ }
1331
+ }, [generalSettings]);
1332
+
1301
1333
  const [pageSize, setPageSize] = usePersistedPageSize({
1302
1334
  storageKey: 'pagination:core-menu:pageSize',
1303
1335
  defaultValue: 12,
1304
- allowedValues: [6, 12, 24, 48],
1336
+ allowedValues: pageSizeOptions,
1305
1337
  });
1306
1338
 
1307
1339
  const { data: allMenus, refetch: refetchAllMenus } = useQuery<MenuItem[]>({
@@ -2578,7 +2610,7 @@ export default function MenuPage() {
2578
2610
  setPageSize(size);
2579
2611
  setPage(1);
2580
2612
  }}
2581
- pageSizeOptions={[6, 12, 24, 48]}
2613
+ pageSizeOptions={pageSizeOptions}
2582
2614
  />
2583
2615
  </div>
2584
2616
 
@@ -44,6 +44,10 @@ export default function PreferencesPage() {
44
44
  const {
45
45
  currentTheme,
46
46
  setCurrentTheme,
47
+ density,
48
+ setDensity,
49
+ zoom,
50
+ setZoom,
47
51
  refetchUser,
48
52
  request,
49
53
  getSettingValue,
@@ -121,6 +125,21 @@ export default function PreferencesPage() {
121
125
  }
122
126
  };
123
127
 
128
+ const handleSpacingDensityChange = (
129
+ value: 'compact' | 'comfortable' | 'spacious'
130
+ ) => {
131
+ setDensity(value);
132
+ showToastHandler(
133
+ 'success',
134
+ t('spacingDensityUpdated') || 'Spacing density updated successfully'
135
+ );
136
+ };
137
+
138
+ const handleZoomChange = (value: string) => {
139
+ setZoom(value);
140
+ showToastHandler('success', t('zoomUpdated') || 'Zoom updated successfully');
141
+ };
142
+
124
143
  const handleLanguageChange = async (code: string) => {
125
144
  try {
126
145
  await request({
@@ -226,6 +245,64 @@ export default function PreferencesPage() {
226
245
  </Select>
227
246
  </div>
228
247
  </div>
248
+
249
+ <div className="flex justify-between mt-4">
250
+ <div className="flex-col">
251
+ <label className="font-semibold">
252
+ {t('spacingDensity')}
253
+ </label>
254
+ <p className="text-muted-foreground text-sm">
255
+ {t('setPreferredSpacingDensity')}
256
+ </p>
257
+ </div>
258
+
259
+ <div className="h-full flex items-center">
260
+ <Select
261
+ value={density}
262
+ onValueChange={handleSpacingDensityChange}
263
+ >
264
+ <SelectTrigger className="w-[180px] bg-background">
265
+ <SelectValue placeholder={t('selectSpacingDensity')} />
266
+ </SelectTrigger>
267
+ <SelectContent>
268
+ <SelectItem value="compact">
269
+ {t('densityCompact')}
270
+ </SelectItem>
271
+ <SelectItem value="comfortable">
272
+ {t('densityComfortable')}
273
+ </SelectItem>
274
+ <SelectItem value="spacious">
275
+ {t('densitySpacious')}
276
+ </SelectItem>
277
+ </SelectContent>
278
+ </Select>
279
+ </div>
280
+ </div>
281
+
282
+ <div className="flex justify-between mt-4">
283
+ <div className="flex-col">
284
+ <label className="font-semibold">{t('zoom')}</label>
285
+ <p className="text-muted-foreground text-sm">
286
+ {t('setPreferredZoom')}
287
+ </p>
288
+ </div>
289
+
290
+ <div className="h-full flex items-center">
291
+ <Select value={zoom} onValueChange={handleZoomChange}>
292
+ <SelectTrigger className="w-[180px] bg-background">
293
+ <SelectValue placeholder={t('selectZoom')} />
294
+ </SelectTrigger>
295
+ <SelectContent>
296
+ <SelectItem value="80%">80%</SelectItem>
297
+ <SelectItem value="90%">90%</SelectItem>
298
+ <SelectItem value="100%">100%</SelectItem>
299
+ <SelectItem value="110%">110%</SelectItem>
300
+ <SelectItem value="125%">125%</SelectItem>
301
+ <SelectItem value="150%">150%</SelectItem>
302
+ </SelectContent>
303
+ </Select>
304
+ </div>
305
+ </div>
229
306
  </CardContent>
230
307
  </Card>
231
308
 
@@ -55,6 +55,8 @@ type RoleMenusSectionProps = {
55
55
 
56
56
  const EMPTY_MENUS: Menu[] = [];
57
57
 
58
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
59
+
58
60
  export function RoleMenusSection({
59
61
  roleId,
60
62
  onMenuChange,
@@ -63,10 +65,40 @@ export function RoleMenusSection({
63
65
  const { request, currentLocaleCode } = useApp();
64
66
  const [togglingMenuId, setTogglingMenuId] = useState<number | null>(null);
65
67
  const [page, setPage] = useState(1);
68
+ const { data: generalSettings } = useQuery<{
69
+ data: Array<{ slug: string; value: string }>;
70
+ }>({
71
+ queryKey: ['setting-group-general'],
72
+ queryFn: async () => {
73
+ const response = await request<{
74
+ data: Array<{ slug: string; value: string }>;
75
+ }>({ url: '/setting/group/general', method: 'GET' });
76
+ return response.data;
77
+ },
78
+ staleTime: 5 * 60 * 1000,
79
+ });
80
+
81
+ const pageSizeOptions = useMemo(() => {
82
+ const setting = generalSettings?.data?.find(
83
+ (s) => s.slug === 'pagination-page-sizes'
84
+ );
85
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
86
+ try {
87
+ const parsed = JSON.parse(setting.value) as string[];
88
+ const sizes = parsed
89
+ .map(Number)
90
+ .filter((n) => !isNaN(n) && n > 0)
91
+ .sort((a, b) => a - b);
92
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
93
+ } catch {
94
+ return DEFAULT_PAGE_SIZES;
95
+ }
96
+ }, [generalSettings]);
97
+
66
98
  const [pageSize, setPageSize] = usePersistedPageSize({
67
99
  storageKey: 'pagination:global:pageSize',
68
- defaultValue: 10,
69
- allowedValues: [10, 20, 30, 40, 50],
100
+ defaultValue: 12,
101
+ allowedValues: pageSizeOptions,
70
102
  });
71
103
  const [expandedMenus, setExpandedMenus] = useState<string[]>([]);
72
104
 
@@ -466,7 +498,7 @@ export function RoleMenusSection({
466
498
  <SelectValue placeholder={pageSize} />
467
499
  </SelectTrigger>
468
500
  <SelectContent side="top">
469
- {[10, 20, 30, 40, 50].map((size) => (
501
+ {pageSizeOptions.map((size) => (
470
502
  <SelectItem key={size} value={`${size}`}>
471
503
  {size}
472
504
  </SelectItem>
@@ -156,6 +156,8 @@ function getErrorMessage(error: unknown, fallbackMessage: string) {
156
156
  return fallbackMessage;
157
157
  }
158
158
 
159
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
160
+
159
161
  export default function RolePage() {
160
162
  const t = useTranslations('core.RolePage');
161
163
  const { request, currentLocaleCode, locales, getSettingValue } = useApp();
@@ -168,10 +170,40 @@ export default function RolePage() {
168
170
  const [selectedLocale, setSelectedLocale] = useState(currentLocaleCode);
169
171
 
170
172
  const [page, setPage] = useState(1);
173
+ const { data: generalSettings } = useQuery<{
174
+ data: Array<{ slug: string; value: string }>;
175
+ }>({
176
+ queryKey: ['setting-group-general'],
177
+ queryFn: async () => {
178
+ const response = await request<{
179
+ data: Array<{ slug: string; value: string }>;
180
+ }>({ url: '/setting/group/general', method: 'GET' });
181
+ return response.data;
182
+ },
183
+ staleTime: 5 * 60 * 1000,
184
+ });
185
+
186
+ const pageSizeOptions = useMemo(() => {
187
+ const setting = generalSettings?.data?.find(
188
+ (s) => s.slug === 'pagination-page-sizes'
189
+ );
190
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
191
+ try {
192
+ const parsed = JSON.parse(setting.value) as string[];
193
+ const sizes = parsed
194
+ .map(Number)
195
+ .filter((n) => !isNaN(n) && n > 0)
196
+ .sort((a, b) => a - b);
197
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
198
+ } catch {
199
+ return DEFAULT_PAGE_SIZES;
200
+ }
201
+ }, [generalSettings]);
202
+
171
203
  const [pageSize, setPageSize] = usePersistedPageSize({
172
204
  storageKey: 'pagination:core-roles:pageSize',
173
205
  defaultValue: 12,
174
- allowedValues: [6, 12, 24, 48],
206
+ allowedValues: pageSizeOptions,
175
207
  });
176
208
  const [activeTab, setActiveTab] = useState('basic-info');
177
209
  const [openDeleteModal, setOpenDeleteModal] = useState(false);
@@ -681,7 +713,7 @@ export default function RolePage() {
681
713
  setPageSize(size);
682
714
  setPage(1);
683
715
  }}
684
- pageSizeOptions={[6, 12, 24, 48]}
716
+ pageSizeOptions={pageSizeOptions}
685
717
  />
686
718
  </div>
687
719
 
@@ -26,7 +26,7 @@ import {
26
26
  Search,
27
27
  } from 'lucide-react';
28
28
  import { useTranslations } from 'next-intl';
29
- import { useState } from 'react';
29
+ import { useMemo, useState } from 'react';
30
30
  import { toast } from 'sonner';
31
31
 
32
32
  type RouteType = {
@@ -54,6 +54,8 @@ const normalizeRouteMethod = (method: string | null) => {
54
54
  return normalizedMethod || UNKNOWN_METHOD_LABEL;
55
55
  };
56
56
 
57
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
58
+
57
59
  export function RoleRoutesSection({
58
60
  roleId,
59
61
  onRouteChange,
@@ -62,10 +64,40 @@ export function RoleRoutesSection({
62
64
  const { request } = useApp();
63
65
  const [togglingRouteId, setTogglingRouteId] = useState<number | null>(null);
64
66
  const [page, setPage] = useState(1);
67
+ const { data: generalSettings } = useQuery<{
68
+ data: Array<{ slug: string; value: string }>;
69
+ }>({
70
+ queryKey: ['setting-group-general'],
71
+ queryFn: async () => {
72
+ const response = await request<{
73
+ data: Array<{ slug: string; value: string }>;
74
+ }>({ url: '/setting/group/general', method: 'GET' });
75
+ return response.data;
76
+ },
77
+ staleTime: 5 * 60 * 1000,
78
+ });
79
+
80
+ const pageSizeOptions = useMemo(() => {
81
+ const setting = generalSettings?.data?.find(
82
+ (s) => s.slug === 'pagination-page-sizes'
83
+ );
84
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
85
+ try {
86
+ const parsed = JSON.parse(setting.value) as string[];
87
+ const sizes = parsed
88
+ .map(Number)
89
+ .filter((n) => !isNaN(n) && n > 0)
90
+ .sort((a, b) => a - b);
91
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
92
+ } catch {
93
+ return DEFAULT_PAGE_SIZES;
94
+ }
95
+ }, [generalSettings]);
96
+
65
97
  const [pageSize, setPageSize] = usePersistedPageSize({
66
98
  storageKey: 'pagination:global:pageSize',
67
- defaultValue: 10,
68
- allowedValues: [10, 20, 30, 40, 50],
99
+ defaultValue: 12,
100
+ allowedValues: pageSizeOptions,
69
101
  });
70
102
  const [searchTerm, setSearchTerm] = useState('');
71
103
  const [searchType, setSearchType] = useState<SearchType>('contains');
@@ -355,7 +387,7 @@ export function RoleRoutesSection({
355
387
  <SelectValue placeholder={pageSize} />
356
388
  </SelectTrigger>
357
389
  <SelectContent side="top">
358
- {[10, 20, 30, 40, 50].map((size) => (
390
+ {pageSizeOptions.map((size) => (
359
391
  <SelectItem key={size} value={`${size}`}>
360
392
  {size}
361
393
  </SelectItem>