create-windy 0.2.33 → 0.3.0

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 (284) hide show
  1. package/dist/cli.js +173 -17
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +9 -0
  6. package/template/README.md +3 -1
  7. package/template/apps/agent-server/Dockerfile +1 -0
  8. package/template/apps/agent-server/README.md +28 -5
  9. package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
  10. package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
  11. package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
  12. package/template/apps/agent-server/pyproject.toml +1 -1
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
  14. package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/app.py +63 -3
  18. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
  21. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
  22. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
  25. package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
  26. package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
  27. package/template/apps/agent-server/tests/test_api.py +16 -1
  28. package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
  29. package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
  30. package/template/apps/server/package.json +4 -1
  31. package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
  32. package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
  33. package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
  34. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  35. package/template/apps/server/src/agent/run-contracts.ts +2 -0
  36. package/template/apps/server/src/agent/run-facade.test.ts +52 -2
  37. package/template/apps/server/src/agent/run-facade.ts +38 -9
  38. package/template/apps/server/src/agent/tool-registry.ts +2 -6
  39. package/template/apps/server/src/application-services.ts +50 -0
  40. package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
  41. package/template/apps/server/src/audit/search-summary.ts +2 -6
  42. package/template/apps/server/src/auth/credential-restriction.ts +1 -0
  43. package/template/apps/server/src/auth/password.ts +30 -21
  44. package/template/apps/server/src/auth/routes.test.ts +44 -3
  45. package/template/apps/server/src/auth/routes.ts +6 -1
  46. package/template/apps/server/src/auth/runtime.ts +4 -1
  47. package/template/apps/server/src/auth/service.ts +25 -7
  48. package/template/apps/server/src/configuration/bootstrap.ts +20 -1
  49. package/template/apps/server/src/configuration/definition.test.ts +71 -0
  50. package/template/apps/server/src/configuration/definition.ts +242 -12
  51. package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
  52. package/template/apps/server/src/configuration/repository.ts +27 -2
  53. package/template/apps/server/src/configuration/routes.ts +4 -1
  54. package/template/apps/server/src/configuration/service.test.ts +100 -0
  55. package/template/apps/server/src/configuration/service.ts +50 -10
  56. package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
  57. package/template/apps/server/src/data-governance/export/service.ts +2 -6
  58. package/template/apps/server/src/example-modules.ts +4 -1
  59. package/template/apps/server/src/guards.ts +18 -5
  60. package/template/apps/server/src/http/request-source.test.ts +85 -0
  61. package/template/apps/server/src/http/request-source.ts +90 -0
  62. package/template/apps/server/src/index.ts +45 -45
  63. package/template/apps/server/src/license/routes.ts +2 -0
  64. package/template/apps/server/src/license/runtime-service.ts +10 -11
  65. package/template/apps/server/src/module-bootstrap.ts +2 -2
  66. package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
  67. package/template/apps/server/src/module-host/initialize.ts +30 -44
  68. package/template/apps/server/src/module-host/request-context.ts +29 -23
  69. package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
  70. package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
  71. package/template/apps/server/src/module-host/route-declaration.ts +45 -0
  72. package/template/apps/server/src/module-host/route-installer.ts +27 -3
  73. package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
  74. package/template/apps/server/src/module-host/storage-port.ts +134 -1
  75. package/template/apps/server/src/module-host/storage-scope.ts +89 -0
  76. package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
  77. package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
  78. package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
  79. package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
  80. package/template/apps/server/src/module-host/user-directory-port.ts +139 -0
  81. package/template/apps/server/src/module-host.ts +4 -0
  82. package/template/apps/server/src/notification/channel-adapter.ts +46 -0
  83. package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
  84. package/template/apps/server/src/notification/delivery-policy.ts +37 -0
  85. package/template/apps/server/src/notification/delivery-repository.ts +198 -0
  86. package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
  87. package/template/apps/server/src/notification/delivery-worker.ts +88 -0
  88. package/template/apps/server/src/notification/drizzle-management.ts +180 -0
  89. package/template/apps/server/src/notification/drizzle-query.ts +160 -0
  90. package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
  91. package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
  92. package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
  93. package/template/apps/server/src/notification/extension-routes.ts +111 -0
  94. package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
  95. package/template/apps/server/src/notification/memory-support.ts +72 -0
  96. package/template/apps/server/src/notification/preference-repository.ts +95 -0
  97. package/template/apps/server/src/notification/recipient-directory.ts +104 -0
  98. package/template/apps/server/src/notification/repository.test.ts +171 -0
  99. package/template/apps/server/src/notification/repository.ts +179 -32
  100. package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
  101. package/template/apps/server/src/notification/route-support.ts +225 -0
  102. package/template/apps/server/src/notification/routes.test.ts +182 -0
  103. package/template/apps/server/src/notification/routes.ts +225 -132
  104. package/template/apps/server/src/notification/service.test.ts +104 -0
  105. package/template/apps/server/src/notification/service.ts +140 -0
  106. package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
  107. package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
  108. package/template/apps/server/src/persistence.integration.test.ts +41 -2
  109. package/template/apps/server/src/route-guards.ts +1 -0
  110. package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
  111. package/template/apps/server/src/runtime-development.ts +2 -0
  112. package/template/apps/server/src/runtime.test.ts +23 -9
  113. package/template/apps/server/src/runtime.ts +39 -2
  114. package/template/apps/server/src/scheduler/audit.ts +2 -11
  115. package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
  116. package/template/apps/server/src/scheduler/definitions.ts +7 -0
  117. package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
  118. package/template/apps/server/src/scheduler/routes.ts +2 -7
  119. package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
  120. package/template/apps/server/src/search/opensearch-client.ts +145 -0
  121. package/template/apps/server/src/search/service.ts +2 -6
  122. package/template/apps/server/src/settings/routes.ts +15 -3
  123. package/template/apps/server/src/settings/runtime.ts +10 -1
  124. package/template/apps/server/src/storage/runtime.ts +13 -0
  125. package/template/apps/server/src/system/audit-query.ts +33 -4
  126. package/template/apps/server/src/system/audit-routes.test.ts +58 -4
  127. package/template/apps/server/src/system/audit-routes.ts +6 -5
  128. package/template/apps/server/src/system/built-in-roles.ts +1 -0
  129. package/template/apps/server/src/system/department-organization.ts +320 -0
  130. package/template/apps/server/src/system/department-routes.test.ts +207 -48
  131. package/template/apps/server/src/system/department-routes.ts +114 -33
  132. package/template/apps/server/src/system/drizzle-repository.ts +15 -7
  133. package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
  134. package/template/apps/server/src/system/drizzle-system.ts +37 -9
  135. package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
  136. package/template/apps/server/src/system/identity-route-config.ts +7 -2
  137. package/template/apps/server/src/system/mutation-runner.ts +23 -0
  138. package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
  139. package/template/apps/server/src/system/resource-access-response.ts +12 -6
  140. package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
  141. package/template/apps/server/src/system/route-helpers.ts +2 -5
  142. package/template/apps/server/src/system/route-types.ts +12 -3
  143. package/template/apps/server/src/system/routes-validation.test.ts +26 -0
  144. package/template/apps/server/src/system/routes.test.ts +10 -2
  145. package/template/apps/server/src/system/routes.ts +70 -36
  146. package/template/apps/server/src/system/seed.ts +7 -0
  147. package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
  148. package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
  149. package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
  150. package/template/apps/server/src/work-order/search-provider.ts +60 -1
  151. package/template/apps/server/src/work-order/service.ts +42 -10
  152. package/template/apps/web/src/app/error-pages.ts +9 -0
  153. package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
  154. package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
  155. package/template/apps/web/src/components/common/FormDialog.vue +16 -2
  156. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
  157. package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
  158. package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
  159. package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
  160. package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
  161. package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
  162. package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
  163. package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
  164. package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
  165. package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
  166. package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
  167. package/template/apps/web/src/lib/date-time.ts +22 -10
  168. package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
  169. package/template/apps/web/src/main.ts +14 -1
  170. package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
  171. package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
  172. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
  173. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
  174. package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
  175. package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
  176. package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
  177. package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
  178. package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
  179. package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
  180. package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
  181. package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
  182. package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
  183. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
  184. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
  185. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
  186. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
  187. package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
  188. package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
  189. package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
  190. package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
  191. package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
  192. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
  193. package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
  194. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
  195. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
  196. package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
  197. package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
  198. package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
  199. package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
  200. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
  201. package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
  202. package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
  203. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
  204. package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
  205. package/template/apps/web/src/pages/system/resource-config.ts +12 -4
  206. package/template/apps/web/src/router/error-navigation.ts +50 -0
  207. package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
  208. package/template/apps/web/src/router/index.ts +71 -0
  209. package/template/apps/web/src/router/index.webtest.ts +29 -0
  210. package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
  211. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  212. package/template/apps/web/src/services/auth-api.ts +5 -0
  213. package/template/apps/web/src/services/http.ts +43 -6
  214. package/template/apps/web/src/services/http.webtest.ts +70 -0
  215. package/template/apps/web/src/services/notification-api.ts +85 -2
  216. package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
  217. package/template/apps/web/src/services/search-api.ts +39 -0
  218. package/template/apps/web/src/services/system-api.ts +18 -0
  219. package/template/apps/web/vitest.config.ts +1 -0
  220. package/template/docker-compose.yml +68 -0
  221. package/template/docs/architecture/ai-egress.md +49 -7
  222. package/template/docs/architecture/ai-runtime.md +34 -6
  223. package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
  224. package/template/docs/architecture/object-storage.md +22 -2
  225. package/template/docs/architecture/search-provider-protocol.md +38 -2
  226. package/template/docs/development/custom-error-pages.md +78 -0
  227. package/template/docs/development/custom-login-page.md +3 -3
  228. package/template/docs/platform/agent-runtime.md +76 -7
  229. package/template/docs/platform/audit-reliability.md +46 -2
  230. package/template/docs/platform/business-user-directory.md +82 -0
  231. package/template/docs/platform/module-configuration.md +13 -4
  232. package/template/docs/platform/notifications.md +67 -11
  233. package/template/docs/platform/organization-membership.md +17 -3
  234. package/template/docs/platform/system-crud-api.md +16 -0
  235. package/template/docs/platform/web-system-crud.md +6 -2
  236. package/template/package.json +4 -0
  237. package/template/packages/config/index.test.ts +45 -0
  238. package/template/packages/config/package.json +1 -1
  239. package/template/packages/config/src/platform.ts +169 -48
  240. package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
  241. package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
  242. package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
  243. package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
  244. package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
  245. package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
  246. package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
  247. package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
  248. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  249. package/template/packages/database/package.json +1 -1
  250. package/template/packages/database/src/schema/identity.ts +7 -0
  251. package/template/packages/database/src/schema/notifications.ts +135 -0
  252. package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
  253. package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
  254. package/template/packages/modules/package.json +1 -1
  255. package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
  256. package/template/packages/modules/src/ai-operation-composition.ts +115 -0
  257. package/template/packages/modules/src/manifest.ts +26 -0
  258. package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
  259. package/template/packages/modules/src/system-api-permissions.ts +54 -0
  260. package/template/packages/modules/src/system-audit-actions.ts +15 -0
  261. package/template/packages/modules/src/system-features.ts +6 -1
  262. package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
  263. package/template/packages/modules/src/system-module-catalog.ts +28 -4
  264. package/template/packages/modules/src/system-modules.test.ts +13 -1
  265. package/template/packages/modules/src/system-modules.ts +12 -0
  266. package/template/packages/modules/src/system-permissions.ts +11 -5
  267. package/template/packages/server-sdk/index.ts +11 -0
  268. package/template/packages/server-sdk/package.json +3 -2
  269. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
  270. package/template/packages/server-sdk/src/module-host.ts +8 -0
  271. package/template/packages/server-sdk/src/request-context.ts +2 -0
  272. package/template/packages/server-sdk/src/storage-port.ts +66 -0
  273. package/template/packages/server-sdk/src/tool-registration.ts +6 -0
  274. package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
  275. package/template/packages/shared/package.json +1 -1
  276. package/template/packages/shared/src/audit.ts +25 -0
  277. package/template/packages/shared/src/module-configuration.ts +5 -0
  278. package/template/packages/shared/src/notification.ts +92 -1
  279. package/template/packages/shared/src/password.ts +7 -2
  280. package/template/packages/shared/src/platform-settings.ts +2 -0
  281. package/template/packages/storage/package.json +1 -1
  282. package/template/packages/storage/src/upload-service.test.ts +16 -0
  283. package/template/packages/storage/src/upload-service.ts +5 -1
  284. package/template/scripts/notification-postgres-regression.ts +27 -0
@@ -0,0 +1,210 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, shallowRef, watch } from "vue";
3
+ import { Input } from "@/components/ui/input";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { listSystemResource, type SystemRecord } from "@/services/system-api";
12
+
13
+ const props = defineProps<{
14
+ item?: SystemRecord;
15
+ defaults: Record<string, unknown>;
16
+ }>();
17
+
18
+ const emit = defineEmits<{
19
+ submit: [payload: Record<string, unknown>];
20
+ }>();
21
+
22
+ const form = shallowRef<Record<string, unknown>>({});
23
+ const departments = shallowRef<SystemRecord[]>([]);
24
+ const loading = shallowRef(false);
25
+ const error = shallowRef("");
26
+ const transitionTargetId = shallowRef<string>();
27
+ const isRoot = computed(() => Boolean(props.item && !props.item.parentId));
28
+ const isDisabling = computed(
29
+ () =>
30
+ props.item?.status === "active" &&
31
+ form.value.status === "disabled" &&
32
+ !isRoot.value,
33
+ );
34
+ const parentOptions = computed(() =>
35
+ departments.value.filter(
36
+ (department) =>
37
+ department.id !== props.item?.id &&
38
+ !isDescendant(department.id, props.item?.id),
39
+ ),
40
+ );
41
+ const migrationOptions = computed(() =>
42
+ departments.value.filter(
43
+ (department) =>
44
+ department.id !== props.item?.id &&
45
+ !isDescendant(department.id, props.item?.id),
46
+ ),
47
+ );
48
+
49
+ watch(
50
+ () => [props.item, props.defaults] as const,
51
+ () => {
52
+ form.value = {
53
+ ...props.defaults,
54
+ ...(props.item || {}),
55
+ status: props.item?.status || props.defaults.status || "active",
56
+ };
57
+ transitionTargetId.value = undefined;
58
+ },
59
+ { immediate: true },
60
+ );
61
+
62
+ onMounted(() => void loadDepartments());
63
+
64
+ async function loadDepartments() {
65
+ loading.value = true;
66
+ error.value = "";
67
+ try {
68
+ const result = await listSystemResource("departments", {
69
+ page: 1,
70
+ pageSize: 100,
71
+ status: "active",
72
+ sort: "order,name",
73
+ });
74
+ departments.value = result.items;
75
+ } catch (currentError) {
76
+ error.value =
77
+ currentError instanceof Error
78
+ ? currentError.message
79
+ : String(currentError);
80
+ } finally {
81
+ loading.value = false;
82
+ }
83
+ }
84
+
85
+ function update(key: string, value: unknown) {
86
+ form.value = { ...form.value, [key]: value };
87
+ }
88
+
89
+ function submit() {
90
+ emit("submit", {
91
+ ...form.value,
92
+ ...(isDisabling.value
93
+ ? { _transitionTargetDepartmentId: transitionTargetId.value }
94
+ : {}),
95
+ });
96
+ }
97
+
98
+ function isDescendant(candidateId: string, ancestorId: unknown): boolean {
99
+ if (typeof ancestorId !== "string") return false;
100
+ const byId = new Map(
101
+ departments.value.map((department) => [department.id, department]),
102
+ );
103
+ const visited = new Set<string>();
104
+ let current = byId.get(candidateId);
105
+ while (typeof current?.parentId === "string" && !visited.has(current.id)) {
106
+ visited.add(current.id);
107
+ if (current.parentId === ancestorId) return true;
108
+ current = byId.get(current.parentId);
109
+ }
110
+ return false;
111
+ }
112
+
113
+ defineExpose({ submit });
114
+ </script>
115
+
116
+ <template>
117
+ <div class="space-y-4">
118
+ <label class="grid gap-2 text-sm font-medium">
119
+ <span>部门编码</span>
120
+ <Input
121
+ :model-value="String(form.code || '')"
122
+ placeholder="例如 sales"
123
+ @update:model-value="update('code', String($event))"
124
+ />
125
+ </label>
126
+ <label class="grid gap-2 text-sm font-medium">
127
+ <span>部门名称</span>
128
+ <Input
129
+ :model-value="String(form.name || '')"
130
+ placeholder="例如 销售部"
131
+ @update:model-value="update('name', String($event))"
132
+ />
133
+ </label>
134
+ <label class="grid gap-2 text-sm font-medium">
135
+ <span>上级部门</span>
136
+ <Select
137
+ :model-value="typeof form.parentId === 'string' ? form.parentId : ''"
138
+ :disabled="loading || isRoot"
139
+ @update:model-value="update('parentId', String($event))"
140
+ >
141
+ <SelectTrigger class="w-full" aria-label="上级部门">
142
+ <SelectValue
143
+ :placeholder="
144
+ isRoot
145
+ ? '平台唯一根组织'
146
+ : loading
147
+ ? '正在加载…'
148
+ : '请选择上级部门'
149
+ "
150
+ />
151
+ </SelectTrigger>
152
+ <SelectContent>
153
+ <SelectItem
154
+ v-for="department in parentOptions"
155
+ :key="department.id"
156
+ :value="department.id"
157
+ >
158
+ {{ String(department.name || department.code) }}
159
+ </SelectItem>
160
+ </SelectContent>
161
+ </Select>
162
+ </label>
163
+ <label class="grid gap-2 text-sm font-medium">
164
+ <span>排序</span>
165
+ <Input
166
+ type="number"
167
+ min="0"
168
+ :model-value="String(form.order ?? 10)"
169
+ @update:model-value="update('order', Number($event))"
170
+ />
171
+ </label>
172
+ <label v-if="item" class="grid gap-2 text-sm font-medium">
173
+ <span>状态</span>
174
+ <Select
175
+ :model-value="String(form.status || 'active')"
176
+ :disabled="isRoot"
177
+ @update:model-value="update('status', String($event))"
178
+ >
179
+ <SelectTrigger class="w-full" aria-label="部门状态">
180
+ <SelectValue />
181
+ </SelectTrigger>
182
+ <SelectContent>
183
+ <SelectItem value="active">启用</SelectItem>
184
+ <SelectItem value="disabled">停用</SelectItem>
185
+ </SelectContent>
186
+ </Select>
187
+ </label>
188
+ <label v-if="isDisabling" class="grid gap-2 text-sm font-medium">
189
+ <span>关联数据迁移至</span>
190
+ <Select v-model="transitionTargetId">
191
+ <SelectTrigger class="w-full" aria-label="关联数据迁移目标">
192
+ <SelectValue placeholder="请选择启用部门" />
193
+ </SelectTrigger>
194
+ <SelectContent>
195
+ <SelectItem
196
+ v-for="department in migrationOptions"
197
+ :key="department.id"
198
+ :value="department.id"
199
+ >
200
+ {{ String(department.name || department.code) }}
201
+ </SelectItem>
202
+ </SelectContent>
203
+ </Select>
204
+ <span class="text-xs font-normal text-muted-foreground">
205
+ 下级部门、用户归属和角色自定义部门范围会一起迁移。
206
+ </span>
207
+ </label>
208
+ <p v-if="error" class="text-sm text-destructive">{{ error }}</p>
209
+ </div>
210
+ </template>
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ import { computed, shallowRef, watch } from "vue";
3
+ import FormDialog from "@/components/common/FormDialog.vue";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { listSystemResource, type SystemRecord } from "@/services/system-api";
12
+
13
+ const open = defineModel<boolean>("open", { default: false });
14
+ const props = defineProps<{
15
+ department?: SystemRecord;
16
+ loading?: boolean;
17
+ }>();
18
+ const emit = defineEmits<{
19
+ confirm: [targetDepartmentId: string];
20
+ }>();
21
+
22
+ const targetDepartmentId = shallowRef<string>();
23
+ const departments = shallowRef<SystemRecord[]>([]);
24
+ const error = shallowRef("");
25
+ const targets = computed(() =>
26
+ departments.value.filter(
27
+ (department) =>
28
+ department.id !== props.department?.id &&
29
+ !isDescendant(department.id, props.department?.id),
30
+ ),
31
+ );
32
+
33
+ watch(open, (value) => {
34
+ if (!value) return;
35
+ targetDepartmentId.value = undefined;
36
+ void loadDepartments();
37
+ });
38
+
39
+ async function loadDepartments() {
40
+ error.value = "";
41
+ try {
42
+ const result = await listSystemResource("departments", {
43
+ page: 1,
44
+ pageSize: 100,
45
+ status: "active",
46
+ sort: "order,name",
47
+ });
48
+ departments.value = result.items;
49
+ } catch (currentError) {
50
+ error.value =
51
+ currentError instanceof Error
52
+ ? currentError.message
53
+ : String(currentError);
54
+ }
55
+ }
56
+
57
+ function submit() {
58
+ if (targetDepartmentId.value) emit("confirm", targetDepartmentId.value);
59
+ }
60
+
61
+ function isDescendant(candidateId: string, ancestorId: unknown): boolean {
62
+ if (typeof ancestorId !== "string") return false;
63
+ const byId = new Map(
64
+ departments.value.map((department) => [department.id, department]),
65
+ );
66
+ const visited = new Set<string>();
67
+ let current = byId.get(candidateId);
68
+ while (typeof current?.parentId === "string" && !visited.has(current.id)) {
69
+ visited.add(current.id);
70
+ if (current.parentId === ancestorId) return true;
71
+ current = byId.get(current.parentId);
72
+ }
73
+ return false;
74
+ }
75
+ </script>
76
+
77
+ <template>
78
+ <FormDialog
79
+ v-model:open="open"
80
+ title="删除部门"
81
+ description="删除前会把下级部门、用户归属和角色自定义部门范围迁移到指定部门。"
82
+ submit-text="迁移并删除"
83
+ :loading="loading"
84
+ :submit-disabled="!department?.parentId || !targetDepartmentId"
85
+ @submit="submit"
86
+ >
87
+ <p
88
+ v-if="department && !department.parentId"
89
+ class="text-sm text-destructive"
90
+ >
91
+ 平台唯一根组织不能删除。
92
+ </p>
93
+ <label v-else class="grid gap-2 text-sm font-medium">
94
+ <span>迁移目标</span>
95
+ <Select v-model="targetDepartmentId">
96
+ <SelectTrigger class="w-full" aria-label="删除部门迁移目标">
97
+ <SelectValue placeholder="请选择启用部门" />
98
+ </SelectTrigger>
99
+ <SelectContent>
100
+ <SelectItem
101
+ v-for="department in targets"
102
+ :key="department.id"
103
+ :value="department.id"
104
+ >
105
+ {{ String(department.name || department.code) }}
106
+ </SelectItem>
107
+ </SelectContent>
108
+ </Select>
109
+ </label>
110
+ <p v-if="error" class="text-sm text-destructive">{{ error }}</p>
111
+ </FormDialog>
112
+ </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { onMounted, shallowRef, watch } from "vue";
3
3
  import { PlusIcon } from "@lucide/vue";
4
+ import { PASSWORD_POLICY, type PasswordPolicy } from "@southwind-ai/shared";
4
5
  import { Button } from "@/components/ui/button";
5
6
  import { Input } from "@/components/ui/input";
6
7
  import {
@@ -16,6 +17,7 @@ import {
16
17
  quickCreateDepartment,
17
18
  type SystemRecord,
18
19
  } from "@/services/system-api";
20
+ import { loadPasswordPolicy } from "@/services/auth-api";
19
21
  import UserRoleSelector from "./UserRoleSelector.vue";
20
22
 
21
23
  interface DepartmentOption {
@@ -45,6 +47,7 @@ const quickName = shallowRef("");
45
47
  const quickCode = shallowRef("");
46
48
  const quickParentId = shallowRef<string | undefined>();
47
49
  const governedTouched = shallowRef<Record<string, boolean>>({});
50
+ const passwordPolicy = shallowRef<PasswordPolicy>(PASSWORD_POLICY);
48
51
 
49
52
  watch(
50
53
  () => [props.item, props.defaults] as const,
@@ -56,9 +59,17 @@ watch(
56
59
  );
57
60
 
58
61
  onMounted(() => {
59
- void loadDepartments();
62
+ void Promise.all([loadDepartments(), loadRuntimePasswordPolicy()]);
60
63
  });
61
64
 
65
+ async function loadRuntimePasswordPolicy() {
66
+ try {
67
+ passwordPolicy.value = await loadPasswordPolicy();
68
+ } catch {
69
+ // Server validation remains authoritative; keep the packaged safe default.
70
+ }
71
+ }
72
+
62
73
  function update(key: string, value: unknown) {
63
74
  if (key === "email" || key === "phone") {
64
75
  governedTouched.value = { ...governedTouched.value, [key]: true };
@@ -244,7 +255,9 @@ defineExpose({ submit });
244
255
  type="password"
245
256
  autocomplete="new-password"
246
257
  :model-value="String(form.temporaryPassword || '')"
247
- placeholder="至少 12 个字符"
258
+ :minlength="passwordPolicy.minLength"
259
+ :maxlength="passwordPolicy.maxLength"
260
+ :placeholder="`${passwordPolicy.minLength}-${passwordPolicy.maxLength} 个字符`"
248
261
  @update:model-value="update('temporaryPassword', String($event))"
249
262
  />
250
263
  <span class="text-xs font-normal text-muted-foreground">
@@ -281,7 +294,7 @@ defineExpose({ submit });
281
294
  </div>
282
295
  <Select v-model="quickParentId">
283
296
  <SelectTrigger class="w-full" aria-label="上级部门">
284
- <SelectValue placeholder="上级部门(可选)" />
297
+ <SelectValue placeholder="请选择上级部门" />
285
298
  </SelectTrigger>
286
299
  <SelectContent>
287
300
  <SelectItem
@@ -296,7 +309,7 @@ defineExpose({ submit });
296
309
  <Button
297
310
  type="button"
298
311
  size="sm"
299
- :disabled="creating"
312
+ :disabled="creating || !quickParentId"
300
313
  @click="createDepartment"
301
314
  >
302
315
  {{ creating ? "创建中…" : "创建并选中" }}
@@ -0,0 +1,79 @@
1
+ import { shallowRef } from "vue";
2
+ import type { SystemRecord } from "@/services/system-api";
3
+ import { transitionDepartment } from "@/services/system-api";
4
+ import { useAppToast } from "@/composables/useAppToast";
5
+
6
+ export function useDepartmentTransitions(options: {
7
+ reload: () => Promise<void>;
8
+ mutated: () => void;
9
+ }) {
10
+ const loading = shallowRef(false);
11
+ const toast = useAppToast();
12
+
13
+ async function disable(
14
+ department: SystemRecord,
15
+ targetDepartmentId: string | undefined,
16
+ ): Promise<boolean> {
17
+ if (!targetDepartmentId) {
18
+ toast.error("请选择迁移目标");
19
+ return false;
20
+ }
21
+ return transition(
22
+ department,
23
+ "disable",
24
+ targetDepartmentId,
25
+ "部门已停用,关联数据已迁移",
26
+ );
27
+ }
28
+
29
+ async function remove(
30
+ department: SystemRecord,
31
+ targetDepartmentId: string | undefined,
32
+ ): Promise<boolean> {
33
+ if (!department.parentId) {
34
+ toast.error("平台唯一根组织不能删除");
35
+ return false;
36
+ }
37
+ if (!targetDepartmentId) {
38
+ toast.error("请选择迁移目标");
39
+ return false;
40
+ }
41
+ return transition(
42
+ department,
43
+ "delete",
44
+ targetDepartmentId,
45
+ "部门已删除,关联数据已迁移",
46
+ );
47
+ }
48
+
49
+ async function transition(
50
+ department: SystemRecord,
51
+ action: "disable" | "delete",
52
+ targetDepartmentId: string,
53
+ successMessage: string,
54
+ ): Promise<boolean> {
55
+ loading.value = true;
56
+ try {
57
+ await transitionDepartment(department.id, {
58
+ action,
59
+ targetDepartmentId,
60
+ });
61
+ await options.reload();
62
+ toast.success(successMessage);
63
+ options.mutated();
64
+ return true;
65
+ } catch (currentError) {
66
+ toast.error(action === "disable" ? "部门停用失败" : "部门删除失败", {
67
+ description:
68
+ currentError instanceof Error
69
+ ? currentError.message
70
+ : String(currentError),
71
+ });
72
+ return false;
73
+ } finally {
74
+ loading.value = false;
75
+ }
76
+ }
77
+
78
+ return { loading, disable, remove };
79
+ }
@@ -0,0 +1,14 @@
1
+ import { computed } from "vue";
2
+ import { useAccessSnapshot } from "@/composables/useAccessSnapshot";
3
+
4
+ export function useNotificationPermissions() {
5
+ const access = useAccessSnapshot();
6
+ const canUpdate = computed(
7
+ () =>
8
+ access.snapshot.value?.actor.permissionKeys.includes(
9
+ "system.notification.update",
10
+ ) === true,
11
+ );
12
+
13
+ return { canUpdate };
14
+ }
@@ -33,9 +33,11 @@ export function useSystemResource(options: UseSystemResourceOptions) {
33
33
  const q = shallowRef("");
34
34
  const status = shallowRef("");
35
35
  const auditFilters = shallowRef<{
36
+ q?: string;
36
37
  actorId?: string;
37
38
  action?: string;
38
39
  occurredAfter?: string;
40
+ occurredBefore?: string;
39
41
  }>({});
40
42
  const exportOptions = shallowRef<{ includeScheduler?: boolean }>({});
41
43
  const loading = shallowRef(false);
@@ -34,9 +34,15 @@ describe("useSystemResource 审计导出", () => {
34
34
  apiResource: "audit-logs",
35
35
  readonly: true,
36
36
  });
37
+ resource.setAuditFilters({
38
+ q: "案件甲",
39
+ actorId: "user_1",
40
+ action: "case.review",
41
+ occurredAfter: "2026-07-20T01:15:00.000Z",
42
+ occurredBefore: "2026-07-20T01:20:59.999Z",
43
+ });
37
44
  resource.setExportOptions({ includeScheduler: true });
38
45
 
39
- await resource.reload();
40
46
  await resource.exportCurrent("audit-logs.json");
41
47
 
42
48
  expect(api.list).toHaveBeenCalledWith(
@@ -45,7 +51,14 @@ describe("useSystemResource 审计导出", () => {
45
51
  );
46
52
  expect(api.exportCurrent).toHaveBeenCalledWith(
47
53
  "audit-logs",
48
- expect.objectContaining({ includeScheduler: true }),
54
+ expect.objectContaining({
55
+ q: "案件甲",
56
+ actorId: "user_1",
57
+ action: "case.review",
58
+ occurredAfter: "2026-07-20T01:15:00.000Z",
59
+ occurredBefore: "2026-07-20T01:20:59.999Z",
60
+ includeScheduler: true,
61
+ }),
49
62
  );
50
63
  });
51
64
  });
@@ -15,7 +15,12 @@ export interface ResourceColumn {
15
15
  label: string;
16
16
  descriptionKey?: string;
17
17
  headerDescription?: string;
18
- format?: "license-policy" | "date-time" | "audit-action" | "governed-value";
18
+ format?:
19
+ | "license-policy"
20
+ | "date-time"
21
+ | "audit-action"
22
+ | "audit-ip"
23
+ | "governed-value";
19
24
  }
20
25
 
21
26
  export interface ResourceField {
@@ -111,7 +116,8 @@ export const systemResourceConfigs: Record<
111
116
  key: "departments",
112
117
  apiResource: "departments",
113
118
  title: "部门组织",
114
- description: "维护组织部门编码、名称、排序和启停状态。",
119
+ description:
120
+ "维护唯一根组织下的部门树;停用或删除前会迁移下级、用户归属和角色数据范围。",
115
121
  searchPlaceholder: "搜索部门编码或名称",
116
122
  columns: [
117
123
  { key: "code", label: "编码" },
@@ -124,7 +130,8 @@ export const systemResourceConfigs: Record<
124
130
  { key: "name", label: "名称", placeholder: "例如 销售部" },
125
131
  { key: "order", label: "排序", type: "number" },
126
132
  ],
127
- createDefaults: () => ({ order: 10 }),
133
+ importable: false,
134
+ createDefaults: () => ({ order: 10, status: "active" }),
128
135
  },
129
136
  menus: {
130
137
  key: "menus",
@@ -219,11 +226,12 @@ export const systemResourceConfigs: Record<
219
226
  apiResource: "audit-logs",
220
227
  title: "审计日志",
221
228
  description: "查看 Guard、功能开关、CRUD 和业务操作产生的审计事件。",
222
- searchPlaceholder: "使用操作者、动作和时间筛选",
229
+ searchPlaceholder: "搜索审计事实",
223
230
  columns: [
224
231
  { key: "action", label: "动作", format: "audit-action" },
225
232
  { key: "outcome", label: "结果" },
226
233
  { key: "actor", label: "操作者" },
234
+ { key: "ip", label: "IP", format: "audit-ip" },
227
235
  { key: "occurredAt", label: "时间", format: "date-time" },
228
236
  ],
229
237
  fields: [],
@@ -0,0 +1,50 @@
1
+ import type { Router } from "vue-router";
2
+ import type { ApiFailure } from "@/services/http";
3
+ import { safeReturnTo } from "./auth-navigation";
4
+
5
+ const errorRouteNames = {
6
+ internal: "internal-error",
7
+ maintenance: "maintenance",
8
+ network: "network-error",
9
+ } as const;
10
+
11
+ export function installApiFailureNavigation(router: Router) {
12
+ return (failure: ApiFailure): void => {
13
+ if (router.currentRoute.value.meta.errorPage === true) return;
14
+ const query: Record<string, string> = {
15
+ from: safeErrorReturnTo(router.currentRoute.value.fullPath),
16
+ };
17
+ if (failure.requestId) query.requestId = failure.requestId;
18
+ scheduleErrorNavigation(router, {
19
+ name: errorRouteNames[failure.kind],
20
+ query,
21
+ });
22
+ };
23
+ }
24
+
25
+ export function installUnexpectedErrorNavigation(router: Router) {
26
+ return (from?: string): void => {
27
+ if (router.currentRoute.value.meta.errorPage === true) return;
28
+ scheduleErrorNavigation(router, {
29
+ name: "internal-error",
30
+ query: {
31
+ from: safeErrorReturnTo(from || router.currentRoute.value.fullPath),
32
+ },
33
+ });
34
+ };
35
+ }
36
+
37
+ export function safeErrorReturnTo(value: string): string {
38
+ const target = safeReturnTo(value);
39
+ return target.startsWith("/error/") ? "/" : target;
40
+ }
41
+
42
+ function scheduleErrorNavigation(
43
+ router: Router,
44
+ target: Parameters<Router["replace"]>[0],
45
+ ): void {
46
+ setTimeout(() => {
47
+ if (router.currentRoute.value.meta.errorPage === true) return;
48
+ void router.replace(target);
49
+ }, 0);
50
+ }