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,74 @@
1
+ <script setup lang="ts">
2
+ import { computed, useTemplateRef } from "vue";
3
+ import type { PermissionDefinition } from "@southwind-ai/shared";
4
+ import FormDialog from "@/components/common/FormDialog.vue";
5
+ import type { SystemRecord } from "@/services/system-api";
6
+ import type { SystemResourceConfig } from "../resource-config";
7
+ import DepartmentOrganizationForm from "./department/DepartmentOrganizationForm.vue";
8
+ import RolePolicyForm from "./role-policy/RolePolicyForm.vue";
9
+ import SystemResourceForm from "./SystemResourceForm.vue";
10
+ import UserAccountForm from "./user-account/UserAccountForm.vue";
11
+
12
+ const open = defineModel<boolean>("open", { default: false });
13
+ const props = defineProps<{
14
+ config: SystemResourceConfig;
15
+ item?: SystemRecord;
16
+ defaults: Record<string, unknown>;
17
+ permissions?: PermissionDefinition[];
18
+ departmentCreateMode?: "create" | "manage";
19
+ loading?: boolean;
20
+ }>();
21
+ const emit = defineEmits<{
22
+ submit: [payload: Record<string, unknown>];
23
+ }>();
24
+
25
+ interface ResourceFormHandle {
26
+ submit(): void;
27
+ }
28
+
29
+ const resourceForm = useTemplateRef<ResourceFormHandle>("resourceForm");
30
+ const title = computed(() => (props.item ? "编辑记录" : "新增记录"));
31
+ </script>
32
+
33
+ <template>
34
+ <FormDialog
35
+ v-model:open="open"
36
+ :title="title"
37
+ :description="config.description"
38
+ :loading="loading"
39
+ :content-class="config.key === 'roles' ? 'sm:max-w-4xl' : undefined"
40
+ @submit="resourceForm?.submit()"
41
+ >
42
+ <RolePolicyForm
43
+ v-if="config.key === 'roles'"
44
+ ref="resourceForm"
45
+ :item="item"
46
+ :defaults="defaults"
47
+ :permissions="permissions || []"
48
+ @submit="emit('submit', $event)"
49
+ />
50
+ <UserAccountForm
51
+ v-else-if="config.key === 'users'"
52
+ ref="resourceForm"
53
+ :item="item"
54
+ :defaults="defaults"
55
+ :department-create-mode="departmentCreateMode"
56
+ @submit="emit('submit', $event)"
57
+ />
58
+ <DepartmentOrganizationForm
59
+ v-else-if="config.key === 'departments'"
60
+ ref="resourceForm"
61
+ :item="item"
62
+ :defaults="defaults"
63
+ @submit="emit('submit', $event)"
64
+ />
65
+ <SystemResourceForm
66
+ v-else
67
+ ref="resourceForm"
68
+ :fields="config.fields"
69
+ :item="item"
70
+ :defaults="defaults"
71
+ @submit="emit('submit', $event)"
72
+ />
73
+ </FormDialog>
74
+ </template>
@@ -8,9 +8,11 @@ import AuditLogFilters from "./AuditLogFilters.vue";
8
8
  // @windy-module system.audit end
9
9
 
10
10
  interface AuditLogFilterValue {
11
+ q?: string;
11
12
  actorId?: string;
12
13
  action?: string;
13
14
  occurredAfter?: string;
15
+ occurredBefore?: string;
14
16
  }
15
17
 
16
18
  interface AuditExportOptions {
@@ -35,10 +37,7 @@ const emit = defineEmits<{
35
37
  @export-options="emit('auditExportOptions', $event)"
36
38
  />
37
39
  <!-- @windy-module system.audit end -->
38
- <div
39
- v-if="!config.usesCustomFilters"
40
- class="flex flex-col gap-2 md:flex-row"
41
- >
40
+ <div v-if="!config.usesCustomFilters" class="flex flex-col gap-2 md:flex-row">
42
41
  <Input
43
42
  v-model="keyword"
44
43
  :placeholder="config.searchPlaceholder"
@@ -1,27 +1,29 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, shallowRef, useTemplateRef } from "vue";
2
+ import { computed, onMounted, shallowRef } from "vue";
3
3
  import type { PermissionDefinition } from "@southwind-ai/shared";
4
4
  import ConfirmDialog from "@/components/common/ConfirmDialog.vue";
5
5
  import DataPagination from "@/components/common/DataPagination.vue";
6
- import FormDialog from "@/components/common/FormDialog.vue";
7
6
  import { Button } from "@/components/ui/button";
8
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
9
- import { DownloadIcon, PlusIcon, RefreshCwIcon, UploadIcon } from "@lucide/vue";
10
8
  import type { SystemRecord } from "@/services/system-api";
11
9
  import { useSystemResource } from "../composables/useSystemResource";
12
10
  import { useAppToast } from "@/composables/useAppToast";
13
11
  import type { SystemResourceConfig } from "../resource-config";
14
- import SystemResourceForm from "./SystemResourceForm.vue";
15
12
  import SystemResourceTable from "./SystemResourceTable.vue";
16
13
  // @windy-module system.bulk-data begin
17
14
  import DictionaryBulkPanel from "./dictionary-bulk/DictionaryBulkPanel.vue";
18
15
  // @windy-module system.bulk-data end
19
- import RolePolicyForm from "./role-policy/RolePolicyForm.vue";
20
- import UserAccountForm from "./user-account/UserAccountForm.vue";
21
16
  import FeatureLicensePolicyDialog from "./FeatureLicensePolicyDialog.vue";
22
17
  import SystemResourceFilters from "./SystemResourceFilters.vue";
23
18
  import { useFeatureLicenseEditor } from "../composables/useFeatureLicenseEditor";
24
19
  import { saveUserAccount } from "./user-account/save-user-account";
20
+ import DepartmentTransitionDialog from "./department/DepartmentTransitionDialog.vue";
21
+ import { useDepartmentTransitions } from "../composables/useDepartmentTransitions";
22
+ import SystemResourceActions from "./SystemResourceActions.vue";
23
+ import SystemResourceEditor from "./SystemResourceEditor.vue";
24
+ // @windy-module system.audit begin
25
+ import AuditLogDetailDialog from "./AuditLogDetailDialog.vue";
26
+ // @windy-module system.audit end
25
27
 
26
28
  const props = withDefaults(
27
29
  defineProps<{
@@ -39,6 +41,7 @@ const props = withDefaults(
39
41
  // @windy-module system.bulk-data end
40
42
  },
41
43
  );
44
+ const auditViewable = props.config.apiResource === "audit-logs";
42
45
 
43
46
  const emit = defineEmits<{
44
47
  mutated: [];
@@ -57,10 +60,16 @@ const toast = useAppToast();
57
60
  const editorOpen = shallowRef(false);
58
61
  const confirmOpen = shallowRef(false);
59
62
  const editingItem = shallowRef<SystemRecord | undefined>();
63
+ const viewingItem = shallowRef<SystemRecord | undefined>();
64
+ const detailOpen = shallowRef(false);
60
65
  const pendingDelete = shallowRef<SystemRecord | undefined>();
61
66
  const keyword = shallowRef("");
62
67
  const pendingToggleId = shallowRef("");
63
68
  const toggleOverrides = shallowRef<Record<string, boolean>>({});
69
+ const departmentTransitions = useDepartmentTransitions({
70
+ reload: resource.reload,
71
+ mutated: () => emit("mutated"),
72
+ });
64
73
  const {
65
74
  open: licenseEditorOpen,
66
75
  item: licenseEditorItem,
@@ -76,17 +85,7 @@ const {
76
85
  onFailed: (description) =>
77
86
  toast.error("License 范围更新失败", { description }),
78
87
  });
79
- interface ResourceFormHandle {
80
- submit(): void;
81
- }
82
-
83
- const resourceForm = useTemplateRef<ResourceFormHandle>("resourceForm");
84
- const importFileInput = useTemplateRef<HTMLInputElement>("importFileInput");
85
-
86
88
  const formDefaults = computed(() => props.config.createDefaults());
87
- const dialogTitle = computed(() =>
88
- editingItem.value ? "编辑记录" : "新增记录",
89
- );
90
89
  const displayedItems = computed(() =>
91
90
  resource.items.value.map((item) => {
92
91
  const inlineToggle = props.config.inlineToggle;
@@ -115,16 +114,40 @@ function openDelete(item: SystemRecord) {
115
114
  confirmOpen.value = true;
116
115
  }
117
116
 
117
+ function openDetail(item: SystemRecord) {
118
+ viewingItem.value = item;
119
+ detailOpen.value = true;
120
+ }
121
+
118
122
  async function submit(payload: Record<string, unknown>) {
123
+ if (
124
+ props.config.key === "departments" &&
125
+ editingItem.value &&
126
+ editingItem.value.status === "active" &&
127
+ payload.status === "disabled"
128
+ ) {
129
+ const targetDepartmentId =
130
+ typeof payload._transitionTargetDepartmentId === "string"
131
+ ? payload._transitionTargetDepartmentId
132
+ : undefined;
133
+ const disabled = await departmentTransitions.disable(
134
+ editingItem.value,
135
+ targetDepartmentId,
136
+ );
137
+ if (disabled) editorOpen.value = false;
138
+ return;
139
+ }
140
+ const normalizedPayload = { ...payload };
141
+ delete normalizedPayload._transitionTargetDepartmentId;
119
142
  const saved =
120
143
  props.config.key === "users"
121
144
  ? await saveUserAccount(
122
- payload,
145
+ normalizedPayload,
123
146
  editingItem.value?.id,
124
147
  resource.save,
125
148
  toast,
126
149
  )
127
- : await resource.save(payload, editingItem.value?.id);
150
+ : await resource.save(normalizedPayload, editingItem.value?.id);
128
151
  if (saved) {
129
152
  editorOpen.value = false;
130
153
  emit("mutated");
@@ -151,8 +174,19 @@ async function toggleInline(item: SystemRecord, checked: boolean) {
151
174
  }
152
175
  }
153
176
 
154
- async function confirmDelete() {
177
+ async function confirmDelete(targetDepartmentId?: string) {
155
178
  if (!pendingDelete.value) return;
179
+ if (props.config.key === "departments") {
180
+ const deleted = await departmentTransitions.remove(
181
+ pendingDelete.value,
182
+ targetDepartmentId,
183
+ );
184
+ if (deleted) {
185
+ confirmOpen.value = false;
186
+ pendingDelete.value = undefined;
187
+ }
188
+ return;
189
+ }
156
190
  await resource.remove(pendingDelete.value.id);
157
191
  if (!resource.error.value) {
158
192
  confirmOpen.value = false;
@@ -170,24 +204,12 @@ function applySearchPreset(value: string) {
170
204
  applySearch();
171
205
  }
172
206
 
173
- function submitForm() {
174
- resourceForm.value?.submit();
175
- }
176
-
177
207
  function exportData() {
178
208
  void resource.exportCurrent(`${props.config.apiResource}.json`);
179
209
  }
180
210
 
181
- function openImport() {
182
- importFileInput.value?.click();
183
- }
184
-
185
- async function importData(event: Event) {
186
- const input = event.target as HTMLInputElement;
187
- const file = input.files?.[0];
188
- if (!file) return;
211
+ async function importData(file: File) {
189
212
  await resource.importFile(file);
190
- input.value = "";
191
213
  }
192
214
  </script>
193
215
 
@@ -206,40 +228,15 @@ async function importData(event: Event) {
206
228
  class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
207
229
  >
208
230
  <CardTitle class="text-base">数据列表</CardTitle>
209
- <div class="flex flex-wrap gap-2">
210
- <Button variant="outline" @click="resource.reload">
211
- <RefreshCwIcon data-icon="inline-start" />
212
- 刷新
213
- </Button>
214
- <Button
215
- v-if="!config.replacesNativeTransfer"
216
- variant="outline"
217
- @click="exportData"
218
- >
219
- <DownloadIcon data-icon="inline-start" />
220
- 导出
221
- </Button>
222
- <Button
223
- v-if="resource.canImport.value && !config.replacesNativeTransfer"
224
- variant="outline"
225
- @click="openImport"
226
- >
227
- <UploadIcon data-icon="inline-start" />
228
- 导入
229
- </Button>
230
- <Button v-if="resource.canCreate.value" @click="openCreate">
231
- <PlusIcon data-icon="inline-start" />
232
- 新增
233
- </Button>
234
- <input
235
- v-if="!config.replacesNativeTransfer"
236
- ref="importFileInput"
237
- type="file"
238
- accept="application/json,.json"
239
- class="hidden"
240
- @change="importData"
241
- />
242
- </div>
231
+ <SystemResourceActions
232
+ :can-create="resource.canCreate.value"
233
+ :can-import="resource.canImport.value"
234
+ :replaces-native-transfer="config.replacesNativeTransfer"
235
+ @refresh="resource.reload"
236
+ @export="exportData"
237
+ @import="importData"
238
+ @create="openCreate"
239
+ />
243
240
  </div>
244
241
  <SystemResourceFilters
245
242
  v-model:keyword="keyword"
@@ -270,12 +267,14 @@ async function importData(event: Event) {
270
267
  :inline-toggle="config.inlineToggle"
271
268
  :toggle-pending-id="pendingToggleId"
272
269
  :loading="resource.loading.value"
270
+ :viewable="auditViewable"
273
271
  :page="resource.page.value"
274
272
  :page-size="resource.pageSize.value"
275
273
  @edit="openEdit"
276
274
  @edit-license="openLicenseEditor"
277
275
  @remove="openDelete"
278
276
  @toggle="toggleInline"
277
+ @view="openDetail"
279
278
  />
280
279
  <DataPagination
281
280
  :page="resource.page.value"
@@ -305,42 +304,28 @@ async function importData(event: Event) {
305
304
  </p>
306
305
  </section>
307
306
  <!-- @windy-module system.bulk-data end -->
308
- <FormDialog
307
+ <SystemResourceEditor
309
308
  v-if="!config.inlineToggle"
310
309
  v-model:open="editorOpen"
311
- :title="dialogTitle"
312
- :description="config.description"
313
- :loading="resource.saving.value"
314
- :content-class="config.key === 'roles' ? 'sm:max-w-4xl' : undefined"
315
- @submit="submitForm"
316
- >
317
- <RolePolicyForm
318
- v-if="config.key === 'roles'"
319
- ref="resourceForm"
320
- :item="editingItem"
321
- :defaults="formDefaults"
322
- :permissions="permissions || []"
323
- @submit="submit"
324
- />
325
- <UserAccountForm
326
- v-else-if="config.key === 'users'"
327
- ref="resourceForm"
328
- :item="editingItem"
329
- :defaults="formDefaults"
330
- :department-create-mode="departmentCreateMode"
331
- @submit="submit"
332
- />
333
- <SystemResourceForm
334
- v-else
335
- ref="resourceForm"
336
- :fields="config.fields"
337
- :item="editingItem"
338
- :defaults="formDefaults"
339
- @submit="submit"
340
- />
341
- </FormDialog>
310
+ :config="config"
311
+ :item="editingItem"
312
+ :defaults="formDefaults"
313
+ :permissions="permissions"
314
+ :department-create-mode="departmentCreateMode"
315
+ :loading="resource.saving.value || departmentTransitions.loading.value"
316
+ @submit="submit"
317
+ />
318
+
319
+ <DepartmentTransitionDialog
320
+ v-if="config.key === 'departments'"
321
+ v-model:open="confirmOpen"
322
+ :department="pendingDelete"
323
+ :loading="departmentTransitions.loading.value"
324
+ @confirm="confirmDelete"
325
+ />
342
326
 
343
327
  <ConfirmDialog
328
+ v-else
344
329
  v-model:open="confirmOpen"
345
330
  title="删除记录"
346
331
  description="删除后会进入软删除状态,并写入审计事件。"
@@ -356,5 +341,13 @@ async function importData(event: Event) {
356
341
  :loading="resource.saving.value"
357
342
  @save="saveLicensePolicy"
358
343
  />
344
+
345
+ <!-- @windy-module system.audit begin -->
346
+ <AuditLogDetailDialog
347
+ v-if="config.key === 'auditLogs'"
348
+ v-model:open="detailOpen"
349
+ :item="viewingItem"
350
+ />
351
+ <!-- @windy-module system.audit end -->
359
352
  </div>
360
353
  </template>
@@ -4,11 +4,15 @@ import { systemResourceConfigs } from "../resource-config";
4
4
  import SystemResourcePage from "./SystemResourcePage.vue";
5
5
  import FeatureLicensePolicyDialog from "./FeatureLicensePolicyDialog.vue";
6
6
  import SystemResourceTable from "./SystemResourceTable.vue";
7
+ import DepartmentOrganizationForm from "./department/DepartmentOrganizationForm.vue";
8
+ import DepartmentTransitionDialog from "./department/DepartmentTransitionDialog.vue";
7
9
 
8
10
  const api = vi.hoisted(() => ({
9
11
  list: vi.fn(),
10
12
  update: vi.fn(),
11
13
  quickCreateDepartment: vi.fn(),
14
+ transitionDepartment: vi.fn(),
15
+ loadPasswordPolicy: vi.fn(),
12
16
  }));
13
17
 
14
18
  vi.mock("@/services/system-api", async () => {
@@ -20,12 +24,25 @@ vi.mock("@/services/system-api", async () => {
20
24
  listSystemResource: api.list,
21
25
  updateSystemResource: api.update,
22
26
  quickCreateDepartment: api.quickCreateDepartment,
27
+ transitionDepartment: api.transitionDepartment,
23
28
  };
24
29
  });
25
30
 
31
+ vi.mock("@/services/auth-api", () => ({
32
+ loadPasswordPolicy: api.loadPasswordPolicy,
33
+ }));
34
+
26
35
  afterEach(() => vi.clearAllMocks());
27
36
 
28
37
  describe("SystemResourcePage", () => {
38
+ api.loadPasswordPolicy.mockResolvedValue({
39
+ minLength: 8,
40
+ requireUppercase: true,
41
+ requireLowercase: true,
42
+ requireNumber: true,
43
+ requireSpecial: false,
44
+ });
45
+
29
46
  test("权限策略使用 Manifest 目录级联勾选而不是手输权限 key", async () => {
30
47
  api.list.mockResolvedValue({
31
48
  items: [
@@ -260,4 +277,55 @@ describe("SystemResourcePage", () => {
260
277
  );
261
278
  wrapper.unmount();
262
279
  });
280
+
281
+ test("部门停用和删除使用显式迁移目标", async () => {
282
+ const department = {
283
+ id: "dept_sales",
284
+ code: "sales",
285
+ name: "销售部",
286
+ parentId: "dept_platform",
287
+ status: "active",
288
+ };
289
+ api.list.mockResolvedValue({
290
+ items: [department],
291
+ total: 1,
292
+ page: 1,
293
+ pageSize: 10,
294
+ });
295
+ api.transitionDepartment.mockResolvedValue({
296
+ department: { ...department, status: "disabled" },
297
+ movedChildren: 0,
298
+ movedUsers: 1,
299
+ updatedRoles: 0,
300
+ });
301
+ const wrapper = mount(SystemResourcePage, {
302
+ props: { config: systemResourceConfigs.departments },
303
+ });
304
+ await flushPromises();
305
+
306
+ wrapper.getComponent(SystemResourceTable).vm.$emit("edit", department);
307
+ await flushPromises();
308
+ wrapper.getComponent(DepartmentOrganizationForm).vm.$emit("submit", {
309
+ ...department,
310
+ status: "disabled",
311
+ _transitionTargetDepartmentId: "dept_platform",
312
+ });
313
+ await flushPromises();
314
+ expect(api.transitionDepartment).toHaveBeenCalledWith("dept_sales", {
315
+ action: "disable",
316
+ targetDepartmentId: "dept_platform",
317
+ });
318
+
319
+ wrapper.getComponent(SystemResourceTable).vm.$emit("remove", department);
320
+ await flushPromises();
321
+ wrapper
322
+ .getComponent(DepartmentTransitionDialog)
323
+ .vm.$emit("confirm", "dept_platform");
324
+ await flushPromises();
325
+ expect(api.transitionDepartment).toHaveBeenLastCalledWith("dept_sales", {
326
+ action: "delete",
327
+ targetDepartmentId: "dept_platform",
328
+ });
329
+ wrapper.unmount();
330
+ });
263
331
  });
@@ -38,6 +38,7 @@ const props = defineProps<{
38
38
  inlineToggle?: ResourceInlineToggle;
39
39
  togglePendingId?: string;
40
40
  loading?: boolean;
41
+ viewable?: boolean;
41
42
  page?: number;
42
43
  pageSize?: number;
43
44
  }>();
@@ -47,6 +48,7 @@ const emit = defineEmits<{
47
48
  editLicense: [item: SystemRecord];
48
49
  remove: [item: SystemRecord];
49
50
  toggle: [item: SystemRecord, checked: boolean];
51
+ view: [item: SystemRecord];
50
52
  }>();
51
53
 
52
54
  const hasRows = computed(() => props.items.length > 0);
@@ -54,6 +56,14 @@ const access = useAccessSnapshot();
54
56
 
55
57
  function formatValue(item: SystemRecord, column: ResourceColumn): string {
56
58
  if (column.format === "license-policy") return formatLicensePolicy(item);
59
+ if (column.format === "audit-ip") {
60
+ const actor = item.actor;
61
+ if (!actor || typeof actor !== "object" || Array.isArray(actor)) {
62
+ return "未记录";
63
+ }
64
+ const ip = (actor as Record<string, unknown>).ip;
65
+ return typeof ip === "string" && ip.trim() ? ip : "未记录";
66
+ }
57
67
  const value = item[column.key];
58
68
  if (column.format === "audit-action")
59
69
  return auditAction(value)?.label || String(value || "-");
@@ -218,6 +228,15 @@ function licenseVersionLabel(version: string): string {
218
228
  />
219
229
  </div>
220
230
  <div v-else class="flex justify-end gap-2">
231
+ <Button
232
+ v-if="viewable"
233
+ variant="outline"
234
+ size="sm"
235
+ :aria-label="`查看审计日志 ${String(item.id)} 详情`"
236
+ @click="emit('view', item)"
237
+ >
238
+ 查看详情
239
+ </Button>
221
240
  <Button
222
241
  v-if="editable"
223
242
  variant="outline"
@@ -235,7 +254,7 @@ function licenseVersionLabel(version: string): string {
235
254
  删除
236
255
  </Button>
237
256
  <span
238
- v-if="!editable && !deletable"
257
+ v-if="!viewable && !editable && !deletable"
239
258
  class="text-sm text-muted-foreground"
240
259
  >
241
260
  只读
@@ -157,19 +157,40 @@ describe("SystemResourceTable", () => {
157
157
  });
158
158
 
159
159
  // @windy-module system.audit begin
160
- test("审计动作显示稳定中文名称和说明而不是内部 code", () => {
160
+ test("审计表直接显示动作摘要、IP,并从操作列进入详情", async () => {
161
+ const item = {
162
+ id: "audit_1",
163
+ action: "feature.update",
164
+ actor: { id: "user_1", name: "管理员", ip: "203.0.113.8" },
165
+ target: { type: "features", id: "system.audit" },
166
+ };
161
167
  const wrapper = mount(SystemResourceTable, {
162
168
  props: {
163
- columns: [{ key: "action", label: "动作", format: "audit-action" }],
164
- items: [{ id: "audit_1", action: "feature.update" }],
169
+ columns: systemResourceConfigs.auditLogs.columns,
170
+ items: [item],
171
+ viewable: true,
165
172
  },
166
173
  });
167
174
 
168
175
  expect(wrapper.text()).toContain("功能开关更新");
169
- expect(wrapper.text()).not.toContain("feature.update");
170
- expect(
171
- wrapper.get('button[type="button"]').attributes("title"),
172
- ).toBeUndefined();
176
+ expect(wrapper.text()).toContain("更新 features(system.audit)");
177
+ expect(wrapper.text()).toContain("203.0.113.8");
178
+ expect(wrapper.text()).toContain("查看详情");
179
+ await wrapper
180
+ .get('[aria-label="查看审计日志 audit_1 详情"]')
181
+ .trigger("click");
182
+ expect(wrapper.emitted("view")).toEqual([[item]]);
183
+ });
184
+
185
+ test("历史审计没有 IP 时显示未记录", () => {
186
+ const wrapper = mount(SystemResourceTable, {
187
+ props: {
188
+ columns: [{ key: "ip", label: "IP", format: "audit-ip" }],
189
+ items: [{ id: "audit_legacy", actor: { id: "user_1" } }],
190
+ },
191
+ });
192
+
193
+ expect(wrapper.text()).toContain("未记录");
173
194
  });
174
195
  // @windy-module system.audit end
175
196