@hed-hog/core 0.0.370 → 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 (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 +141 -29
  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 +8 -3
  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
@@ -68,7 +68,7 @@ import {
68
68
  IconX,
69
69
  } from '@tabler/icons-react';
70
70
  import { useTranslations } from 'next-intl';
71
- import { useState } from 'react';
71
+ import { useMemo, useState } from 'react';
72
72
  import { toast } from 'sonner';
73
73
 
74
74
  interface Component {
@@ -111,6 +111,8 @@ interface RoleOption {
111
111
  name?: string;
112
112
  }
113
113
 
114
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
115
+
114
116
  export function ComponentsTab() {
115
117
  const t = useTranslations('core.DashboardManagement');
116
118
  const { request, locales, currentLocaleCode } = useApp();
@@ -133,10 +135,40 @@ export function ComponentsTab() {
133
135
  useState<Component | null>(null);
134
136
  const [selectedRoleIds, setSelectedRoleIds] = useState<number[]>([]);
135
137
  const [page, setPage] = useState(1);
138
+ const { data: generalSettings } = useQuery<{
139
+ data: Array<{ slug: string; value: string }>;
140
+ }>({
141
+ queryKey: ['setting-group-general'],
142
+ queryFn: async () => {
143
+ const response = await request<{
144
+ data: Array<{ slug: string; value: string }>;
145
+ }>({ url: '/setting/group/general', method: 'GET' });
146
+ return response.data;
147
+ },
148
+ staleTime: 5 * 60 * 1000,
149
+ });
150
+
151
+ const pageSizeOptions = useMemo(() => {
152
+ const setting = generalSettings?.data?.find(
153
+ (s) => s.slug === 'pagination-page-sizes'
154
+ );
155
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
156
+ try {
157
+ const parsed = JSON.parse(setting.value) as string[];
158
+ const sizes = parsed
159
+ .map(Number)
160
+ .filter((n) => !isNaN(n) && n > 0)
161
+ .sort((a, b) => a - b);
162
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
163
+ } catch {
164
+ return DEFAULT_PAGE_SIZES;
165
+ }
166
+ }, [generalSettings]);
167
+
136
168
  const [pageSize, setPageSize] = usePersistedPageSize({
137
169
  storageKey: 'pagination:global:pageSize',
138
- defaultValue: 10,
139
- allowedValues: [10, 20, 30, 50],
170
+ defaultValue: 12,
171
+ allowedValues: pageSizeOptions,
140
172
  });
141
173
  const [searchQuery, setSearchQuery] = useState('');
142
174
  const [roleSearchQuery, setRoleSearchQuery] = useState('');
@@ -909,7 +941,7 @@ export function ComponentsTab() {
909
941
  <SelectValue />
910
942
  </SelectTrigger>
911
943
  <SelectContent>
912
- {[10, 20, 30, 50].map((size) => (
944
+ {pageSizeOptions.map((size) => (
913
945
  <SelectItem key={size} value={String(size)}>
914
946
  {size}
915
947
  </SelectItem>
@@ -133,6 +133,8 @@ type RequestError = {
133
133
  };
134
134
  };
135
135
 
136
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
137
+
136
138
  export function DashboardRolesTab() {
137
139
  const t = useTranslations('core.DashboardManagement');
138
140
  const { request, currentLocaleCode, getSettingValue } = useApp();
@@ -146,10 +148,40 @@ export function DashboardRolesTab() {
146
148
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
147
149
  const [roleToDelete, setRoleToDelete] = useState<number | null>(null);
148
150
  const [page, setPage] = useState(1);
151
+ const { data: generalSettings } = useQuery<{
152
+ data: Array<{ slug: string; value: string }>;
153
+ }>({
154
+ queryKey: ['setting-group-general'],
155
+ queryFn: async () => {
156
+ const response = await request<{
157
+ data: Array<{ slug: string; value: string }>;
158
+ }>({ url: '/setting/group/general', method: 'GET' });
159
+ return response.data;
160
+ },
161
+ staleTime: 5 * 60 * 1000,
162
+ });
163
+
164
+ const pageSizeOptions = useMemo(() => {
165
+ const setting = generalSettings?.data?.find(
166
+ (s) => s.slug === 'pagination-page-sizes'
167
+ );
168
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
169
+ try {
170
+ const parsed = JSON.parse(setting.value) as string[];
171
+ const sizes = parsed
172
+ .map(Number)
173
+ .filter((n) => !isNaN(n) && n > 0)
174
+ .sort((a, b) => a - b);
175
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
176
+ } catch {
177
+ return DEFAULT_PAGE_SIZES;
178
+ }
179
+ }, [generalSettings]);
180
+
149
181
  const [pageSize, setPageSize] = usePersistedPageSize({
150
182
  storageKey: 'pagination:global:pageSize',
151
- defaultValue: 10,
152
- allowedValues: [10, 20, 30, 50],
183
+ defaultValue: 12,
184
+ allowedValues: pageSizeOptions,
153
185
  });
154
186
  const searchQuery = '';
155
187
  const debouncedSearch = useDebounce(searchQuery, 500);
@@ -539,7 +571,7 @@ export function DashboardRolesTab() {
539
571
  <SelectValue />
540
572
  </SelectTrigger>
541
573
  <SelectContent>
542
- {[10, 20, 30, 50].map((size) => (
574
+ {pageSizeOptions.map((size) => (
543
575
  <SelectItem key={size} value={String(size)}>
544
576
  {size}
545
577
  </SelectItem>
@@ -67,7 +67,7 @@ import {
67
67
  IconX,
68
68
  } from '@tabler/icons-react';
69
69
  import { useTranslations } from 'next-intl';
70
- import { useState } from 'react';
70
+ import { useMemo, useState } from 'react';
71
71
  import { toast } from 'sonner';
72
72
 
73
73
  interface Dashboard {
@@ -98,6 +98,8 @@ interface RoleOption {
98
98
  name?: string;
99
99
  }
100
100
 
101
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
102
+
101
103
  export function DashboardsTab() {
102
104
  const t = useTranslations('core.DashboardManagement');
103
105
  const { request, locales, currentLocaleCode } = useApp();
@@ -120,10 +122,40 @@ export function DashboardsTab() {
120
122
  useState<Dashboard | null>(null);
121
123
  const [selectedRoleIds, setSelectedRoleIds] = useState<number[]>([]);
122
124
  const [page, setPage] = useState(1);
125
+ const { data: generalSettings } = useQuery<{
126
+ data: Array<{ slug: string; value: string }>;
127
+ }>({
128
+ queryKey: ['setting-group-general'],
129
+ queryFn: async () => {
130
+ const response = await request<{
131
+ data: Array<{ slug: string; value: string }>;
132
+ }>({ url: '/setting/group/general', method: 'GET' });
133
+ return response.data;
134
+ },
135
+ staleTime: 5 * 60 * 1000,
136
+ });
137
+
138
+ const pageSizeOptions = useMemo(() => {
139
+ const setting = generalSettings?.data?.find(
140
+ (s) => s.slug === 'pagination-page-sizes'
141
+ );
142
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
143
+ try {
144
+ const parsed = JSON.parse(setting.value) as string[];
145
+ const sizes = parsed
146
+ .map(Number)
147
+ .filter((n) => !isNaN(n) && n > 0)
148
+ .sort((a, b) => a - b);
149
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
150
+ } catch {
151
+ return DEFAULT_PAGE_SIZES;
152
+ }
153
+ }, [generalSettings]);
154
+
123
155
  const [pageSize, setPageSize] = usePersistedPageSize({
124
156
  storageKey: 'pagination:global:pageSize',
125
- defaultValue: 10,
126
- allowedValues: [10, 20, 30, 50],
157
+ defaultValue: 12,
158
+ allowedValues: pageSizeOptions,
127
159
  });
128
160
  const [searchQuery, setSearchQuery] = useState('');
129
161
  const [roleSearchQuery, setRoleSearchQuery] = useState('');
@@ -653,7 +685,7 @@ export function DashboardsTab() {
653
685
  <SelectValue />
654
686
  </SelectTrigger>
655
687
  <SelectContent>
656
- {[10, 20, 30, 50].map((size) => (
688
+ {pageSizeOptions.map((size) => (
657
689
  <SelectItem key={size} value={String(size)}>
658
690
  {size}
659
691
  </SelectItem>
@@ -127,6 +127,8 @@ type ComponentListResponse = {
127
127
  data?: Component[];
128
128
  };
129
129
 
130
+ const DEFAULT_PAGE_SIZES = [6, 12, 24, 48, 96] as const;
131
+
130
132
  export function ItemsTab() {
131
133
  const t = useTranslations('core.DashboardManagement');
132
134
  const { request, currentLocaleCode, getSettingValue } = useApp();
@@ -144,10 +146,40 @@ export function ItemsTab() {
144
146
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
145
147
  const [itemToDelete, setItemToDelete] = useState<number | null>(null);
146
148
  const [page, setPage] = useState(1);
149
+ const { data: generalSettings } = useQuery<{
150
+ data: Array<{ slug: string; value: string }>;
151
+ }>({
152
+ queryKey: ['setting-group-general'],
153
+ queryFn: async () => {
154
+ const response = await request<{
155
+ data: Array<{ slug: string; value: string }>;
156
+ }>({ url: '/setting/group/general', method: 'GET' });
157
+ return response.data;
158
+ },
159
+ staleTime: 5 * 60 * 1000,
160
+ });
161
+
162
+ const pageSizeOptions = useMemo(() => {
163
+ const setting = generalSettings?.data?.find(
164
+ (s) => s.slug === 'pagination-page-sizes'
165
+ );
166
+ if (!setting?.value) return DEFAULT_PAGE_SIZES;
167
+ try {
168
+ const parsed = JSON.parse(setting.value) as string[];
169
+ const sizes = parsed
170
+ .map(Number)
171
+ .filter((n) => !isNaN(n) && n > 0)
172
+ .sort((a, b) => a - b);
173
+ return sizes.length > 0 ? sizes : DEFAULT_PAGE_SIZES;
174
+ } catch {
175
+ return DEFAULT_PAGE_SIZES;
176
+ }
177
+ }, [generalSettings]);
178
+
147
179
  const [pageSize, setPageSize] = usePersistedPageSize({
148
180
  storageKey: 'pagination:global:pageSize',
149
- defaultValue: 10,
150
- allowedValues: [10, 20, 30, 50],
181
+ defaultValue: 12,
182
+ allowedValues: pageSizeOptions,
151
183
  });
152
184
  const searchQuery = '';
153
185
  const debouncedSearch = useDebounce(searchQuery, 500);
@@ -647,7 +679,7 @@ export function ItemsTab() {
647
679
  <SelectValue />
648
680
  </SelectTrigger>
649
681
  <SelectContent>
650
- {[10, 20, 30, 50].map((size) => (
682
+ {pageSizeOptions.map((size) => (
651
683
  <SelectItem key={size} value={String(size)}>
652
684
  {size}
653
685
  </SelectItem>
@@ -19,6 +19,7 @@ import {
19
19
  AlertDialogHeader,
20
20
  AlertDialogTitle,
21
21
  } from '@/components/ui/alert-dialog';
22
+ import { DoNodePoolPicker } from '@/components/integration-profile/do-node-pool-picker';
22
23
  import { Avatar, AvatarFallback } from '@/components/ui/avatar';
23
24
  import { Badge } from '@/components/ui/badge';
24
25
  import { Button } from '@/components/ui/button';
@@ -166,6 +167,9 @@ type ImportValidationResponse = {
166
167
 
167
168
  // ─── Provider field definitions ───────────────────────────────────────────────
168
169
 
170
+ /** Placeholder devolvido pelo backend no lugar de um segredo armazenado. */
171
+ const SECRET_MASK = '********';
172
+
169
173
  const PROVIDER_FIELDS: Record<string, FieldDef[]> = {
170
174
  smtp: [
171
175
  {
@@ -653,6 +657,61 @@ const PROVIDER_FIELDS: Record<string, FieldDef[]> = {
653
657
  placeholder: 'https://eu.altcha.org/api/v1',
654
658
  },
655
659
  ],
660
+ // O node pool de vídeo NÃO é um campo de texto: é escolhido por nome via picker
661
+ // dedicado (carrega os pools da DO API com o token + cluster_id). Ver DoNodePoolPicker.
662
+ digitalocean: [
663
+ {
664
+ key: 'api_token',
665
+ labelKey: 'fieldApiToken',
666
+ type: 'password',
667
+ required: true,
668
+ colSpan: true,
669
+ },
670
+ {
671
+ key: 'cluster_id',
672
+ labelKey: 'fieldClusterId',
673
+ type: 'text',
674
+ required: true,
675
+ },
676
+ {
677
+ key: 'region',
678
+ labelKey: 'fieldRegion',
679
+ type: 'text',
680
+ required: true,
681
+ placeholder: 'nyc1',
682
+ },
683
+ ],
684
+ kubernetes: [
685
+ {
686
+ key: 'api_server',
687
+ labelKey: 'fieldApiServer',
688
+ type: 'url',
689
+ required: true,
690
+ colSpan: true,
691
+ placeholder: 'https://10.0.0.1:6443',
692
+ },
693
+ {
694
+ key: 'token',
695
+ labelKey: 'fieldToken',
696
+ type: 'password',
697
+ required: true,
698
+ colSpan: true,
699
+ },
700
+ {
701
+ key: 'ca_cert',
702
+ labelKey: 'fieldCaCert',
703
+ type: 'password',
704
+ required: false,
705
+ colSpan: true,
706
+ },
707
+ {
708
+ key: 'namespace',
709
+ labelKey: 'fieldNamespace',
710
+ type: 'text',
711
+ required: false,
712
+ placeholder: 'hcode',
713
+ },
714
+ ],
656
715
  };
657
716
 
658
717
  // ─── Helpers ─────────────────────────────────────────────────────────────────
@@ -1075,6 +1134,10 @@ export default function IntegrationProfilesPage() {
1075
1134
  {};
1076
1135
  const providerSlug =
1077
1136
  providers.find((p) => p.id === data.provider_id)?.slug ?? '';
1137
+ // Segredos chegam mascarados (********) do backend e PERMANECEM mascarados no
1138
+ // form: o campo fica desabilitado mostrando ********; o botão "trocar" limpa e
1139
+ // reabilita para digitar um novo valor. Em branco ou mascarado, o backend
1140
+ // preserva o segredo armazenado.
1078
1141
  if (
1079
1142
  providerSlug.endsWith('-oauth') &&
1080
1143
  !Array.isArray(loadedConfig.callback_urls)
@@ -1844,6 +1907,15 @@ export default function IntegrationProfilesPage() {
1844
1907
 
1845
1908
  const isPassword = field.type === 'password';
1846
1909
  const isVisible = visibleFields[field.key] ?? false;
1910
+ const currentValue = String(
1911
+ formData.config[field.key] ?? ''
1912
+ );
1913
+ // Segredo já armazenado: campo desabilitado mostrando ******** +
1914
+ // botão "trocar" que limpa o valor e reabilita para digitar um novo.
1915
+ const isLockedSecret =
1916
+ isPassword &&
1917
+ Boolean(editingProfile) &&
1918
+ currentValue === SECRET_MASK;
1847
1919
 
1848
1920
  return (
1849
1921
  <div
@@ -1865,49 +1937,76 @@ export default function IntegrationProfilesPage() {
1865
1937
  id={`ip-cfg-${field.key}`}
1866
1938
  type={
1867
1939
  isPassword
1868
- ? isVisible
1940
+ ? isVisible && !isLockedSecret
1869
1941
  ? 'text'
1870
1942
  : 'password'
1871
1943
  : field.type === 'number'
1872
1944
  ? 'number'
1873
1945
  : 'text'
1874
1946
  }
1875
- value={String(formData.config[field.key] ?? '')}
1947
+ value={currentValue}
1876
1948
  onChange={(e) =>
1877
1949
  updateConfig(field.key, e.target.value)
1878
1950
  }
1879
- placeholder={field.placeholder}
1880
- required={field.required}
1951
+ placeholder={
1952
+ isPassword && editingProfile
1953
+ ? t('leaveBlankToKeep')
1954
+ : field.placeholder
1955
+ }
1956
+ required={
1957
+ field.required &&
1958
+ !(isPassword && Boolean(editingProfile))
1959
+ }
1881
1960
  autoComplete={
1882
1961
  isPassword ? 'new-password' : undefined
1883
1962
  }
1884
1963
  className={isPassword ? 'pr-10' : undefined}
1964
+ disabled={isLockedSecret}
1885
1965
  />
1886
- {isPassword && (
1887
- <Button
1888
- type="button"
1889
- variant="ghost"
1890
- size="icon"
1891
- className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
1892
- onClick={() =>
1893
- setVisibleFields((p) => ({
1894
- ...p,
1895
- [field.key]: !p[field.key],
1896
- }))
1897
- }
1898
- aria-label={
1899
- isVisible
1900
- ? t('hidePassword')
1901
- : t('showPassword')
1902
- }
1903
- >
1904
- {isVisible ? (
1905
- <EyeOff className="h-4 w-4" />
1906
- ) : (
1907
- <Eye className="h-4 w-4" />
1908
- )}
1909
- </Button>
1910
- )}
1966
+ {isPassword &&
1967
+ (isLockedSecret ? (
1968
+ <Button
1969
+ type="button"
1970
+ variant="ghost"
1971
+ size="icon"
1972
+ className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
1973
+ onClick={() => {
1974
+ updateConfig(field.key, '');
1975
+ setVisibleFields((p) => ({
1976
+ ...p,
1977
+ [field.key]: true,
1978
+ }));
1979
+ }}
1980
+ aria-label={t('changeSecret')}
1981
+ title={t('changeSecret')}
1982
+ >
1983
+ <Pencil className="h-4 w-4" />
1984
+ </Button>
1985
+ ) : (
1986
+ <Button
1987
+ type="button"
1988
+ variant="ghost"
1989
+ size="icon"
1990
+ className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
1991
+ onClick={() =>
1992
+ setVisibleFields((p) => ({
1993
+ ...p,
1994
+ [field.key]: !p[field.key],
1995
+ }))
1996
+ }
1997
+ aria-label={
1998
+ isVisible
1999
+ ? t('hidePassword')
2000
+ : t('showPassword')
2001
+ }
2002
+ >
2003
+ {isVisible ? (
2004
+ <EyeOff className="h-4 w-4" />
2005
+ ) : (
2006
+ <Eye className="h-4 w-4" />
2007
+ )}
2008
+ </Button>
2009
+ ))}
1911
2010
  </div>
1912
2011
  </div>
1913
2012
  );
@@ -1916,6 +2015,19 @@ export default function IntegrationProfilesPage() {
1916
2015
  </div>
1917
2016
  )}
1918
2017
 
2018
+ {/* ── DigitalOcean: node pool de vídeo (escolha por nome via API) ── */}
2019
+ {selectedProvider?.slug === 'digitalocean' && (
2020
+ <DoNodePoolPicker
2021
+ apiToken={String(formData.config.api_token ?? '')}
2022
+ clusterId={String(formData.config.cluster_id ?? '')}
2023
+ value={String(formData.config.video_node_pool_name ?? '')}
2024
+ profileId={editingProfile?.id}
2025
+ onChange={(name) =>
2026
+ updateConfig('video_node_pool_name', name)
2027
+ }
2028
+ />
2029
+ )}
2030
+
1919
2031
  {/* ── Callback URLs (OAuth only) ── */}
1920
2032
  {isOAuthProvider && (
1921
2033
  <div className="rounded-lg border">