@hed-hog/core 0.0.370 → 0.0.373

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 (248) 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_provider.yaml +9 -0
  158. package/hedhog/data/integration_type.yaml +6 -0
  159. package/hedhog/data/menu.yaml +6 -0
  160. package/hedhog/data/role.yaml +7 -0
  161. package/hedhog/data/route.yaml +36 -0
  162. package/hedhog/data/setting_group.yaml +119 -13
  163. package/hedhog/frontend/app/access-log/page.tsx.ejs +35 -3
  164. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +36 -3
  165. package/hedhog/frontend/app/account/components/mcp-tokens.tsx.ejs +39 -4
  166. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +39 -4
  167. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +35 -2
  168. package/hedhog/frontend/app/configurations/[slug]/components/byte-size-input.tsx.ejs +160 -0
  169. package/hedhog/frontend/app/configurations/[slug]/components/duration-input.tsx.ejs +157 -0
  170. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +244 -63
  171. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +33 -1
  172. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +35 -3
  173. package/hedhog/frontend/app/dashboard/management/tabs/components-tab.tsx.ejs +36 -4
  174. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +35 -3
  175. package/hedhog/frontend/app/dashboard/management/tabs/dashboards-tab.tsx.ejs +36 -4
  176. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +35 -3
  177. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +156 -38
  178. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +118 -11
  179. package/hedhog/frontend/app/mail/log/page.tsx.ejs +37 -4
  180. package/hedhog/frontend/app/menu/page.tsx.ejs +34 -2
  181. package/hedhog/frontend/app/preferences/page.tsx.ejs +77 -0
  182. package/hedhog/frontend/app/roles/menus.tsx.ejs +35 -3
  183. package/hedhog/frontend/app/roles/page.tsx.ejs +34 -2
  184. package/hedhog/frontend/app/roles/routes.tsx.ejs +36 -4
  185. package/hedhog/frontend/app/roles/users.tsx.ejs +36 -4
  186. package/hedhog/frontend/app/routes/page.tsx.ejs +34 -2
  187. package/hedhog/frontend/app/setting-list/page.tsx.ejs +34 -2
  188. package/hedhog/frontend/app/users/page.tsx.ejs +51 -21
  189. package/hedhog/frontend/messages/en.json +69 -4
  190. package/hedhog/frontend/messages/pt.json +69 -4
  191. package/hedhog/table/setting.yaml +2 -0
  192. package/hedhog/table/webhook_call_log.yaml +49 -46
  193. package/package.json +9 -4
  194. package/src/ai/ai.service.ts +9 -6
  195. package/src/app-command/app-command.module.ts +9 -0
  196. package/src/{webhook-integration/webhook-command.registry.ts → app-command/app-command.registry.ts} +9 -9
  197. package/src/auth/auth.controller.ts +20 -4
  198. package/src/auth/auth.service.ts +10 -36
  199. package/src/auth/dto/forget.dto.ts +5 -1
  200. package/src/auth/guards/auth.guard.ts +4 -1
  201. package/src/challenge/challenge.service.ts +30 -19
  202. package/src/file/dto/upload.dto.ts +25 -1
  203. package/src/file/file.controller.ts +31 -18
  204. package/src/file/file.service.ts +341 -16
  205. package/src/file/image-optimization.service.ts +111 -46
  206. package/src/file/provider/s3.provider.ts +16 -7
  207. package/src/file/provider/spaces.provider.ts +15 -5
  208. package/src/index.ts +4 -1
  209. package/src/integration/services/event-subscriber.registry.ts +15 -0
  210. package/src/integration/services/outbox.processor.ts +2 -5
  211. package/src/integration/services/outbox.service.ts +35 -17
  212. package/src/integration-profile/integration-credential-crypto.service.ts +146 -0
  213. package/src/integration-profile/integration-profile.controller.ts +2 -0
  214. package/src/integration-profile/integration-profile.module.ts +5 -2
  215. package/src/integration-profile/integration-profile.secrets.ts +51 -0
  216. package/src/integration-profile/integration-profile.service.ts +335 -54
  217. package/src/integration-profile/integration-profile.utils.ts +56 -0
  218. package/src/language/en.json +5 -1
  219. package/src/language/pt.json +5 -1
  220. package/src/mail/mail.controller.ts +5 -29
  221. package/src/mail/mail.module.ts +5 -5
  222. package/src/mail/mail.service.ts +37 -13
  223. package/src/mcp-chat/mcp-chat.service.ts +5 -5
  224. package/src/oauth/oauth-callback-coordinator.service.spec.ts +159 -0
  225. package/src/oauth/providers/facebook.provider.ts +3 -3
  226. package/src/oauth/providers/github.provider.ts +3 -3
  227. package/src/oauth/providers/google.provider.ts +3 -3
  228. package/src/oauth/providers/microsoft-entra-id.provider.ts +3 -3
  229. package/src/oauth/providers/microsoft.provider.ts +3 -3
  230. package/src/pdf/pdf.service.ts +19 -6
  231. package/src/profile/profile.controller.ts +21 -14
  232. package/src/profile/profile.service.ts +23 -20
  233. package/src/security/security.service.spec.ts +222 -0
  234. package/src/setting/setting.service.ts +15 -2
  235. package/src/token/token.service.ts +2 -1
  236. package/src/user/user.controller.ts +26 -16
  237. package/src/user/user.service.ts +25 -0
  238. package/src/utils/webauthn-origin.ts +38 -0
  239. package/src/webhook-integration/webhook-integration.controller.ts +23 -0
  240. package/src/webhook-integration/webhook-integration.module.ts +3 -3
  241. package/src/webhook-integration/webhook-integration.service.ts +115 -5
  242. package/dist/webhook-integration/webhook-command.registry.d.ts +0 -22
  243. package/dist/webhook-integration/webhook-command.registry.d.ts.map +0 -1
  244. package/dist/webhook-integration/webhook-command.registry.js.map +0 -1
  245. package/src/menu/menu.service.spec.ts +0 -247
  246. package/src/route/route.service.spec.ts +0 -300
  247. package/src/screen/screen.service.spec.ts +0 -298
  248. package/src/user/user.service.spec.ts +0 -294
@@ -30,7 +30,7 @@ import {
30
30
  UserCircle,
31
31
  } from 'lucide-react';
32
32
  import { useTranslations } from 'next-intl';
33
- import { useState } from 'react';
33
+ import { useMemo, useState } from 'react';
34
34
  import { toast } from 'sonner';
35
35
 
36
36
  type RoleUsersSectionProps = {
@@ -38,6 +38,8 @@ type RoleUsersSectionProps = {
38
38
  onUserChange?: () => void;
39
39
  };
40
40
 
41
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
42
+
41
43
  export function RoleUsersSection({
42
44
  roleId,
43
45
  onUserChange,
@@ -46,10 +48,40 @@ export function RoleUsersSection({
46
48
  const { request } = useApp();
47
49
  const [togglingUserId, setTogglingUserId] = useState<number | null>(null);
48
50
  const [page, setPage] = useState(1);
51
+ const { data: generalSettings } = useQuery<{
52
+ data: Array<{ slug: string; value: string }>;
53
+ }>({
54
+ queryKey: ['setting-group-general'],
55
+ queryFn: async () => {
56
+ const response = await request<{
57
+ data: Array<{ slug: string; value: string }>;
58
+ }>({ url: '/setting/group/general', method: 'GET' });
59
+ return response.data;
60
+ },
61
+ staleTime: 5 * 60 * 1000,
62
+ });
63
+
64
+ const pageSizeOptions = useMemo(() => {
65
+ const setting = generalSettings?.data?.find(
66
+ (s) => s.slug === 'pagination-page-sizes'
67
+ );
68
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
69
+ try {
70
+ const parsed = JSON.parse(setting.value) as string[];
71
+ const sizes = parsed
72
+ .map(Number)
73
+ .filter((n) => !isNaN(n) && n > 0)
74
+ .sort((a, b) => a - b);
75
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
76
+ } catch {
77
+ return DEFAULT_PAGE_SIZES;
78
+ }
79
+ }, [generalSettings]);
80
+
49
81
  const [pageSize, setPageSize] = usePersistedPageSize({
50
82
  storageKey: 'pagination:global:pageSize',
51
- defaultValue: 10,
52
- allowedValues: [10, 20, 30, 40, 50],
83
+ defaultValue: 12,
84
+ allowedValues: pageSizeOptions,
53
85
  });
54
86
  const [searchTerm, setSearchTerm] = useState('');
55
87
  const debouncedSearch = useDebounce(searchTerm);
@@ -259,7 +291,7 @@ export function RoleUsersSection({
259
291
  <SelectValue placeholder={pageSize} />
260
292
  </SelectTrigger>
261
293
  <SelectContent side="top">
262
- {[10, 20, 30, 40, 50].map((size) => (
294
+ {pageSizeOptions.map((size) => (
263
295
  <SelectItem key={size} value={`${size}`}>
264
296
  {size}
265
297
  </SelectItem>
@@ -505,6 +505,8 @@ function RouteScreensSection({ routeId }: { routeId: number }) {
505
505
  );
506
506
  }
507
507
 
508
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
509
+
508
510
  export default function RoutePage() {
509
511
  const t = useTranslations('core.RoutePage');
510
512
  const { request, currentLocaleCode } = useApp();
@@ -514,10 +516,40 @@ export default function RoutePage() {
514
516
  const [methodFilter, setMethodFilter] = useState<string>(ALL_FILTER);
515
517
  const [viewMode, setViewMode] = useState<'cards' | 'list'>('cards');
516
518
  const [page, setPage] = useState(1);
519
+ const { data: generalSettings } = useQuery<{
520
+ data: Array<{ slug: string; value: string }>;
521
+ }>({
522
+ queryKey: ['setting-group-general'],
523
+ queryFn: async () => {
524
+ const response = await request<{
525
+ data: Array<{ slug: string; value: string }>;
526
+ }>({ url: '/setting/group/general', method: 'GET' });
527
+ return response.data;
528
+ },
529
+ staleTime: 5 * 60 * 1000,
530
+ });
531
+
532
+ const pageSizeOptions = useMemo(() => {
533
+ const setting = generalSettings?.data?.find(
534
+ (s) => s.slug === 'pagination-page-sizes'
535
+ );
536
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
537
+ try {
538
+ const parsed = JSON.parse(setting.value) as string[];
539
+ const sizes = parsed
540
+ .map(Number)
541
+ .filter((n) => !isNaN(n) && n > 0)
542
+ .sort((a, b) => a - b);
543
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
544
+ } catch {
545
+ return DEFAULT_PAGE_SIZES;
546
+ }
547
+ }, [generalSettings]);
548
+
517
549
  const [pageSize, setPageSize] = usePersistedPageSize({
518
550
  storageKey: 'pagination:core-routes:pageSize',
519
551
  defaultValue: 12,
520
- allowedValues: [6, 12, 24, 48],
552
+ allowedValues: pageSizeOptions,
521
553
  });
522
554
  const [isDialogOpen, setIsDialogOpen] = useState(false);
523
555
  const [editingRoute, setEditingRoute] = useState<RouteItem | null>(null);
@@ -1067,7 +1099,7 @@ export default function RoutePage() {
1067
1099
  setPageSize(nextPageSize);
1068
1100
  setPage(1);
1069
1101
  }}
1070
- pageSizeOptions={[6, 12, 24, 48]}
1102
+ pageSizeOptions={pageSizeOptions}
1071
1103
  />
1072
1104
  </div>
1073
1105
 
@@ -90,15 +90,47 @@ const createEmptyPage = <T,>(pageSize: number): PaginatedResult<T> => ({
90
90
  data: [],
91
91
  });
92
92
 
93
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
94
+
93
95
  export default function CoreSettingListPage() {
94
96
  const t = useTranslations('core.SettingListPage');
95
97
  const { request, currentLocaleCode, showToastHandler } = useApp();
96
98
  const [search, setSearch] = useState('');
97
99
  const [page, setPage] = useState(1);
100
+ const { data: generalSettings } = useQuery<{
101
+ data: Array<{ slug: string; value: string }>;
102
+ }>({
103
+ queryKey: ['setting-group-general'],
104
+ queryFn: async () => {
105
+ const response = await request<{
106
+ data: Array<{ slug: string; value: string }>;
107
+ }>({ url: '/setting/group/general', method: 'GET' });
108
+ return response.data;
109
+ },
110
+ staleTime: 5 * 60 * 1000,
111
+ });
112
+
113
+ const pageSizeOptions = useMemo(() => {
114
+ const setting = generalSettings?.data?.find(
115
+ (s) => s.slug === 'pagination-page-sizes'
116
+ );
117
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
118
+ try {
119
+ const parsed = JSON.parse(setting.value) as string[];
120
+ const sizes = parsed
121
+ .map(Number)
122
+ .filter((n) => !isNaN(n) && n > 0)
123
+ .sort((a, b) => a - b);
124
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
125
+ } catch {
126
+ return DEFAULT_PAGE_SIZES;
127
+ }
128
+ }, [generalSettings]);
129
+
98
130
  const [pageSize, setPageSize] = usePersistedPageSize({
99
131
  storageKey: 'pagination:setting-list:pageSize',
100
132
  defaultValue: 12,
101
- allowedValues: [6, 12, 24, 48],
133
+ allowedValues: pageSizeOptions,
102
134
  });
103
135
  const [viewMode, setViewMode] = usePersistedViewMode({
104
136
  storageKey: 'pagination:setting-list:viewMode',
@@ -484,7 +516,7 @@ export default function CoreSettingListPage() {
484
516
  setPageSize(size);
485
517
  setPage(1);
486
518
  }}
487
- pageSizeOptions={[6, 12, 24, 48]}
519
+ pageSizeOptions={pageSizeOptions}
488
520
  />
489
521
  )}
490
522
 
@@ -17,12 +17,6 @@ import {
17
17
  CardHeader,
18
18
  CardTitle,
19
19
  } from '@/components/ui/card';
20
- import {
21
- DropdownMenu,
22
- DropdownMenuContent,
23
- DropdownMenuItem,
24
- DropdownMenuTrigger,
25
- } from '@/components/ui/dropdown-menu';
26
20
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
27
21
  import { Skeleton } from '@/components/ui/skeleton';
28
22
  import {
@@ -34,6 +28,11 @@ import {
34
28
  TableRow,
35
29
  } from '@/components/ui/table';
36
30
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
31
+ import {
32
+ Tooltip,
33
+ TooltipContent,
34
+ TooltipTrigger,
35
+ } from '@/components/ui/tooltip';
37
36
  import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
38
37
  import { getPhotoUrl } from '@/lib/get-photo-url';
39
38
  import { getUserEmail } from '@/lib/get-user-email';
@@ -50,7 +49,6 @@ import {
50
49
  LayoutGrid,
51
50
  List,
52
51
  Mail,
53
- MoreHorizontal,
54
52
  Pencil,
55
53
  ShieldCheck,
56
54
  UserPlus,
@@ -72,6 +70,8 @@ type RequestResponse<T> = {
72
70
  };
73
71
  };
74
72
 
73
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
74
+
75
75
  export default function UserPage() {
76
76
  const t = useTranslations('core.UserPage');
77
77
  const { request, currentLocaleCode } = useApp();
@@ -82,10 +82,40 @@ export default function UserPage() {
82
82
  const [viewMode, setViewMode] = useState<UserViewMode>('table');
83
83
 
84
84
  const [page, setPage] = useState(1);
85
+ const { data: generalSettings } = useQuery<{
86
+ data: Array<{ slug: string; value: string }>;
87
+ }>({
88
+ queryKey: ['setting-group-general'],
89
+ queryFn: async () => {
90
+ const response = await request<{
91
+ data: Array<{ slug: string; value: string }>;
92
+ }>({ url: '/setting/group/general', method: 'GET' });
93
+ return response.data;
94
+ },
95
+ staleTime: 5 * 60 * 1000,
96
+ });
97
+
98
+ const pageSizeOptions = useMemo(() => {
99
+ const setting = generalSettings?.data?.find(
100
+ (s) => s.slug === 'pagination-page-sizes'
101
+ );
102
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
103
+ try {
104
+ const parsed = JSON.parse(setting.value) as string[];
105
+ const sizes = parsed
106
+ .map(Number)
107
+ .filter((n) => !isNaN(n) && n > 0)
108
+ .sort((a, b) => a - b);
109
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
110
+ } catch {
111
+ return DEFAULT_PAGE_SIZES;
112
+ }
113
+ }, [generalSettings]);
114
+
85
115
  const [pageSize, setPageSize] = usePersistedPageSize({
86
116
  storageKey: 'pagination:global:pageSize',
87
117
  defaultValue: 12,
88
- allowedValues: [6, 12, 24, 48],
118
+ allowedValues: pageSizeOptions,
89
119
  });
90
120
  const [statusFilter, setStatusFilter] = useState('all');
91
121
 
@@ -253,19 +283,19 @@ export default function UserPage() {
253
283
  header: '',
254
284
  cell: ({ row }) => (
255
285
  <div className="flex justify-end">
256
- <DropdownMenu>
257
- <DropdownMenuTrigger asChild>
258
- <Button variant="ghost" size="icon" className="h-8 w-8">
259
- <MoreHorizontal className="h-4 w-4" />
286
+ <Tooltip>
287
+ <TooltipTrigger asChild>
288
+ <Button
289
+ variant="ghost"
290
+ size="icon"
291
+ className="h-8 w-8"
292
+ onClick={() => handleOpenEdit(row.original)}
293
+ >
294
+ <Pencil className="h-4 w-4" />
260
295
  </Button>
261
- </DropdownMenuTrigger>
262
- <DropdownMenuContent align="end">
263
- <DropdownMenuItem onClick={() => handleOpenEdit(row.original)}>
264
- <Pencil className="mr-2 h-4 w-4" />
265
- {t('buttonEditUser')}
266
- </DropdownMenuItem>
267
- </DropdownMenuContent>
268
- </DropdownMenu>
296
+ </TooltipTrigger>
297
+ <TooltipContent>{t('buttonEditUser')}</TooltipContent>
298
+ </Tooltip>
269
299
  </div>
270
300
  ),
271
301
  },
@@ -599,7 +629,7 @@ export default function UserPage() {
599
629
  setPageSize(size);
600
630
  setPage(1);
601
631
  }}
602
- pageSizeOptions={[6, 12, 24, 48]}
632
+ pageSizeOptions={pageSizeOptions}
603
633
  />
604
634
  </div>
605
635
 
@@ -113,6 +113,17 @@
113
113
  "light": "Light",
114
114
  "dark": "Dark",
115
115
  "system": "System",
116
+ "spacingDensity": "Spacing Density",
117
+ "setPreferredSpacingDensity": "Set the spacing density of the interface",
118
+ "selectSpacingDensity": "Select the density",
119
+ "densityCompact": "Small",
120
+ "densityComfortable": "Medium",
121
+ "densitySpacious": "Large",
122
+ "spacingDensityUpdated": "Spacing density updated successfully",
123
+ "zoom": "Interface Zoom",
124
+ "setPreferredZoom": "Set the zoom level of the interface",
125
+ "selectZoom": "Select the zoom",
126
+ "zoomUpdated": "Zoom updated successfully",
116
127
  "localization": "Localization",
117
128
  "language": "Language",
118
129
  "setPreferredLanguage": "Set your preferred language",
@@ -713,6 +724,14 @@
713
724
  "passwordRequired": "Password is required",
714
725
  "passwordMinLength": "Password must be at least 6 characters long",
715
726
  "mfaVerificationMessage": "Enter the verification code",
727
+ "securityKeyAvailableMessage": "Use your Security Key to continue",
728
+ "securityKeyPromptTitle": "Sign in with Security Key",
729
+ "securityKeyPromptDescription": "Your account has a registered Security Key. You can authenticate now without leaving this screen.",
730
+ "securityKeyButton": "Use Security Key",
731
+ "securityKeyButtonLoading": "Waiting for Security Key...",
732
+ "securityKeyUnavailable": "Could not start Security Key authentication.",
733
+ "otherMfaMethodsButton": "Use another method",
734
+ "backToPasswordButton": "Back to email and password",
716
735
  "passwordResetRequiredMessage": "You must change your password before continuing.",
717
736
  "hidePassword": "Hide password",
718
737
  "showPassword": "Show password",
@@ -1244,6 +1263,15 @@
1244
1263
  "temporaryOpenUrlNotReturned": "Temporary open URL not returned.",
1245
1264
  "uploadedFileAlt": "Uploaded file",
1246
1265
  "fileUrlPlaceholder": "File URL",
1266
+ "durationUnitSeconds": "Seconds",
1267
+ "durationUnitMinutes": "Minutes",
1268
+ "durationUnitHours": "Hours",
1269
+ "durationUnitDays": "Days",
1270
+ "byteUnitBytes": "Bytes",
1271
+ "byteUnitKB": "KB",
1272
+ "byteUnitMB": "MB",
1273
+ "byteUnitGB": "GB",
1274
+ "byteUnitTB": "TB",
1247
1275
  "selectOption": "Select an option",
1248
1276
  "selectMailProfile": "Select a mail profile",
1249
1277
  "mailProfileEmptyState": "No mail profiles found.",
@@ -1294,7 +1322,9 @@
1294
1322
  "instanceNamePlaceholder": "MyInstance",
1295
1323
  "tokenLabel": "Token *",
1296
1324
  "tokenPlaceholder": "Authentication token"
1297
- }
1325
+ },
1326
+ "selectInfraProfile": "Select infrastructure profile",
1327
+ "infraProfileEmptyState": "No infrastructure profiles found. Create a DigitalOcean or Kubernetes profile first."
1298
1328
  },
1299
1329
  "AddWidgetDialog": {
1300
1330
  "title": "Select Dashboard Components",
@@ -1629,6 +1659,7 @@
1629
1659
  "dataFetching": "Data Fetching"
1630
1660
  },
1631
1661
  "RichTextEditor": {
1662
+ "emptyPlaceholder": "Start writing...",
1632
1663
  "bold": "Bold",
1633
1664
  "italic": "Italic",
1634
1665
  "underline": "Underline",
@@ -1784,7 +1815,17 @@
1784
1815
  "understood": "Understood",
1785
1816
  "statusCode": "Status",
1786
1817
  "method": "Method",
1787
- "url": "URL"
1818
+ "url": "URL",
1819
+ "technicalDetails": "Technical details",
1820
+ "currentPath": "Current path",
1821
+ "user": "User",
1822
+ "email": "Email",
1823
+ "browser": "Browser",
1824
+ "os": "Operating system",
1825
+ "datetime": "Date/time",
1826
+ "copy": "Copy details",
1827
+ "copied": "Copied!",
1828
+ "copySuccess": "Details copied to clipboard"
1788
1829
  },
1789
1830
  "ForbiddenPage": {
1790
1831
  "message": "Forbidden: You don't have permission to access this page.",
@@ -2026,7 +2067,13 @@
2026
2067
  "logPayload": "Received payload",
2027
2068
  "logRequest": "Request",
2028
2069
  "logOrderDesc": "Most recent first",
2029
- "logOrderAsc": "Oldest first"
2070
+ "logOrderAsc": "Oldest first",
2071
+ "logRetry": "Retry call",
2072
+ "logRetrySuccess": "Webhook call retried successfully",
2073
+ "logRetryError": "Failed to retry the webhook call",
2074
+ "logRetryFailedAgain": "The retry ran but the call failed again",
2075
+ "logRetryConfirmTitle": "Retry this webhook call?",
2076
+ "logRetryConfirmDescription": "All active actions of this webhook will run again using the original payload. Actions that already succeeded (e.g. emails or WhatsApp messages) may be sent again."
2030
2077
  },
2031
2078
  "McpChatPage": {
2032
2079
  "title": "MCP Chat",
@@ -2378,6 +2425,24 @@
2378
2425
  "noCallbackUrls": "No callback URLs configured.",
2379
2426
  "callbackUrlPlaceholder": "https://yourdomain.com/callback/...",
2380
2427
  "showPassword": "Show value",
2381
- "hidePassword": "Hide value"
2428
+ "hidePassword": "Hide value",
2429
+ "leaveBlankToKeep": "Leave blank to keep current value",
2430
+ "changeSecret": "Change secret",
2431
+ "fieldApiToken": "API Token",
2432
+ "fieldClusterId": "Cluster ID",
2433
+ "fieldVideoNodePoolId": "Video Node Pool ID",
2434
+ "fieldVideoNodePoolName": "Video Node Pool Name",
2435
+ "fieldApiServer": "API Server URL",
2436
+ "fieldCaCert": "CA Certificate (PEM)",
2437
+ "fieldNamespace": "Namespace",
2438
+ "fieldVideoNodePool": "Video Node Pool",
2439
+ "nodePoolSectionTitle": "Video Node Pool",
2440
+ "nodePoolSectionDescription": "Pool where video conversion pods run. Load the pools from DigitalOcean and pick one by name.",
2441
+ "nodePoolLoad": "Load pools",
2442
+ "nodePoolLoading": "Loading…",
2443
+ "nodePoolPlaceholder": "Select a node pool",
2444
+ "nodePoolNoneFound": "No node pools found for this cluster.",
2445
+ "nodePoolLoadError": "Failed to load node pools. Check the API token and cluster ID.",
2446
+ "nodePoolNeedCreds": "Fill in the API token and cluster ID first, then load the pools."
2382
2447
  }
2383
2448
  }
@@ -113,6 +113,17 @@
113
113
  "light": "Claro",
114
114
  "dark": "Escuro",
115
115
  "system": "Sistema",
116
+ "spacingDensity": "Densidade de Espaçamento",
117
+ "setPreferredSpacingDensity": "Defina a densidade de espaçamento da interface",
118
+ "selectSpacingDensity": "Selecione a densidade",
119
+ "densityCompact": "Pequeno",
120
+ "densityComfortable": "Médio",
121
+ "densitySpacious": "Grande",
122
+ "spacingDensityUpdated": "Densidade de espaçamento atualizada com sucesso",
123
+ "zoom": "Zoom da Interface",
124
+ "setPreferredZoom": "Defina o nível de zoom da interface",
125
+ "selectZoom": "Selecione o zoom",
126
+ "zoomUpdated": "Zoom atualizado com sucesso",
116
127
  "localization": "Localização",
117
128
  "language": "Idioma",
118
129
  "setPreferredLanguage": "Defina seu idioma preferido",
@@ -713,6 +724,14 @@
713
724
  "passwordRequired": "Senha obrigatória",
714
725
  "passwordMinLength": "Senha mínima de 6 caracteres",
715
726
  "mfaVerificationMessage": "Digite o código de verificação",
727
+ "securityKeyAvailableMessage": "Use sua Security Key para continuar",
728
+ "securityKeyPromptTitle": "Entrar com Security Key",
729
+ "securityKeyPromptDescription": "Sua conta possui uma Security Key cadastrada. Você pode autenticar agora sem sair desta tela.",
730
+ "securityKeyButton": "Usar Security Key",
731
+ "securityKeyButtonLoading": "Aguardando Security Key...",
732
+ "securityKeyUnavailable": "Não foi possível iniciar a autenticação com Security Key.",
733
+ "otherMfaMethodsButton": "Usar outro método",
734
+ "backToPasswordButton": "Voltar para e-mail e senha",
716
735
  "passwordResetRequiredMessage": "Você deve alterar sua senha antes de continuar.",
717
736
  "hidePassword": "Ocultar senha",
718
737
  "showPassword": "Mostrar senha",
@@ -1247,6 +1266,15 @@
1247
1266
  "temporaryOpenUrlNotReturned": "URL temporária de abertura não retornada.",
1248
1267
  "uploadedFileAlt": "Arquivo enviado",
1249
1268
  "fileUrlPlaceholder": "URL do arquivo",
1269
+ "durationUnitSeconds": "Segundos",
1270
+ "durationUnitMinutes": "Minutos",
1271
+ "durationUnitHours": "Horas",
1272
+ "durationUnitDays": "Dias",
1273
+ "byteUnitBytes": "Bytes",
1274
+ "byteUnitKB": "KB",
1275
+ "byteUnitMB": "MB",
1276
+ "byteUnitGB": "GB",
1277
+ "byteUnitTB": "TB",
1250
1278
  "selectOption": "Selecione uma opção",
1251
1279
  "selectMailProfile": "Selecione um perfil de email",
1252
1280
  "mailProfileEmptyState": "Nenhum perfil de email encontrado.",
@@ -1297,7 +1325,9 @@
1297
1325
  "instanceNamePlaceholder": "MinhaInstancia",
1298
1326
  "tokenLabel": "Token *",
1299
1327
  "tokenPlaceholder": "Token de autenticação"
1300
- }
1328
+ },
1329
+ "selectInfraProfile": "Selecione o perfil de infraestrutura",
1330
+ "infraProfileEmptyState": "Nenhum perfil de infraestrutura encontrado. Crie um perfil DigitalOcean ou Kubernetes primeiro."
1301
1331
  },
1302
1332
  "AddWidgetDialog": {
1303
1333
  "title": "Selecionar Componentes do Dashboard",
@@ -1634,6 +1664,7 @@
1634
1664
  "dataFetching": "Busca de Dados"
1635
1665
  },
1636
1666
  "RichTextEditor": {
1667
+ "emptyPlaceholder": "Escreva para começar...",
1637
1668
  "bold": "Negrito",
1638
1669
  "italic": "Itálico",
1639
1670
  "underline": "Sublinhado",
@@ -1839,7 +1870,17 @@
1839
1870
  "understood": "Entendi",
1840
1871
  "statusCode": "Status",
1841
1872
  "method": "Método",
1842
- "url": "URL"
1873
+ "url": "URL",
1874
+ "technicalDetails": "Detalhes técnicos",
1875
+ "currentPath": "Rota atual",
1876
+ "user": "Usuário",
1877
+ "email": "Email",
1878
+ "browser": "Navegador",
1879
+ "os": "Sistema operacional",
1880
+ "datetime": "Data/hora",
1881
+ "copy": "Copiar detalhes",
1882
+ "copied": "Copiado!",
1883
+ "copySuccess": "Detalhes copiados para a área de transferência"
1843
1884
  },
1844
1885
  "ForbiddenPage": {
1845
1886
  "message": "Proibido: Você não tem permissão para acessar esta página.",
@@ -2081,7 +2122,13 @@
2081
2122
  "logPayload": "Payload recebido",
2082
2123
  "logRequest": "Requisição",
2083
2124
  "logOrderDesc": "Mais recentes primeiro",
2084
- "logOrderAsc": "Mais antigos primeiro"
2125
+ "logOrderAsc": "Mais antigos primeiro",
2126
+ "logRetry": "Tentar novamente",
2127
+ "logRetrySuccess": "Chamada do webhook reexecutada com sucesso",
2128
+ "logRetryError": "Falha ao tentar reexecutar a chamada do webhook",
2129
+ "logRetryFailedAgain": "A reexecução rodou, mas a chamada falhou novamente",
2130
+ "logRetryConfirmTitle": "Tentar novamente esta chamada?",
2131
+ "logRetryConfirmDescription": "Todas as actions ativas deste webhook serão executadas de novo usando o payload original. Actions que já tinham dado certo (ex.: e-mails ou mensagens de WhatsApp) podem ser reenviadas."
2085
2132
  },
2086
2133
  "McpChatPage": {
2087
2134
  "title": "Chat MCP",
@@ -2433,6 +2480,24 @@
2433
2480
  "noCallbackUrls": "Nenhuma URL de callback configurada.",
2434
2481
  "callbackUrlPlaceholder": "https://seudominio.com/callback/...",
2435
2482
  "showPassword": "Mostrar valor",
2436
- "hidePassword": "Ocultar valor"
2483
+ "hidePassword": "Ocultar valor",
2484
+ "leaveBlankToKeep": "Deixe em branco para manter o valor atual",
2485
+ "changeSecret": "Trocar segredo",
2486
+ "fieldApiToken": "Token da API",
2487
+ "fieldClusterId": "ID do Cluster",
2488
+ "fieldVideoNodePoolId": "ID do Node Pool de Vídeo",
2489
+ "fieldVideoNodePoolName": "Nome do Node Pool de Vídeo",
2490
+ "fieldApiServer": "URL do API Server",
2491
+ "fieldCaCert": "Certificado CA (PEM)",
2492
+ "fieldNamespace": "Namespace",
2493
+ "fieldVideoNodePool": "Node Pool de Vídeo",
2494
+ "nodePoolSectionTitle": "Node Pool de Vídeo",
2495
+ "nodePoolSectionDescription": "Pool onde os pods de conversão de vídeo rodam. Carregue os pools da DigitalOcean e escolha um pelo nome.",
2496
+ "nodePoolLoad": "Carregar pools",
2497
+ "nodePoolLoading": "Carregando…",
2498
+ "nodePoolPlaceholder": "Selecione um node pool",
2499
+ "nodePoolNoneFound": "Nenhum node pool encontrado para este cluster.",
2500
+ "nodePoolLoadError": "Falha ao carregar os node pools. Verifique o token da API e o ID do cluster.",
2501
+ "nodePoolNeedCreds": "Preencha o token da API e o ID do cluster primeiro, depois carregue os pools."
2437
2502
  }
2438
2503
  }
@@ -22,6 +22,8 @@ columns:
22
22
  enum:
23
23
  - input-text
24
24
  - input-number
25
+ - input-duration
26
+ - input-bytes
25
27
  - input-secret
26
28
  - input-file
27
29
  - input-tags
@@ -1,46 +1,49 @@
1
- columns:
2
- - type: pk
3
- - type: fk
4
- name: webhook_integration_id
5
- isNullable: true
6
- references:
7
- table: webhook_integration
8
- column: id
9
- onDelete: set null
10
- onUpdate: cascade
11
- - name: public_uuid
12
- type: varchar
13
- length: 36
14
- - name: status
15
- type: enum
16
- enum:
17
- - success
18
- - failed
19
- - name: remote_ip
20
- type: varchar
21
- length: 127
22
- isNullable: true
23
- - name: duration_ms
24
- type: int
25
- default: 0
26
- - name: error_message
27
- type: text
28
- isNullable: true
29
- - name: payload_summary
30
- type: json
31
- isNullable: true
32
- - name: response_summary
33
- type: json
34
- isNullable: true
35
- - type: created_at
36
- - type: updated_at
37
- indices:
38
- - columns:
39
- - webhook_integration_id
40
- - columns:
41
- - public_uuid
42
- - columns:
43
- - status
44
- - columns:
45
- - created_at
46
-
1
+ columns:
2
+ - type: pk
3
+ - type: fk
4
+ name: webhook_integration_id
5
+ isNullable: true
6
+ references:
7
+ table: webhook_integration
8
+ column: id
9
+ onDelete: set null
10
+ onUpdate: cascade
11
+ - name: public_uuid
12
+ type: varchar
13
+ length: 36
14
+ - name: status
15
+ type: enum
16
+ enum:
17
+ - success
18
+ - failed
19
+ - name: remote_ip
20
+ type: varchar
21
+ length: 127
22
+ isNullable: true
23
+ - name: duration_ms
24
+ type: int
25
+ default: 0
26
+ - name: error_message
27
+ type: text
28
+ isNullable: true
29
+ - name: payload_summary
30
+ type: json
31
+ isNullable: true
32
+ - name: payload_raw
33
+ type: json
34
+ isNullable: true
35
+ - name: response_summary
36
+ type: json
37
+ isNullable: true
38
+ - type: created_at
39
+ - type: updated_at
40
+ indices:
41
+ - columns:
42
+ - webhook_integration_id
43
+ - columns:
44
+ - public_uuid
45
+ - columns:
46
+ - status
47
+ - columns:
48
+ - created_at
49
+