adminizer 4.6.0 → 4.7.0-build.221

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 (335) hide show
  1. package/Readme.md +12 -0
  2. package/assets/RelationDialogStack-CnHFOYyR.css +1 -0
  3. package/assets/RelationDialogStack-DI2bZxQX.js +20 -0
  4. package/assets/add-4213XScp.js +1 -0
  5. package/assets/add-group--F1XxfJ1.js +1 -0
  6. package/assets/add-group-form-DKRXC2n-.js +1 -0
  7. package/assets/add-user-b9HfMq7z.js +1 -0
  8. package/assets/add-user-form-UXAToWQd.js +1 -0
  9. package/assets/app-Clx8f5hd.css +1 -0
  10. package/assets/app-layout-C02ja4OI.js +1 -0
  11. package/assets/app-shell-D8wtkECO.js +1 -0
  12. package/assets/app-sidebar-BR5_zwxj.js +1 -0
  13. package/assets/app-sidebar-header-BZbIBQjq.js +1 -0
  14. package/assets/app.js +59 -7990
  15. package/assets/breadcrumbs-CODaqVkB.js +1 -0
  16. package/assets/catalog-6ZZjWdFs.js +6 -0
  17. package/assets/dashboard-CftAdKiH.js +3 -0
  18. package/assets/date-picker-DSOkNiNo.js +1 -0
  19. package/assets/del-modal-Cd4M7af8.js +1 -0
  20. package/assets/dynamic-controls-BsrJhPDY.js +1 -0
  21. package/assets/geo-json-CzR5JBXL.js +1 -0
  22. package/assets/handsontable-BI98OF5R.js +336 -0
  23. package/assets/history-DLHSU7Ak.js +1 -0
  24. package/assets/home-NcQ7nnU0.js +2 -0
  25. package/assets/icon-C2JNaLxH.js +1 -0
  26. package/assets/init-user-D2HSyXup.js +1 -0
  27. package/assets/input-error-CH2caTtO.js +1 -0
  28. package/assets/jsoneditor-DVNQmA6G.js +3869 -0
  29. package/assets/list-CFU0-TgN.js +4 -0
  30. package/assets/login-DFSLEO17.js +1 -0
  31. package/assets/manifest.json +246 -273
  32. package/assets/material-icon-Bx9pGQiQ.js +1 -0
  33. package/assets/media-manager-WcKe1EvD.js +6 -0
  34. package/assets/module-CT7PC6pc.js +1 -0
  35. package/assets/monaco-Bl4zdWCn.js +11 -0
  36. package/assets/nav-footer-BiSQkPt4.js +1 -0
  37. package/assets/nav-main-rTH0PfDw.js +1 -0
  38. package/assets/nav-user-Bj6cWs0a.js +1 -0
  39. package/assets/notification-Bi-PX87D.js +20 -0
  40. package/assets/pagination-render-AKJvTIRA.js +1 -0
  41. package/assets/register-BTP4uxsK.js +1 -0
  42. package/assets/ru-eW0WKkga.js +1 -0
  43. package/assets/theme-switcher-Dm8EOGzx.js +1 -0
  44. package/assets/toast-editor-DOg_gvSp.js +668 -0
  45. package/assets/user-filters-list-YUfR4XMI.js +1 -0
  46. package/assets/user-info-BgSbT02v.js +1 -0
  47. package/assets/user-menu-content-TQTXHIv6.js +1 -0
  48. package/assets/{vanilla-picker-l5rcX3cq.js → vanilla-picker-B3LgXoKV.js} +1 -7
  49. package/assets/welcome-vLwTu1R6.js +1 -0
  50. package/controllers/add.d.ts +1 -1
  51. package/controllers/add.js +20 -26
  52. package/controllers/addGroup.d.ts +1 -1
  53. package/controllers/addGroup.js +7 -15
  54. package/controllers/addUser.d.ts +1 -1
  55. package/controllers/addUser.js +10 -16
  56. package/controllers/catalog/Catalog.d.ts +1 -1
  57. package/controllers/catalog/Catalog.js +1 -10
  58. package/controllers/catalog/FrontendCatalogAdapter.d.ts +3 -3
  59. package/controllers/catalog/FrontendCatalogAdapter.js +0 -6
  60. package/controllers/catalog/FrontentCatalogAdapter.d.ts +3 -3
  61. package/controllers/catalog/FrontentCatalogAdapter.js +0 -4
  62. package/controllers/ckeditorUpload.js +2 -18
  63. package/controllers/dashboard.d.ts +1 -1
  64. package/controllers/dashboard.js +2 -5
  65. package/controllers/edit.js +24 -37
  66. package/controllers/editGroup.d.ts +1 -1
  67. package/controllers/editGroup.js +10 -21
  68. package/controllers/editUser.d.ts +1 -1
  69. package/controllers/editUser.js +11 -23
  70. package/controllers/exportData.d.ts +6 -0
  71. package/controllers/exportData.js +315 -0
  72. package/controllers/feed.d.ts +6 -0
  73. package/controllers/feed.js +73 -0
  74. package/controllers/feedback/feedbackController.d.ts +1 -1
  75. package/controllers/feedback/feedbackController.js +5 -7
  76. package/controllers/filter-fields/columns.d.ts +11 -0
  77. package/controllers/filter-fields/columns.js +106 -0
  78. package/controllers/filter-fields/filterFields.d.ts +10 -0
  79. package/controllers/filter-fields/filterFields.js +141 -0
  80. package/controllers/filter-fields/getAllUserFilters.d.ts +6 -0
  81. package/controllers/filter-fields/getAllUserFilters.js +115 -0
  82. package/controllers/filter-fields/groups.d.ts +5 -0
  83. package/controllers/filter-fields/groups.js +26 -0
  84. package/controllers/filter-fields/savedFilters.d.ts +25 -0
  85. package/controllers/filter-fields/savedFilters.js +317 -0
  86. package/controllers/getAllWidgets.d.ts +1 -1
  87. package/controllers/getAllWidgets.js +1 -9
  88. package/controllers/history-actions/HistoryController.js +6 -31
  89. package/controllers/initUser.js +6 -5
  90. package/controllers/inlineUpdate.d.ts +7 -0
  91. package/controllers/inlineUpdate.js +148 -0
  92. package/controllers/list.d.ts +1 -1
  93. package/controllers/list.js +383 -70
  94. package/controllers/listUserFilters.d.ts +5 -0
  95. package/controllers/listUserFilters.js +26 -0
  96. package/controllers/login.js +2 -2
  97. package/controllers/media-manager/mediaManagerApi.js +0 -9
  98. package/controllers/navigation/edit.js +3 -9
  99. package/controllers/notifications/NotificationController.d.ts +1 -1
  100. package/controllers/notifications/NotificationController.js +12 -36
  101. package/controllers/register.js +10 -9
  102. package/controllers/remove.js +10 -19
  103. package/controllers/timezones.d.ts +1 -1
  104. package/controllers/timezones.js +0 -6
  105. package/controllers/userApiKey.d.ts +7 -0
  106. package/controllers/userApiKey.js +36 -0
  107. package/controllers/view.js +14 -26
  108. package/controllers/welcome.d.ts +1 -1
  109. package/controllers/welcome.js +0 -4
  110. package/controllers/widgets/Action.d.ts +1 -1
  111. package/controllers/widgets/Action.js +1 -9
  112. package/controllers/widgets/Custom.d.ts +1 -1
  113. package/controllers/widgets/Custom.js +1 -9
  114. package/controllers/widgets/Info.d.ts +1 -1
  115. package/controllers/widgets/Info.js +1 -9
  116. package/controllers/widgets/filterInfo.d.ts +1 -0
  117. package/controllers/widgets/filterInfo.js +44 -0
  118. package/controllers/widgets/switch.d.ts +1 -1
  119. package/controllers/widgets/switch.js +1 -9
  120. package/controllers/widgetsDB.d.ts +1 -1
  121. package/controllers/widgetsDB.js +2 -8
  122. package/helpers/accessRightsHelper.d.ts +1 -1
  123. package/helpers/accessRightsHelper.js +5 -4
  124. package/helpers/apiKeyHelper.d.ts +5 -0
  125. package/helpers/apiKeyHelper.js +8 -0
  126. package/helpers/configHelper.js +12 -15
  127. package/helpers/controllerHelper.d.ts +14 -21
  128. package/helpers/controllerHelper.js +50 -83
  129. package/helpers/fieldsHelper.js +3 -3
  130. package/helpers/filterDatetimeHelper.d.ts +5 -0
  131. package/helpers/filterDatetimeHelper.js +84 -0
  132. package/helpers/inertiaActionsHelper.d.ts +2 -2
  133. package/helpers/inertiaActionsHelper.js +5 -4
  134. package/helpers/inertiaAddHelper.d.ts +3 -414
  135. package/helpers/inertiaAddHelper.js +39 -15
  136. package/helpers/inertiaGroupHelper.d.ts +2 -2
  137. package/helpers/inertiaGroupHelper.js +3 -3
  138. package/helpers/inertiaListHelper.d.ts +7 -3
  139. package/helpers/inertiaListHelper.js +26 -12
  140. package/helpers/inertiaMenuHelper.d.ts +1 -0
  141. package/helpers/inertiaMenuHelper.js +15 -3
  142. package/helpers/inertiaUserHelper.d.ts +3 -2
  143. package/helpers/inertiaUserHelper.js +7 -3
  144. package/helpers/menuHelper.d.ts +5 -2
  145. package/helpers/menuHelper.js +64 -12
  146. package/helpers/viteHelper.js +1 -2
  147. package/index.d.ts +6 -2
  148. package/index.js +6 -2
  149. package/interfaces/adminpanelConfig.d.ts +126 -39
  150. package/interfaces/internalModelAccess.d.ts +37 -0
  151. package/interfaces/internalModelAccess.js +1 -0
  152. package/interfaces/listQueryBuilder.d.ts +22 -0
  153. package/interfaces/listQueryBuilder.js +1 -0
  154. package/interfaces/queryCriteria.d.ts +54 -0
  155. package/interfaces/queryCriteria.js +1 -0
  156. package/interfaces/types.d.ts +1 -13
  157. package/lib/Adminizer.d.ts +4 -2
  158. package/lib/Adminizer.js +29 -10
  159. package/lib/DataAccessor.d.ts +8 -5
  160. package/lib/DataAccessor.js +89 -42
  161. package/lib/I18n.d.ts +5 -1
  162. package/lib/I18n.js +45 -19
  163. package/lib/MiddlewareManager.d.ts +8 -0
  164. package/lib/MiddlewareManager.js +53 -0
  165. package/lib/ai-assistant/OpenAiModelService.d.ts +3 -3
  166. package/lib/ai-assistant/OpenAiModelService.js +23 -24
  167. package/lib/catalog/Navigation.d.ts +1 -0
  168. package/lib/catalog/Navigation.js +12 -11
  169. package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -2
  170. package/lib/controls/markdown/ToastUiEditor.d.ts +1 -2
  171. package/lib/feedback/FeedbackHandler.js +2 -2
  172. package/lib/filters/ConditionValidator.d.ts +107 -0
  173. package/lib/filters/ConditionValidator.js +483 -0
  174. package/lib/filters/CustomFilterHandler.d.ts +122 -0
  175. package/lib/filters/CustomFilterHandler.js +131 -0
  176. package/lib/filters/FilterCustomFieldHandler.d.ts +2 -0
  177. package/lib/filters/FilterCustomFieldHandler.js +2 -0
  178. package/lib/filters/FilterService.d.ts +82 -0
  179. package/lib/filters/FilterService.js +323 -0
  180. package/lib/filters/index.d.ts +6 -0
  181. package/lib/filters/index.js +4 -0
  182. package/lib/history-actions/AbstractHistoryAdapter.d.ts +5 -4
  183. package/lib/history-actions/AbstractHistoryAdapter.js +45 -30
  184. package/lib/history-actions/DefaultHistoryAdapter.d.ts +2 -0
  185. package/lib/history-actions/DefaultHistoryAdapter.js +12 -6
  186. package/lib/history-actions/HistoryHandler.js +1 -1
  187. package/lib/list-query-builder/ListQueryBuilder.d.ts +117 -0
  188. package/lib/list-query-builder/ListQueryBuilder.js +707 -0
  189. package/lib/media-manager/AbstractMediaManager.d.ts +9 -8
  190. package/lib/media-manager/DefaultMediaManager.d.ts +2 -0
  191. package/lib/media-manager/DefaultMediaManager.js +27 -15
  192. package/lib/media-manager/Items.d.ts +4 -1
  193. package/lib/media-manager/Items.js +62 -59
  194. package/lib/media-manager/MediaManagerHandler.js +1 -1
  195. package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -1
  196. package/lib/media-manager/helpers/MediaManagerHelper.js +38 -4
  197. package/lib/model/AbstractModel.d.ts +33 -25
  198. package/lib/model/AbstractModel.js +54 -2
  199. package/lib/model/InternalModelAccessFactory.d.ts +18 -0
  200. package/lib/model/InternalModelAccessFactory.js +82 -0
  201. package/lib/model/ModelHandler.d.ts +6 -0
  202. package/lib/model/ModelHandler.js +14 -0
  203. package/lib/model/adapter/sequelize.d.ts +32 -10
  204. package/lib/model/adapter/sequelize.js +409 -62
  205. package/lib/model/adapter/typeorm.d.ts +62 -0
  206. package/lib/model/adapter/typeorm.js +777 -0
  207. package/lib/model/internalModelAccessToken.d.ts +1 -0
  208. package/lib/model/internalModelAccessToken.js +1 -0
  209. package/lib/model/internalModelRepository.d.ts +3 -0
  210. package/lib/model/internalModelRepository.js +4 -0
  211. package/lib/notifications/AbstractNotificationService.d.ts +3 -0
  212. package/lib/notifications/AbstractNotificationService.js +42 -36
  213. package/lib/notifications/GeneralNotificationService.js +37 -40
  214. package/lib/notifications/SystemNotificationService.js +33 -36
  215. package/lib/requestProcessor.js +0 -3
  216. package/lib/ui-i18n/getUiTranslations.d.ts +1 -0
  217. package/lib/ui-i18n/getUiTranslations.js +3 -0
  218. package/lib/ui-i18n/uiTranslationKeys.d.ts +6 -0
  219. package/lib/ui-i18n/uiTranslationKeys.js +268 -0
  220. package/lib/widgets/widgetHandler.d.ts +7 -2
  221. package/lib/widgets/widgetHandler.js +98 -18
  222. package/models/FilterAP.d.ts +116 -0
  223. package/models/FilterAP.js +68 -0
  224. package/models/FilterColumnAP.d.ts +32 -0
  225. package/models/FilterColumnAP.js +21 -0
  226. package/models/GroupAP.d.ts +1 -1
  227. package/models/HistoryActionsAP.d.ts +3 -3
  228. package/models/MediaManagerMetaAP.d.ts +1 -1
  229. package/models/NavigationAP.d.ts +1 -1
  230. package/models/UserAP.d.ts +6 -1
  231. package/models/UserAP.js +4 -0
  232. package/package.json +39 -32
  233. package/policies/authPolicies.d.ts +12 -0
  234. package/policies/authPolicies.js +104 -0
  235. package/policies/permissionResolvers.d.ts +12 -0
  236. package/policies/permissionResolvers.js +52 -0
  237. package/services/FeedService.d.ts +70 -0
  238. package/services/FeedService.js +334 -0
  239. package/system/Router.d.ts +1 -0
  240. package/system/Router.js +136 -59
  241. package/system/bindAccessRights.js +11 -30
  242. package/system/bindAuthorization.js +18 -18
  243. package/system/bindCors.js +16 -1
  244. package/system/bindCustomFilterHandlers.d.ts +2 -0
  245. package/system/bindCustomFilterHandlers.js +4 -0
  246. package/system/bindInertia.js +18 -61
  247. package/system/bindReqFunctions.js +10 -2
  248. package/system/bindTranslations.js +7 -3
  249. package/system/buildInternalModelAccess.d.ts +4 -0
  250. package/system/buildInternalModelAccess.js +59 -0
  251. package/system/defaults.js +18 -14
  252. package/system/routePolicies.d.ts +2 -0
  253. package/system/routePolicies.js +29 -0
  254. package/translations/en.json +151 -39
  255. package/translations/ru.json +211 -64
  256. package/ui/components/handsontable.js +13 -27
  257. package/ui/components/ui/dialog-stack.js +1 -3
  258. package/ui/components/ui/popover.js +1 -1
  259. package/ui/components/ui/sidebar.js +1 -1
  260. package/assets/RelationDialogStack-D9UVEF9s.js +0 -407
  261. package/assets/RelationDialogStack-DV8OITPR.css +0 -4
  262. package/assets/add-BlvzWQKF.js +0 -1
  263. package/assets/add-group-1Uoq9NWe.js +0 -1
  264. package/assets/add-group-form-B01gwDGu.js +0 -1
  265. package/assets/add-user-BQCAsaRy.js +0 -1
  266. package/assets/add-user-form-Cgwc9JkI.js +0 -1
  267. package/assets/app-DaOMy1nl.css +0 -72
  268. package/assets/app-layout-CAm60qoE.js +0 -1
  269. package/assets/app-shell-QWCtn9ye.js +0 -1
  270. package/assets/app-sidebar-CfaYbnSy.js +0 -1
  271. package/assets/app-sidebar-header-BsPFDhZ8.js +0 -1
  272. package/assets/breadcrumbs-CZZirDj8.js +0 -1
  273. package/assets/catalog-k5myk7fk.js +0 -6
  274. package/assets/dashboard-BkL9ybBw.js +0 -3
  275. package/assets/date-picker-B5AZ_7Q9.js +0 -1
  276. package/assets/del-modal-BoyXZloG.js +0 -1
  277. package/assets/dynamic-controls-k61T9W2P.js +0 -1
  278. package/assets/form-1WGY0YgG.js +0 -1
  279. package/assets/form-DJapG3qF.js +0 -1
  280. package/assets/geo-json-DsMUHo0l.js +0 -4
  281. package/assets/handsontable-PxYdjN8c.js +0 -298
  282. package/assets/history-CluyR-yJ.js +0 -1
  283. package/assets/home-Dug9zBzM.js +0 -2
  284. package/assets/icon-DfDLMG5f.js +0 -1
  285. package/assets/init-user-CF80MEnT.js +0 -1
  286. package/assets/input-error-DuuqFgZa.js +0 -1
  287. package/assets/jsoneditor-De1FvcYu.js +0 -3882
  288. package/assets/list-CuexshNt.js +0 -1
  289. package/assets/list-table-BsEWjTKY.js +0 -22
  290. package/assets/login-eDemYzN-.js +0 -1
  291. package/assets/material-icon-DMT8KdmE.js +0 -1
  292. package/assets/media-manager-DfYa_mT_.js +0 -14
  293. package/assets/module-Cd4Tm6uq.js +0 -1
  294. package/assets/monaco-BhBYtg3d.js +0 -11
  295. package/assets/nav-footer-D3olYxtz.js +0 -1
  296. package/assets/nav-main-BctqyzeV.js +0 -1
  297. package/assets/nav-user-Bn1Lt-BZ.js +0 -1
  298. package/assets/notification-D64qvni-.js +0 -27
  299. package/assets/pagination-render-DxD78ahJ.js +0 -1
  300. package/assets/register-349PwYbe.js +0 -1
  301. package/assets/theme-switcher-DfExsHW2.js +0 -1
  302. package/assets/toast-editor-ESK5iWPx.js +0 -845
  303. package/assets/user-info-DtBK5Kh8.js +0 -1
  304. package/assets/user-menu-content-0XYOCMaA.js +0 -1
  305. package/assets/welcome-PDp-gUei.js +0 -1
  306. package/controllers/form.d.ts +0 -1
  307. package/controllers/form.js +0 -86
  308. package/helpers/formHelper.d.ts +0 -6
  309. package/helpers/formHelper.js +0 -38
  310. package/helpers/inertiaFromHelper.d.ts +0 -11
  311. package/helpers/inertiaFromHelper.js +0 -98
  312. package/helpers/runtimeMode.d.ts +0 -1
  313. package/helpers/runtimeMode.js +0 -3
  314. package/lib/PolicyManager.d.ts +0 -8
  315. package/lib/PolicyManager.js +0 -53
  316. package/lib/datatable/NodeTable.d.ts +0 -49
  317. package/lib/datatable/NodeTable.js +0 -207
  318. package/lib/model/adapter/waterline.d.ts +0 -32
  319. package/lib/model/adapter/waterline.js +0 -180
  320. package/migrations/index.d.ts +0 -3
  321. package/migrations/index.js +0 -4
  322. package/migrations/knex/20250106000001_adminizer_init.d.ts +0 -8
  323. package/migrations/knex/20250106000001_adminizer_init.js +0 -164
  324. package/migrations/umzug/0001.d.ts +0 -7
  325. package/migrations/umzug/0001.js +0 -121
  326. package/migrations/umzugExports.d.ts +0 -2
  327. package/migrations/umzugExports.js +0 -9
  328. package/system/bindForms.d.ts +0 -2
  329. package/system/bindForms.js +0 -20
  330. /package/{policies → middlewares}/addMessages.d.ts +0 -0
  331. /package/{policies → middlewares}/addMessages.js +0 -0
  332. /package/{policies → middlewares}/checkAuth.d.ts +0 -0
  333. /package/{policies → middlewares}/checkAuth.js +0 -0
  334. /package/{policies → middlewares}/setLocale.d.ts +0 -0
  335. /package/{policies → middlewares}/setLocale.js +0 -0
@@ -0,0 +1,317 @@
1
+ import { ControllerHelper } from "../../helpers/controllerHelper.js";
2
+ import { FilterService } from "../../lib/filters/FilterService.js";
3
+ import { Adminizer } from "../../lib/Adminizer.js";
4
+ import { GROUP_FILTER_VISIBILITY_TOKEN } from "../../policies/permissionResolvers.js";
5
+ /**
6
+ * POST /adminizer/model/:model/filter/apply
7
+ * Apply temporary filter (without saving) - stores filter in session keyed by model name
8
+ */
9
+ export async function applyTemporaryFilter(req, res) {
10
+ const modelResource = ControllerHelper.findModelResource(req);
11
+ if (!modelResource.model) {
12
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
13
+ }
14
+ const { name, conditions, columns } = req.body;
15
+ if (!conditions || !Array.isArray(conditions)) {
16
+ return res.status(400).send({ error: req.i18n.__('Conditions are required') });
17
+ }
18
+ // Store temporary filter in session keyed by model name
19
+ if (req.session) {
20
+ if (!req.session.temporaryFilters) {
21
+ req.session.temporaryFilters = {};
22
+ }
23
+ req.session.temporaryFilters[modelResource.name] = {
24
+ name: name || req.i18n.__('Temporary filter'),
25
+ conditions,
26
+ columns: columns || undefined
27
+ };
28
+ }
29
+ return res.json({
30
+ success: true,
31
+ filterId: 'temporary',
32
+ name: name || req.i18n.__('Temporary filter'),
33
+ conditionCount: conditions.length
34
+ });
35
+ }
36
+ /**
37
+ * GET /adminizer/model/:model/filter/temporary
38
+ * Returns temporary filter from server session.
39
+ */
40
+ export async function getTemporaryFilter(req, res) {
41
+ const modelResource = ControllerHelper.findModelResource(req);
42
+ if (!modelResource.model) {
43
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
44
+ }
45
+ const temporaryFilter = req.session?.temporaryFilters?.[modelResource.name];
46
+ if (!temporaryFilter) {
47
+ return res.status(404).send({ error: req.i18n.__('Temporary filter not found') });
48
+ }
49
+ return res.json({
50
+ filter: {
51
+ id: 'temporary',
52
+ name: temporaryFilter.name,
53
+ conditions: temporaryFilter.conditions || [],
54
+ columns: temporaryFilter.columns || undefined
55
+ }
56
+ });
57
+ }
58
+ /**
59
+ * GET /adminizer/model/:model/saved-filters
60
+ * Returns list of saved filters for the model
61
+ */
62
+ export async function getSavedFilters(req, res) {
63
+ const modelResource = ControllerHelper.findModelResource(req);
64
+ if (!modelResource.model) {
65
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
66
+ }
67
+ const filterService = new FilterService(req.adminizer);
68
+ // Get filters accessible by user (public filters for all users)
69
+ const filters = await filterService.getFiltersForModel(modelResource.name, req.user, {
70
+ includePublic: true,
71
+ includeSystem: false
72
+ });
73
+ // Load owner info for each filter
74
+ const userModel = req.adminizer.modelHandler.model.get('userap');
75
+ const filtersWithOwner = await Promise.all(filters.map(async (filter) => {
76
+ let ownerInfo = null;
77
+ if (userModel && filter.ownerId) {
78
+ try {
79
+ const owner = await userModel['_findOne']({ where: { id: filter.ownerId } });
80
+ if (owner) {
81
+ ownerInfo = {
82
+ id: owner.id,
83
+ login: owner.login,
84
+ fullName: owner.fullName
85
+ };
86
+ }
87
+ }
88
+ catch (e) {
89
+ // Ignore errors loading owner
90
+ }
91
+ }
92
+ return {
93
+ ...filter,
94
+ ownerInfo
95
+ };
96
+ }));
97
+ // Get result count for each filter
98
+ const filtersWithCount = await Promise.all(filtersWithOwner.map(async (filter) => {
99
+ const count = await filterService.countFilterResults(filter, req.user);
100
+ return {
101
+ ...filter,
102
+ resultCount: count
103
+ };
104
+ }));
105
+ return res.json({
106
+ model: modelResource.name,
107
+ filters: filtersWithCount
108
+ });
109
+ }
110
+ /**
111
+ * POST /adminizer/model/:model/filter
112
+ * Create or update a saved filter
113
+ */
114
+ export async function saveFilter(req, res) {
115
+ const modelResource = ControllerHelper.findModelResource(req);
116
+ if (!modelResource.model) {
117
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
118
+ }
119
+ const { name, description, conditions, sortField, sortDirection, icon, color, filterId, columns, visibility, groupIds } = req.body;
120
+ if (!name) {
121
+ return res.status(400).send({ error: req.i18n.__('Filter name is required') });
122
+ }
123
+ if (!conditions || !Array.isArray(conditions)) {
124
+ return res.status(400).send({ error: req.i18n.__('Filter conditions are required') });
125
+ }
126
+ // Determine visibility and groupIds
127
+ const isAdmin = req.user?.isAdministrator === true;
128
+ const canManageGroupVisibility = isAdmin || req.adminizer.accessRightsHelper.hasPermission(GROUP_FILTER_VISIBILITY_TOKEN, req.user);
129
+ let effectiveVisibility = 'private';
130
+ let effectiveGroupIds;
131
+ if (visibility === 'public' && isAdmin) {
132
+ effectiveVisibility = 'public';
133
+ }
134
+ else if (visibility === 'groups' && canManageGroupVisibility) {
135
+ // Groups visibility: only if groupIds provided and user has access to those groups
136
+ if (groupIds && Array.isArray(groupIds) && groupIds.length > 0) {
137
+ effectiveVisibility = 'groups';
138
+ effectiveGroupIds = groupIds;
139
+ }
140
+ else {
141
+ effectiveVisibility = 'private';
142
+ }
143
+ }
144
+ else {
145
+ effectiveVisibility = 'private';
146
+ }
147
+ const filterService = new FilterService(req.adminizer);
148
+ try {
149
+ // Check if filter with same name exists for this user
150
+ const existingFilters = await filterService.getFiltersForModel(modelResource.name, req.user, {
151
+ includePublic: false,
152
+ includeSystem: false
153
+ });
154
+ const existingFilter = existingFilters.find(f => f.name === name && f.ownerId === req.user.id);
155
+ if (existingFilter && !filterId) {
156
+ // Filter with same name exists, ask for confirmation
157
+ return res.status(409).json({
158
+ error: req.i18n.__('Filter with this name already exists'),
159
+ requiresConfirmation: true,
160
+ existingFilter: {
161
+ id: existingFilter.id,
162
+ name: existingFilter.name
163
+ }
164
+ });
165
+ }
166
+ let filter;
167
+ if (filterId && existingFilter && existingFilter.id === filterId) {
168
+ // Update own existing filter - allow changing visibility
169
+ const updateData = {
170
+ name,
171
+ description,
172
+ conditions,
173
+ sortField,
174
+ sortDirection,
175
+ icon,
176
+ color,
177
+ visibility: effectiveVisibility,
178
+ groupIds: effectiveGroupIds
179
+ };
180
+ // If visibility changed to non-private, disable API feed
181
+ const wasPrivate = existingFilter.visibility === 'private';
182
+ const isNowPrivate = effectiveVisibility === 'private';
183
+ if (wasPrivate && !isNowPrivate) {
184
+ // Changed from private to non-private - disable feed
185
+ updateData.apiEnabled = false;
186
+ updateData.apiKey = undefined;
187
+ }
188
+ else if (!isNowPrivate) {
189
+ // Not private - ensure feed is disabled
190
+ updateData.apiEnabled = false;
191
+ updateData.apiKey = undefined;
192
+ }
193
+ // Handle API access toggle - only allowed for private filters owned by current user
194
+ const apiEnabled = req.body.apiEnabled === true;
195
+ const isOwner = existingFilter.ownerId === req.user.id;
196
+ const isPrivate = effectiveVisibility === 'private';
197
+ // API feed can only be enabled for private filters owned by the current user
198
+ if (isPrivate && isOwner && apiEnabled) {
199
+ updateData.apiEnabled = true;
200
+ // Regenerate key if requested
201
+ if (req.body.regenerateApiKey) {
202
+ updateData.apiKey = crypto.randomUUID();
203
+ }
204
+ else if (!existingFilter.apiKey) {
205
+ // Generate key if enabling for first time
206
+ updateData.apiKey = crypto.randomUUID();
207
+ }
208
+ else {
209
+ // Keep existing key
210
+ updateData.apiKey = existingFilter.apiKey;
211
+ }
212
+ }
213
+ filter = await filterService.updateFilter(filterId, updateData, req.user);
214
+ }
215
+ else if (filterId) {
216
+ // Update someone else's filter - preserve original visibility
217
+ // First get the original filter to preserve its visibility settings
218
+ const originalFilter = await filterService.getFilterById(filterId, req.user);
219
+ if (!originalFilter) {
220
+ return res.status(404).json({ error: req.i18n.__('Filter not found') });
221
+ }
222
+ filter = await filterService.updateFilter(filterId, {
223
+ name,
224
+ description,
225
+ conditions,
226
+ sortField,
227
+ sortDirection,
228
+ icon,
229
+ color,
230
+ // Preserve original visibility - don't allow changing someone else's filter visibility
231
+ visibility: originalFilter.visibility,
232
+ groupIds: originalFilter.groupIds
233
+ }, req.user);
234
+ }
235
+ else {
236
+ // Create new filter
237
+ filter = await filterService.createFilter({
238
+ id: crypto.randomUUID(),
239
+ name,
240
+ description,
241
+ modelName: modelResource.name,
242
+ conditions,
243
+ sortField,
244
+ sortDirection,
245
+ icon,
246
+ color,
247
+ visibility: effectiveVisibility,
248
+ groupIds: effectiveGroupIds,
249
+ ownerId: req.user.id,
250
+ apiEnabled: false,
251
+ version: 1
252
+ }, req.user);
253
+ }
254
+ // Save columns if provided
255
+ if (columns && Array.isArray(columns)) {
256
+ const filterColumns = columns.map((col, index) => ({
257
+ fieldName: col.fieldName,
258
+ order: col.order !== undefined ? col.order : index
259
+ }));
260
+ await filterService.updateFilterColumns(filter.id, filterColumns, req.user);
261
+ }
262
+ // Get result count
263
+ const count = await filterService.countFilterResults(filter, req.user);
264
+ return res.json({
265
+ success: true,
266
+ filter: {
267
+ ...filter,
268
+ resultCount: count
269
+ }
270
+ });
271
+ }
272
+ catch (error) {
273
+ Adminizer.log.error('Error saving filter:', error);
274
+ return res.status(500).json({
275
+ error: req.i18n.__('Failed to save filter'),
276
+ message: error.message
277
+ });
278
+ }
279
+ }
280
+ /**
281
+ * DELETE /adminizer/model/:model/filter/:id
282
+ * Delete a saved filter
283
+ */
284
+ export async function deleteFilter(req, res) {
285
+ const modelResource = ControllerHelper.findModelResource(req);
286
+ if (!modelResource.model) {
287
+ return res.status(404).send({ error: req.i18n.__('Model not found') });
288
+ }
289
+ const filterId = Array.isArray(req.params.id) ? req.params.id[0] : String(req.params.id);
290
+ if (!filterId || filterId === 'undefined' || filterId === 'null') {
291
+ return res.status(400).send({ error: req.i18n.__('Filter ID is required') });
292
+ }
293
+ const filterService = new FilterService(req.adminizer);
294
+ try {
295
+ Adminizer.log.debug(`Deleting filter: ${filterId}`);
296
+ await filterService.deleteFilter(filterId, req.user);
297
+ // Return identical response to prevent filter existence/ownership enumeration.
298
+ return res.status(204).send();
299
+ }
300
+ catch (error) {
301
+ const errorMessage = typeof error?.message === 'string'
302
+ ? error.message.toLowerCase()
303
+ : '';
304
+ const isNotFound = errorMessage.includes('not found');
305
+ const isAccessDenied = errorMessage.includes('access denied');
306
+ // Fail closed without disclosing whether the filter exists or belongs to another user.
307
+ if (isNotFound || isAccessDenied) {
308
+ Adminizer.log.debug(`Filter delete suppressed for id=${filterId}: not found or access denied`);
309
+ return res.status(204).send();
310
+ }
311
+ Adminizer.log.error('Error deleting filter:', error);
312
+ return res.status(500).json({
313
+ error: req.i18n.__('Failed to delete filter'),
314
+ message: error.message
315
+ });
316
+ }
317
+ }
@@ -1 +1 @@
1
- export default function getAllWidgets(req: ReqType, res: ResType): Promise<void | ResType>;
1
+ export default function getAllWidgets(req: ReqType, res: ResType): Promise<ResType>;
@@ -1,13 +1,4 @@
1
- import { redirectToLogin } from '../helpers/inertiaAutHelper.js';
2
1
  export default async function getAllWidgets(req, res) {
3
- if (req.adminizer.config.auth.enable) {
4
- if (!req.user) {
5
- return redirectToLogin(req, res);
6
- }
7
- else if (!req.adminizer.accessRightsHelper.hasPermission(`widgets`, req.user)) {
8
- return res.sendStatus(403);
9
- }
10
- }
11
2
  if (req.method.toUpperCase() === 'GET') {
12
3
  try {
13
4
  return res.json({ widgets: await req.adminizer.widgetHandler.getAll(req.user, req.i18n) });
@@ -16,4 +7,5 @@ export default async function getAllWidgets(req, res) {
16
7
  return res.status(500).send({ error: e.message || 'Internal Server Error' });
17
8
  }
18
9
  }
10
+ return res.status(405).end();
19
11
  }
@@ -1,9 +1,9 @@
1
1
  import { Adminizer } from "../../lib/Adminizer.js";
2
- import { redirectToLogin } from '../../helpers/inertiaAutHelper.js';
2
+ import { getUiTranslations } from "../../lib/ui-i18n/getUiTranslations.js";
3
+ import { HISTORY_UI_TRANSLATION_KEYS } from "../../lib/ui-i18n/uiTranslationKeys.js";
3
4
  export class HistoryController {
4
5
  static async index(req, res) {
5
- const isUiRequest = req.method.toUpperCase() === 'GET';
6
- if (!HistoryController.checkHistoryPermission(req, res, isUiRequest))
6
+ if (!HistoryController.checkHistoryPermission(req, res))
7
7
  return;
8
8
  const adapter = HistoryController.getAdapter(req);
9
9
  if (req.method.toUpperCase() === 'GET') {
@@ -29,21 +29,7 @@ export class HistoryController {
29
29
  title
30
30
  };
31
31
  });
32
- const messages = {
33
- "Models": "",
34
- "All": "",
35
- "Users": "",
36
- "Date": "",
37
- "Pick a date": "",
38
- "Search": "",
39
- "Reset": "",
40
- "Model": "",
41
- "Name": "",
42
- "Event": "",
43
- "User": "",
44
- "The end of the list has been reached": "",
45
- "There are no records to display": "",
46
- };
32
+ const i18nPage = getUiTranslations(req, HISTORY_UI_TRANSLATION_KEYS);
47
33
  return req.Inertia.render({
48
34
  component: 'history',
49
35
  props: {
@@ -52,7 +38,7 @@ export class HistoryController {
52
38
  users: users.map((user) => ({
53
39
  name: user.login
54
40
  })),
55
- messages: Object.fromEntries(Object.keys(messages).map(key => [key, req.i18n.__(key)]))
41
+ i18nPage
56
42
  }
57
43
  });
58
44
  }
@@ -117,22 +103,11 @@ export class HistoryController {
117
103
  const adapter = req.adminizer.config.history?.adapter ?? 'default';
118
104
  return req.adminizer.historyHandler.get(adapter);
119
105
  }
120
- static checkHistoryPermission(req, res, shouldRedirectToLogin = false) {
106
+ static checkHistoryPermission(req, res) {
121
107
  if (!req.adminizer?.historyHandler) {
122
108
  res.status(401).json({ error: 'History system not initialized' });
123
109
  return false;
124
110
  }
125
- if (req.adminizer.config.auth.enable) {
126
- if (!req.user) {
127
- if (shouldRedirectToLogin) {
128
- redirectToLogin(req, res);
129
- }
130
- else {
131
- res.status(401).json({ error: 'Unauthorized' });
132
- }
133
- return false;
134
- }
135
- }
136
111
  const hasPermission = req.adminizer.accessRightsHelper.hasPermission(`history-${req.adminizer.config.history?.adapter ?? 'default'}`, req.user);
137
112
  if (!hasPermission) {
138
113
  res.sendStatus(403);
@@ -1,12 +1,13 @@
1
1
  import { Adminizer } from "../lib/Adminizer.js";
2
2
  import { generate } from "password-hash";
3
3
  import { inertiaInitUserHelper } from "../helpers/inertiaAutHelper.js";
4
+ import { generateUserApiKey } from "../helpers/apiKeyHelper.js";
4
5
  export default async function initUser(req, res) {
5
6
  if (!req.adminizer.config.auth.enable) {
6
7
  return res.redirect(`${req.adminizer.config.routePrefix}/`);
7
8
  }
8
- // TODO refactor CRUD functions for DataAccessor usage
9
- let admins = await req.adminizer.modelHandler.model.get("UserAP")["_find"]({ isAdministrator: true });
9
+ const userModel = req.adminizer.modelHandler.internal("auth").get("UserAP");
10
+ let admins = await userModel.find({ where: { isAdministrator: true } });
10
11
  if (admins.length) {
11
12
  res.redirect(`${req.adminizer.config.routePrefix}/model/userap/login`);
12
13
  }
@@ -31,14 +32,14 @@ export default async function initUser(req, res) {
31
32
  Adminizer.log.debug(`Created admin`);
32
33
  let passwordHashed = generate(login + password + process.env.AP_PASSWORD_SALT);
33
34
  password = 'masked';
34
- // TODO refactor CRUD functions for DataAccessor usage
35
- await req.adminizer.modelHandler.model.get("UserAP")["_create"]({
35
+ await userModel.create({
36
36
  login: login,
37
37
  passwordHashed: passwordHashed,
38
38
  fullName: "Administrator",
39
39
  isActive: true,
40
40
  ...(locale !== undefined && { locale }),
41
- isAdministrator: true
41
+ isAdministrator: true,
42
+ apiKey: generateUserApiKey()
42
43
  });
43
44
  }
44
45
  catch (e) {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Inline update controller for list view
3
+ * PATCH /adminizer/model/:name/inline/:id
4
+ *
5
+ * Body: { field: string, value: any }
6
+ */
7
+ export default function inlineUpdate(req: ReqType, res: ResType): Promise<ResType>;
@@ -0,0 +1,148 @@
1
+ import { ControllerHelper } from "../helpers/controllerHelper.js";
2
+ import { Adminizer } from "../lib/Adminizer.js";
3
+ import { DataAccessor } from "../lib/DataAccessor.js";
4
+ /**
5
+ * Inline update controller for list view
6
+ * PATCH /adminizer/model/:name/inline/:id
7
+ *
8
+ * Body: { field: string, value: any }
9
+ */
10
+ export default async function inlineUpdate(req, res) {
11
+ const allowedFieldTypes = new Set(['string', 'integer', 'number', 'range', 'boolean', 'float', 'email']);
12
+ // Check ID
13
+ if (!req.params.id) {
14
+ return res.status(400).json({ error: 'Record ID is required' });
15
+ }
16
+ // Check field
17
+ if (!req.body.field) {
18
+ return res.status(400).json({ error: 'Field name is required' });
19
+ }
20
+ const modelResource = ControllerHelper.findModelResource(req);
21
+ if (!modelResource.model) {
22
+ return res.status(404).json({ error: 'Model not found' });
23
+ }
24
+ const fieldName = req.body.field;
25
+ const newValue = req.body.value;
26
+ // Get field config
27
+ const dataAccessor = new DataAccessor(req.adminizer, req.user, modelResource, "edit");
28
+ const fields = dataAccessor.getFieldsConfig();
29
+ const fieldConfig = fields[fieldName];
30
+ if (!fieldConfig) {
31
+ return res.status(400).json({ error: `Field '${fieldName}' not found` });
32
+ }
33
+ const identifierField = modelResource.config.identifierField ||
34
+ req.adminizer.config.identifierField ||
35
+ modelResource.model.primaryKey ||
36
+ 'id';
37
+ const blockedInlineFields = new Set(['id', 'createdat', 'updatedat']);
38
+ if (blockedInlineFields.has(fieldName.toLowerCase()) || fieldName === identifierField) {
39
+ return res.status(403).json({ error: `Field '${fieldName}' cannot be edited inline` });
40
+ }
41
+ // Check if inline edit is explicitly disabled for this field
42
+ const baseConfig = fieldConfig.config;
43
+ if (baseConfig.inlineEditable === false) {
44
+ return res.status(403).json({ error: `Field '${fieldName}' is not inline editable` });
45
+ }
46
+ // Check if field has displayModifier (should not be editable)
47
+ if (baseConfig.displayModifier) {
48
+ return res.status(403).json({ error: `Field '${fieldName}' has displayModifier and cannot be edited` });
49
+ }
50
+ // Check if field type supports inline editing.
51
+ // Prefer explicit field config type, fallback to model metadata type.
52
+ const fieldType = (baseConfig.type || fieldConfig.model?.type || '').toLowerCase();
53
+ if (!fieldType || !allowedFieldTypes.has(fieldType)) {
54
+ return res.status(403).json({ error: `Field '${fieldName}' type does not support inline editing` });
55
+ }
56
+ // Validate value
57
+ const validationError = validateInlineValue(newValue, baseConfig, fieldType);
58
+ if (validationError) {
59
+ return res.status(400).json({ error: validationError });
60
+ }
61
+ // Convert value based on field type
62
+ const convertedValue = convertInlineValue(newValue, fieldType);
63
+ // Update record
64
+ const id = req.params.id;
65
+ const params = {};
66
+ params[modelResource.config.identifierField || req.adminizer.config.identifierField] = id;
67
+ try {
68
+ await modelResource.model.update(params, { [fieldName]: convertedValue }, dataAccessor);
69
+ return res.json({
70
+ success: true,
71
+ data: {
72
+ id,
73
+ field: fieldName,
74
+ value: convertedValue
75
+ }
76
+ });
77
+ }
78
+ catch (error) {
79
+ Adminizer.log.error('Inline update error:', error);
80
+ return res.status(500).json({ error: 'Failed to update record' });
81
+ }
82
+ }
83
+ /**
84
+ * Validate value according to inline validation rules
85
+ */
86
+ function validateInlineValue(value, config, fieldType) {
87
+ const validation = config.inlineValidation;
88
+ // String validations
89
+ if (typeof value === 'string') {
90
+ if (validation?.minLength && value.length < validation.minLength) {
91
+ return `Value must be at least ${validation.minLength} characters`;
92
+ }
93
+ if (validation?.maxLength && value.length > validation.maxLength) {
94
+ return `Value must be at most ${validation.maxLength} characters`;
95
+ }
96
+ if (validation?.pattern && !new RegExp(validation.pattern).test(value)) {
97
+ return `Value does not match required pattern`;
98
+ }
99
+ }
100
+ // Number validations
101
+ if (typeof value === 'number' || ['integer', 'number', 'float', 'range'].includes(fieldType || '')) {
102
+ const numValue = Number(value);
103
+ if (isNaN(numValue)) {
104
+ return 'Invalid number';
105
+ }
106
+ if (validation?.min !== undefined && numValue < validation.min) {
107
+ return `Value must be at least ${validation.min}`;
108
+ }
109
+ if (validation?.max !== undefined && numValue > validation.max) {
110
+ return `Value must be at most ${validation.max}`;
111
+ }
112
+ }
113
+ // Custom validation
114
+ if (validation?.validate) {
115
+ const result = validation.validate(value);
116
+ if (typeof result === 'string') {
117
+ return result;
118
+ }
119
+ if (result === false) {
120
+ return 'Invalid value';
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+ /**
126
+ * Convert value to appropriate type
127
+ */
128
+ function convertInlineValue(value, fieldType) {
129
+ if (value === null || value === undefined) {
130
+ return value;
131
+ }
132
+ switch (fieldType) {
133
+ case 'boolean':
134
+ return Boolean(value);
135
+ case 'integer':
136
+ return parseInt(value, 10);
137
+ case 'number':
138
+ case 'float':
139
+ return parseFloat(value);
140
+ case 'range':
141
+ return typeof value === 'string' ? parseFloat(value) : Number(value);
142
+ case 'email':
143
+ case 'string':
144
+ case 'text':
145
+ default:
146
+ return typeof value === 'string' ? value : String(value);
147
+ }
148
+ }
@@ -1 +1 @@
1
- export default function list(req: ReqType, res: ResType): Promise<void | ResType>;
1
+ export default function list(req: ReqType, res: ResType): Promise<import("express").Response<any, Record<string, any>>>;