Compare commits

..

462 Commits

Author SHA1 Message Date
scarqin 197d227cd6 fix: login page language error 2025-01-13 15:03:25 +08:00
scarqin b77ce23b39 fix: System Settings - General After changing the interface language, the internal pages do not automatically follow the language switch 2025-01-08 15:13:37 +08:00
Dot.L a92baf09d9 Merge pull request #162 from APIParkLab/feature/ai-balance
Feature/ai balance
2025-01-08 11:26:18 +08:00
Liujian 46e2edbe13 Merge branch 'main-github-pro' into feature/ai-balance
# Conflicts:
#	frontend/packages/core/src/pages/aiApis/aiApisLayout.tsx
#	frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx
#	frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx
2025-01-08 11:25:55 +08:00
Liujian 5aba86965e Merge remote-tracking branch 'github-pro/main' into main-github-pro
# Conflicts:
#	frontend/packages/common/src/components/aoplatform/LanguageSetting.tsx
#	frontend/packages/common/src/components/aoplatform/TableBtnWithPermission.tsx
#	frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx
#	frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditorResizable.tsx
#	frontend/packages/common/src/const/permissions.ts
#	frontend/packages/common/src/contexts/GlobalStateContext.tsx
#	frontend/packages/common/src/hooks/pluginLoader.ts
#	frontend/packages/common/src/utils/systemRunning.ts
#	frontend/packages/core/src/components/AIProviderSelect/index.tsx
#	frontend/packages/core/src/const/const.tsx
#	frontend/packages/core/src/const/system/const.tsx
#	frontend/packages/core/src/pages/aiApis/index.tsx
#	frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx
#	frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx
#	frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx
#	frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx
#	frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx
#	frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx
#	frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx
#	frontend/packages/core/src/pages/aiSetting/constants.ts
#	frontend/packages/core/src/pages/aiSetting/styles.css
#	frontend/packages/core/src/pages/aiSetting/types.ts
#	frontend/packages/core/src/pages/keySettings/components/ApiKeyContent.tsx
#	frontend/packages/core/src/pages/keySettings/components/StatusFilter.tsx
#	frontend/packages/core/src/pages/keySettings/index.tsx
#	frontend/packages/dashboard/src/component/MonitorApiPage.tsx
2025-01-08 11:20:57 +08:00
Liujian 5924208aaa ignore config.yaml 2025-01-08 11:14:47 +08:00
Liujian 526390816b update package script 2025-01-08 11:11:41 +08:00
ScarChin d7e28c9704 Feature/1.4 (#154)
- Load balancing (can connect to multiple accounts, automatically switch accounts when there is no quota)
- AI call log
- Model rate configuration
2025-01-07 18:47:08 +08:00
Liujian 9cf1cd99c2 Merge remote-tracking branch 'github-pro/feature/1.4' into feature/ai-balance 2025-01-07 16:53:41 +08:00
Liujian f27abbd454 fix: ai provider status error 2025-01-07 16:49:47 +08:00
scarqin 6a7a11a811 fix: In the supplier load chart, the mouse should not show a hand shape except for the cards that can be dragged and sorted. 2025-01-07 16:17:50 +08:00
Liujian 599ee6b9b8 AI API token quantity docking completed 2025-01-07 12:49:23 +08:00
scarqin 09b98c6c0d fix: Details has two slashes 2025-01-07 11:32:29 +08:00
scarqin 13eac21609 fix: After the release log configuration is successful, there is no success prompt and the list is not refreshed 2025-01-07 10:45:42 +08:00
scarqin d0d9e2a9a8 fix: system logs lack of edit permission 2025-01-07 10:32:03 +08:00
scarqin b047c93965 fix: i18n error 2025-01-07 10:04:18 +08:00
Liujian 9cc6696340 fix: ai key config error 2025-01-07 01:42:36 +08:00
scarqin 150a0264c5 fix: AI API should remove prefix matching drop-down selection 2025-01-06 19:29:15 +08:00
scarqin 558a2d8aad fix: There is no slide rail, so the bottom part cannot be displayed. 2025-01-06 19:14:09 +08:00
scarqin fa327114f7 fix: No refetching of lists after deleting keyword search 2025-01-06 17:53:30 +08:00
Liujian 4f7dee570a update ai key status to gateway 2025-01-06 10:57:23 +08:00
Liujian 7a70a6ce01 fix: ai key sort 2025-01-06 10:22:01 +08:00
Liujian 9871e252bc ai balance finish 2025-01-06 09:47:23 +08:00
Liujian d40eb6c4e1 Merge remote-tracking branch 'origin/main' into feature/ai-balance 2025-01-05 23:25:08 +08:00
Liujian a7b0e6d0bf update ai key iml 2025-01-05 23:25:00 +08:00
lichunxian f4f546e654 Merge branch 'feature/1.4' into 'main'
feat: aiApi detail page

See merge request apipark/APIPark!147
2025-01-03 17:47:21 +08:00
ningyv 1fcbb3ecbc feat: aiApi detail page 2025-01-03 17:46:23 +08:00
ningyv 98e3cc973b Merge branch 'feature/1.4' of http://gitlab.eolink.com/apipark/APIPark into feature/1.4 2025-01-03 17:33:17 +08:00
ningyv 220ab53ef2 feat: aiApi detail page 2025-01-03 17:33:06 +08:00
秦圆圆 9ba70063d2 Merge branch 'feature/1.4' into 'main'
fix: The load diagram moves as a whole to the left so that the main content is...

See merge request apipark/APIPark!146
2025-01-03 17:14:05 +08:00
scarqin 076277d0a9 fix: KEY pool adjustment order will fail 2025-01-03 17:13:40 +08:00
scarqin bcd2ba1ec9 fix: The bottom margin of the banner should be sufficient 2025-01-03 16:37:36 +08:00
scarqin 5827afd09c fix: The load diagram moves as a whole to the left so that the main content is in the middle and the AI ​​Services icon is aligned to the left 2025-01-03 16:29:27 +08:00
秦圆圆 4a3e49f4e3 Merge branch 'feature/1.4' into 'main'
Feature/1.4

See merge request apipark/APIPark!144
2025-01-03 16:12:34 +08:00
Liujian 95d24aca41 Merge remote-tracking branch 'origin/feature/1.4' into feature/ai-balance 2025-01-03 11:25:59 +08:00
刘健 345e37bd81 Merge branch 'feature/ai-balance' into 'main'
update ai api list

See merge request apipark/APIPark!145
2025-01-03 10:47:10 +08:00
Liujian 91b2fabf10 update ai api list 2025-01-03 10:46:25 +08:00
scarqin bae1803157 i18n 2025-01-03 09:42:17 +08:00
刘健 911d16de31 Merge branch 'feature/ai-balance' into 'main'
update ai api list

See merge request apipark/APIPark!143
2025-01-03 09:33:46 +08:00
Liujian 1efe924221 update ai api list 2025-01-03 09:33:12 +08:00
scarqin 0230235427 feat: navigate 2025-01-03 09:24:32 +08:00
scarqin 58e737ee28 chore: apis 2025-01-03 09:09:40 +08:00
scarqin 78c98f121d fix: api key timestamp error 2025-01-03 09:06:36 +08:00
秦圆圆 2e3b86741b Merge branch 'feature/1.4' into 'main'
Feature/1.4

See merge request apipark/APIPark!138
2025-01-03 08:20:23 +08:00
scarqin fcd9869caa feat: api url 2025-01-02 10:07:54 +08:00
scarqin f60579b735 fix: padding error 2025-01-02 09:45:32 +08:00
scarqin 6574f36c73 fix: provider tips 2025-01-02 09:32:19 +08:00
scarqin 246ce245b9 fix: alert api list tips 2025-01-02 09:29:03 +08:00
scarqin fa0a211db9 feat: add banner 2025-01-02 08:21:33 +08:00
scarqin 1c536df3c8 feat: apilist 2024-12-31 17:21:56 +08:00
scarqin 82fa1b5b1c chore: change disable to disabled 2024-12-31 16:17:53 +08:00
刘健 dacce748a1 Merge branch 'feature/ai-balance' into 'main'
update api disable to disabled

See merge request apipark/APIPark!142
2024-12-31 15:24:55 +08:00
Liujian b59088c598 update api disable to disabled 2024-12-31 15:24:36 +08:00
scarqin 26dbce9dbf feat: ai column add disable 2024-12-31 15:17:36 +08:00
scarqin 9b6d07dc4c feat: apikey 2024-12-31 15:07:43 +08:00
刘健 00e21c8000 Merge branch 'feature/ai-balance' into 'main'
fix: provider status incorrect

See merge request apipark/APIPark!141
2024-12-31 14:50:41 +08:00
Liujian 8c5f5326d5 fix: provider status incorrect 2024-12-31 14:48:19 +08:00
scarqin 3ebafcbc03 feat: stopaable tips 2024-12-31 14:24:15 +08:00
scarqin 9777859f42 feat: add llm status manage 2024-12-31 12:01:42 +08:00
scarqin 54f76d6576 feat: add priority 2024-12-31 10:27:50 +08:00
刘健 3bb8293478 Merge branch 'feature/ai-balance' into 'main'
update go-common version

See merge request apipark/APIPark!140
2024-12-31 10:17:36 +08:00
Liujian 16e899cab7 update go-common version 2024-12-31 10:16:44 +08:00
scarqin b4b9469284 refacor: delete useless code 2024-12-30 17:03:36 +08:00
scarqin e0896864c2 feat: update list after edit modal 2024-12-30 17:00:09 +08:00
scarqin c7b16e0ea9 feat: ai model 2024-12-30 15:59:43 +08:00
scarqin f6d6920cfb feat: ai model detail 2024-12-30 14:59:22 +08:00
刘健 8b4059a249 Merge branch 'feature/ai-balance' into 'main'
update ai provider list key

See merge request apipark/APIPark!139
2024-12-30 13:50:18 +08:00
Liujian 94b19e7589 update ai provider list key 2024-12-30 13:49:59 +08:00
scarqin a090bb7caa feat: ai apis 2024-12-27 21:28:35 +08:00
刘健 101e97dfec Merge branch 'feature/ai-balance' into 'main'
Feature/ai balance

See merge request apipark/APIPark!137
2024-12-27 16:56:28 +08:00
Liujian 31054a2df7 update simple/providers/configured api 2024-12-27 16:55:54 +08:00
scarqin 58231eb19c feat: api tab 2024-12-27 09:56:15 +08:00
Liujian 8f8b5d2684 Merge branch 'main-github-pro' into feature/ai-balance 2024-12-26 18:22:30 +08:00
Liujian ca5e497dbb Merge remote-tracking branch 'origin/main' into main-github-pro 2024-12-26 18:21:30 +08:00
刘健 13aabcacd2 Merge branch 'feature/ai-balance' into 'main'
Add filtering options to the list

See merge request apipark/APIPark!136
2024-12-26 18:07:17 +08:00
Liujian 3afd0bb609 Add filtering options to the list 2024-12-26 18:06:54 +08:00
刘健 e250a8b57f Merge branch 'feature/ai-balance' into 'main'
Feature/ai balance

See merge request apipark/APIPark!135
2024-12-26 17:26:01 +08:00
Liujian 051fa7647d ai api list add filter condition 2024-12-26 17:25:36 +08:00
Liujian 9c4c794d0f Merge branch 'main-github-pro' into feature/ai-balance 2024-12-26 16:48:03 +08:00
Liujian 9211a28675 update service list api 2024-12-26 16:46:11 +08:00
scarqin 8d660ec7c0 feat: ai model config 2024-12-26 16:05:56 +08:00
秦圆圆 ed8109fc30 Merge branch 'feature/1.4' into 'main'
Feature/1.4

See merge request apipark/APIPark!134
2024-12-26 15:54:20 +08:00
scarqin 37fc63def8 feat: apikey 2024-12-26 15:52:43 +08:00
刘健 8c1a8f67d8 Merge branch 'feature/ai-balance' into 'main'
update simple configured providers

See merge request apipark/APIPark!133
2024-12-26 15:41:24 +08:00
Liujian 94ed7581bb update simple configured providers 2024-12-26 15:41:07 +08:00
scarqin 4a84a69fe7 feat: add api key 2024-12-26 15:39:52 +08:00
刘健 1ebf6c9319 Merge branch 'feature/ai-balance' into 'main'
Feature/ai balance

See merge request apipark/APIPark!132
2024-12-26 15:10:24 +08:00
Liujian d0fc353d0b finish ai provider api 2024-12-26 15:09:44 +08:00
scarqin 977919fdb1 feat: add key 2024-12-26 14:36:24 +08:00
scarqin ae2e37cedb feat: add modal 2024-12-26 14:20:06 +08:00
scarqin 02e5394924 feat: add api keys 2024-12-25 20:23:47 +08:00
scarqin cdc9bb73bb feat: support sort table 2024-12-25 17:53:15 +08:00
scarqin 3a2c0c744c feat: delete apikey 2024-12-25 17:36:26 +08:00
scarqin ffeb76f608 fix: provider change 2024-12-25 17:15:42 +08:00
scarqin 3748cb39b2 feat: sort api 2024-12-25 17:08:26 +08:00
scarqin 543ea52bb3 feat: pagination and search api 2024-12-25 16:51:41 +08:00
scarqin 2ad508ec60 feat: ai provider component 2024-12-25 15:41:20 +08:00
scarqin f9501d6f60 style: apikey layout 2024-12-25 15:05:19 +08:00
scarqin 6bbdf9600d feat: encapsulation select options 2024-12-25 14:43:29 +08:00
scarqin c554c010c5 style: remove keys card border 2024-12-25 14:12:43 +08:00
scarqin 075c976d19 fix: some type error 2024-12-25 14:08:22 +08:00
scarqin 6da49e78ee feat: remove attribute 2024-12-25 13:52:00 +08:00
scarqin 59ce2e0623 feat: add token 2024-12-25 12:01:19 +08:00
Dot.L 09b2a7f1a4 Merge pull request #159 from APIParkLab/feature/ai-balance
add get simple ai provider api
2024-12-25 12:01:03 +08:00
Liujian bd466ac420 add get simple ai provider api 2024-12-25 11:59:34 +08:00
scarqin 96183eb5df feat: apikey get 2024-12-25 10:38:28 +08:00
scarqin 21164859cf feat: template modal 2024-12-25 10:14:07 +08:00
Dot.L 9b50fe68c9 Merge pull request #158 from APIParkLab/feature/ai-balance
Feature/ai balance
2024-12-24 18:23:14 +08:00
刘健 d8576e4dc6 Merge branch 'feature/ai-balance' into 'main'
update go.mod

See merge request apipark/APIPark!131
2024-12-24 18:20:44 +08:00
Liujian 9fc23ad4be update go.mod 2024-12-24 18:20:09 +08:00
scarqin 6c997c0b51 feat: apikeys 2024-12-24 18:14:50 +08:00
刘健 9b10421882 Merge branch 'feature/ai-balance' into 'main'
finish ai apis

See merge request apipark/APIPark!130
2024-12-24 18:01:41 +08:00
Liujian e5bc98cac0 finish ai apis 2024-12-24 18:00:46 +08:00
scarqin e98f320f41 feat: add route apikey and ai api 2024-12-24 17:29:31 +08:00
scarqin d0ef35fb92 feat: flow chart 2024-12-24 16:36:23 +08:00
scarqin 8c512f3163 feat: use translateExtent to limit scroll area 2024-12-24 15:41:14 +08:00
scarqin 8cc0fc9987 disable dragger 2024-12-24 15:36:55 +08:00
scarqin 604200e1db feat: flow chart 2024-12-24 15:31:02 +08:00
scarqin 2a1581acdb feat: remove fit view 2024-12-24 15:14:52 +08:00
scarqin 39e3198821 feat: custom edge 2024-12-24 15:06:48 +08:00
scarqin b4c2b3614b feat: keycard change place 2024-12-24 14:37:45 +08:00
scarqin 5ea0cc7838 fet 2024-12-24 14:34:16 +08:00
scarqin a9b7fc525a feat: model 2024-12-24 14:28:10 +08:00
scarqin b05874fb0a feat: fetch data from api 2024-12-24 13:51:08 +08:00
scarqin 5fdd142a17 feat: change component place 2024-12-24 11:58:51 +08:00
scarqin 243e1da716 feat: dmo 2024-12-24 11:35:31 +08:00
scarqin 772217258c refactor: encapsulation constant 2024-12-24 10:37:10 +08:00
scarqin 57cc9c9db7 feat: draggable button 2024-12-23 18:36:12 +08:00
scarqin e6f6560f3a feat: draggable 2024-12-23 18:17:51 +08:00
scarqin cbce30b4d7 feat: draggable 2024-12-23 18:16:17 +08:00
scarqin 23c8a84b4c feat: add multiple data 2024-12-23 17:56:22 +08:00
scarqin a521bff1f1 chore: 文件拆分 2024-12-23 17:45:50 +08:00
scarqin 986784b128 feat: change type status 2024-12-23 17:42:55 +08:00
scarqin 238c2b8cd3 feat: change group title 2024-12-23 17:31:58 +08:00
scarqin c6a418e00c chore: card status 2024-12-23 17:14:23 +08:00
scarqin e59ea1f84d feat: add handler 2024-12-23 16:11:12 +08:00
Dot.L a1acde5df0 Merge pull request #156 from APIParkLab/feature/ai-balance
update go-common dependence in go.mod
2024-12-23 16:05:41 +08:00
刘健 3ec61165ca Merge branch 'feature/ai-balance' into 'main'
update go-common dependence in go.mod

See merge request apipark/APIPark!129
2024-12-23 15:58:43 +08:00
Liujian cc823a18d4 update go-common dependence in go.mod 2024-12-23 15:58:24 +08:00
刘健 b6aa865a67 Merge branch 'feature/ai-balance' into 'main'
Feature/ai balance

See merge request apipark/APIPark!128
2024-12-23 15:51:09 +08:00
Dot.L 86f83d995b Merge pull request #155 from APIParkLab/feature/ai-balance
finish ai key api
2024-12-23 15:49:27 +08:00
Liujian e314e09fdb finish ai key api 2024-12-23 15:48:19 +08:00
scarqin 4aa4238943 feat: block 2024-12-23 14:09:53 +08:00
scarqin 5d638b8bf4 feat: basic card component 2024-12-23 14:00:24 +08:00
scarqin 5027d817c5 chore: add global rule 2024-12-23 11:36:25 +08:00
scarqin 40b92330eb chore: add global_rules.md 2024-12-23 10:16:59 +08:00
scarqin b990447226 feat: chart 2024-12-20 20:26:18 +08:00
scarqin 482019f514 chore: refactor format eslint and prettier code 2024-12-19 16:17:50 +08:00
scarqin 2027af3a3f chore: add remove unused 2024-12-19 15:49:20 +08:00
scarqin c1a55385d3 change 2024-12-18 17:37:20 +08:00
scarqin 8be47c99dd test 2024-12-18 17:33:39 +08:00
scarqin 10d11f5b99 test 2024-12-18 17:33:08 +08:00
scarqin 3db2c66f12 test 2024-12-18 17:32:23 +08:00
scarqin e8614c0072 test 2024-12-18 17:31:03 +08:00
scarqin 11b4c43845 test 2024-12-18 17:30:16 +08:00
scarqin 36fec4ddbe test 2024-12-18 17:28:17 +08:00
scarqin bb64039a0d test 2024-12-18 17:27:49 +08:00
scarqin 7b43b0b300 test 2024-12-18 17:25:54 +08:00
scarqin fd270c80f5 test 2024-12-18 17:24:02 +08:00
scarqin 12b5801b1b test 2024-12-18 17:20:47 +08:00
scarqin 23a6b38a7d test 2024-12-18 17:19:10 +08:00
scarqin 296118470c test 2024-12-18 17:13:29 +08:00
scarqin 6d4dccc6b7 feat: push 2024-12-18 17:11:43 +08:00
scarqin e449f86c01 fix: login page refresh multiple times 2024-12-18 15:48:51 +08:00
Dot.L a1bdc048a7 Merge pull request #149 from APIParkLab/feature/aibug-fix
add default router when create new rest service
2024-12-16 14:43:26 +08:00
刘健 b5be78416f Update .gitlab-ci.yml file 2024-12-16 14:34:33 +08:00
刘健 89d1fe2c49 Merge branch 'feature/aibug-fix' into 'main'
add default router when create new rest service

See merge request apipark/APIPark!127
2024-12-16 14:33:14 +08:00
Liujian 0a1b08157d add default router when create new rest service 2024-12-16 14:31:52 +08:00
刘健 61503f4146 Merge branch 'feature/aibug-fix' into 'main'
chore: update index.tsx

See merge request apipark/APIPark!126
2024-12-13 20:06:43 +08:00
ningyv d36c66371f Merge pull request #147 from APIParkLab/fix/fixbug-cx
fix: consumer permission
2024-12-13 20:02:45 +08:00
lichunxian 34b259562d Merge branch 'fix/fixbug-cx' into 'main'
fix: consumer permission

See merge request apipark/APIPark!125
2024-12-13 20:01:59 +08:00
ningyv b7bb409e96 fix: consumer permission 2024-12-13 20:00:38 +08:00
Dot.L 517007c941 Merge pull request #146 from APIParkLab/feature/aibug-fix
fix aksk bug
2024-12-13 19:41:10 +08:00
Liujian 4c685a9ec6 fix aksk bug 2024-12-13 18:52:21 +08:00
Dot.L 1aca2099de Merge pull request #145 from APIParkLab/feature/aibug-fix
fix ai provider upstream bug
2024-12-10 17:26:47 +08:00
Liujian a93e5b4ff8 fix ai provider upstream bug 2024-12-10 17:24:51 +08:00
ningyv 85d25bebe2 Merge branch 'main' of github.com:APIParkLab/APIPark 2024-12-10 15:34:44 +08:00
lichunxian 9fa43ccc00 Merge branch 'fix/certificatePermission' into 'main'
fix: table-permission

See merge request apipark/APIPark!124
2024-12-10 15:17:11 +08:00
ningyv c2a11050dd fix: table-permission 2024-12-10 15:16:19 +08:00
Dot.L 080bfc3a44 Merge pull request #143 from eltociear/patch-1
chore: update index.tsx
2024-12-09 21:48:14 +08:00
Liujian f6956ddeca Merge remote-tracking branch 'origin/main' into main-github-pro 2024-12-09 21:42:09 +08:00
Liujian 9f56fa5e14 update init log 2024-12-09 21:32:57 +08:00
lichunxian ccc39b95de Merge branch 'fix/certificatePermission' into 'main'
fix: implement certificate popup permission handling

See merge request apipark/APIPark!123
2024-12-09 15:22:29 +08:00
ningyv 9a2782e54b fix: implement certificate popup permission handling 2024-12-09 15:18:59 +08:00
Liujian 22455e2301 Automatically publish policies and logs during cluster initialization 2024-12-09 00:43:05 +08:00
Liujian 8ed2c84b68 update loki publish 2024-12-09 00:17:50 +08:00
Liujian ccd2a209e2 update .gitignore 2024-12-06 18:48:00 +08:00
lichunxian baf8ed4830 Merge branch 'feature/dataLogPage' into 'main'
fix: improve column width adjustment and optimize date picker performance

See merge request apipark/APIPark!122
2024-12-06 18:03:16 +08:00
ningyv dedb586daf fix: improve column width adjustment and optimize date picker performance 2024-12-06 18:02:44 +08:00
刘健 21cd823791 Merge branch 'feature/data-mask' into 'main'
update publish problem

See merge request apipark/APIPark!121
2024-12-06 16:13:23 +08:00
Liujian c8ab65ef1b update publish problem 2024-12-06 16:12:49 +08:00
lichunxian f1c16fd992 Merge branch 'feature/dataLogPage' into 'main'
Feature/data log page

See merge request apipark/APIPark!120
2024-12-06 15:25:51 +08:00
ningyv 52035341f6 fix: refine time range calculation with second-level precision 2024-12-06 15:24:06 +08:00
ningyv aa62d44717 fix: resolve subscriber permissions 2024-12-06 14:22:59 +08:00
lichunxian a072d1fc8d Merge branch 'feature/dataLogPage' into 'main'
feat: integrate global policy API and implement data log page

See merge request apipark/APIPark!119
2024-12-06 11:51:13 +08:00
ningyv 38a00570d0 feat: integrate global policy API and implement data log page 2024-12-06 11:50:32 +08:00
刘健 43283b9da3 Merge branch 'feature/data-mask' into 'main'
update service publish

See merge request apipark/APIPark!118
2024-12-06 11:32:15 +08:00
Liujian ef82cdbed6 update service publishing difference comparison field 2024-12-06 11:31:58 +08:00
Liujian 2bafe6f31f update service publish 2024-12-06 11:02:40 +08:00
刘健 3eb4f98fd8 Merge branch 'feature/data-mask' into 'main'
add service strategy log

See merge request apipark/APIPark!117
2024-12-06 10:49:21 +08:00
Liujian bb5acad033 add service strategy log 2024-12-06 10:47:47 +08:00
刘健 b8308a446b Merge branch 'feature/data-mask' into 'main'
update service publish

See merge request apipark/APIPark!116
2024-12-06 10:40:20 +08:00
Liujian c86f99ce45 update service publish 2024-12-06 10:39:59 +08:00
lichunxian 952c519e45 Merge branch 'feature/dataLogPage' into 'main'
feat: integrate global policy API and implement data log page

See merge request apipark/APIPark!115
2024-12-05 18:53:51 +08:00
ningyv 07d97fa0bf feat: integrate global policy API and implement data log page 2024-12-05 18:53:11 +08:00
刘健 b0defedf04 Merge branch 'feature/data-mask' into 'main'
fix log bug:Keyword query failed

See merge request apipark/APIPark!114
2024-12-05 18:17:49 +08:00
Liujian edc2fccdeb fix log bug:Keyword query failed 2024-12-05 18:17:29 +08:00
刘健 a75b8a3f13 Merge branch 'feature/data-mask' into 'main'
fix logs bug

See merge request apipark/APIPark!113
2024-12-05 17:21:46 +08:00
Liujian 5aab5f7913 fix logs bug 2024-12-05 17:20:11 +08:00
刘健 9ab7989c8b Merge branch 'feature/data-mask' into 'main'
fix log bug

See merge request apipark/APIPark!112
2024-12-05 17:15:04 +08:00
Liujian e3e11d740a fix log bug 2024-12-05 17:14:26 +08:00
刘健 4bae2edc49 Merge branch 'feature/data-mask' into 'main'
update strategy publish bug

See merge request apipark/APIPark!111
2024-12-05 15:44:22 +08:00
Liujian 4eaa47ca25 update strategy publish bug 2024-12-05 15:43:42 +08:00
刘健 e01f596525 Merge branch 'feature/data-mask' into 'main'
update log label

See merge request apipark/APIPark!109
2024-12-05 15:06:28 +08:00
Liujian 912e8d0d04 update log label 2024-12-05 15:04:43 +08:00
刘健 570c80af91 Merge branch 'feature/data-mask' into 'main'
Feature/data mask

See merge request apipark/APIPark!108
2024-12-05 14:50:19 +08:00
Liujian 7aa0ec0d67 Merge remote-tracking branch 'origin/main' into feature/data-mask
# Conflicts:
#	frontend/packages/core/src/pages/policy/dataMasking/DataMasking.tsx
2024-12-05 14:50:00 +08:00
Liujian 2fea6cb622 update eosc version 2024-12-05 14:43:24 +08:00
Liujian 2195ff900f data mask log commit 2024-12-05 14:39:57 +08:00
Ikko Eltociear Ashimine 836c7699b8 chore: update index.tsx
protocal -> protocol
2024-12-04 15:54:09 +09:00
Liujian 72ed6c814e Merge remote-tracking branch 'github-pro/feature/v1.3/mj' into feature/data-mask 2024-12-03 16:07:13 +08:00
ningyv c33b070509 Merge branch 'feature/v1.3/mj' of github.com:APIParkLab/APIPark into feature/v1.3/mj 2024-11-29 18:39:20 +08:00
ningyv de12d5686c feature: dataMask logs dialog 2024-11-29 18:39:13 +08:00
lichunxian c55a8ac805 Merge branch 'fix/copyIssue' into 'main'
Fix/copy issue

See merge request apipark/APIPark!107
2024-11-29 10:39:28 +08:00
杨梦洁 df3626f3f0 Merge branch 'feature/v1.3/mj' into 'main'
fix: Dashboard Table Height

See merge request apipark/APIPark!106
2024-11-29 10:36:45 +08:00
杨梦洁 28bef97faa fix: Dashboard Table Height 2024-11-29 10:35:32 +08:00
ningyv 9897b6e9dc fix: Fix the issue where copying the URL doesn't work 2024-11-29 10:22:40 +08:00
ningyv 9af6963901 fix: Fixed side navigation disappearing 2024-11-29 10:08:06 +08:00
lichunxian c2d3ebecda Merge branch 'fix/fix-table' into 'main'
Fix/fix table

See merge request apipark/APIPark!105
2024-11-29 09:55:26 +08:00
ningyv ba543311fc Merge branch 'main' of http://gitlab.eolink.com/apipark/APIPark 2024-11-29 09:45:55 +08:00
ningyv 87b8dda97b Fix Set the header width 2024-11-29 09:41:50 +08:00
ningyv e7facf5686 Fix icon issues 2024-11-28 19:15:02 +08:00
刘健 4dd57837c5 Merge branch 'feature/data-mask' into 'main'
update transaction

See merge request apipark/APIPark!104
2024-11-28 18:41:56 +08:00
Liujian d4ebc68e30 update transaction 2024-11-28 18:40:00 +08:00
Maggie 1f8e089e51 Merge pull request #141 from APIParkLab/feature/v1.3/mj
fix: get service remote option
2024-11-28 18:39:17 +08:00
杨梦洁 ce6f463fe8 Merge branch 'feature/v1.3/mj' into 'main'
fix: get service remote option

See merge request apipark/APIPark!103
2024-11-28 18:38:51 +08:00
杨梦洁 a37fe1d794 fix: get service remote option 2024-11-28 18:38:12 +08:00
刘健 818d1ec6bf Merge branch 'feature/data-mask' into 'main'
fix strategy bug

See merge request apipark/APIPark!102
2024-11-28 18:35:20 +08:00
Liujian fc4a5f7e28 fix strategy bug 2024-11-28 18:34:15 +08:00
Maggie 0e3568b584 Merge pull request #140 from APIParkLab/feature/v1.3/mj
fix: service data-masking layout
2024-11-28 18:24:44 +08:00
杨梦洁 3617e4fe29 Merge branch 'feature/v1.3/mj' into 'main'
fix: service data-masking layout

See merge request apipark/APIPark!101
2024-11-28 18:22:44 +08:00
刘健 f2fddc1727 Merge branch 'feature/data-mask' into 'main'
Feature/data mask

See merge request apipark/APIPark!100
2024-11-28 18:19:38 +08:00
Liujian 3b7204f1a6 openapi取消登录校验 2024-11-28 18:19:08 +08:00
杨梦洁 6646bb1e56 fix: service data-masking layout 2024-11-28 18:14:02 +08:00
Liujian c27533f802 Merge remote-tracking branch 'github-pro/main' into feature/data-mask 2024-11-28 17:35:53 +08:00
Maggie 613a47c181 Merge pull request #139 from APIParkLab/feature/v1.3/mj
Feature/v1.3/mj
2024-11-28 17:01:32 +08:00
杨梦洁 90138a142b Merge branch 'feature/v1.3/mj' into 'main'
fix: Modify Context Arct

See merge request apipark/APIPark!99
2024-11-28 17:01:11 +08:00
杨梦洁 4bf8db4898 fix: Modify Context Arct 2024-11-28 16:59:51 +08:00
杨梦洁 4f887f7204 Merge branch 'feature/v1.3/mj' into 'main'
fix: add translation

See merge request apipark/APIPark!98
2024-11-28 13:49:43 +08:00
杨梦洁 14e17ccf2c fix: add translation 2024-11-28 13:47:31 +08:00
刘健 5975670b8c Merge branch 'feature/data-mask' into 'main'
add publish tip

See merge request apipark/APIPark!97
2024-11-28 11:17:36 +08:00
Liujian dbc4bc3343 add publish tip 2024-11-28 11:17:02 +08:00
刘健 ada7635703 Merge branch 'feature/data-mask' into 'main'
fix rest service publish

See merge request apipark/APIPark!95
2024-11-28 10:49:16 +08:00
Maggie 943a77f718 Merge pull request #137 from APIParkLab/feature/v1.3/mj
fix: role list scroll bug and add translation
2024-11-28 10:47:44 +08:00
杨梦洁 ef02c11efa Merge branch 'feature/v1.3/mj' into 'main'
fix: role list scroll bug and add translation

See merge request apipark/APIPark!96
2024-11-28 10:41:45 +08:00
杨梦洁 8c166dae9b fix: role list scroll bug and add translation 2024-11-28 10:40:43 +08:00
Liujian b23da78c26 fix rest service publish 2024-11-27 20:18:23 +08:00
刘健 93ac7310e8 Merge branch 'feature/data-mask' into 'main'
finish service publish

See merge request apipark/APIPark!94
2024-11-27 19:47:16 +08:00
Liujian 9376acc456 finish service publish 2024-11-27 19:46:52 +08:00
Maggie b70a1f9a51 Merge pull request #136 from APIParkLab/feature/v1.3/mj
Feature/v1.3/mj
2024-11-27 19:22:06 +08:00
杨梦洁 ac90a134b4 Merge branch 'feature/v1.3/mj' into 'main'
fix: data-masking and integration bugs

See merge request apipark/APIPark!93
2024-11-27 19:21:58 +08:00
杨梦洁 96bd1cf9f6 fix: data-masking and integration bugs 2024-11-27 19:20:47 +08:00
刘健 960e37a81a Merge branch 'feature/data-mask' into 'main'
Feature/data mask

See merge request apipark/APIPark!92
2024-11-27 19:05:28 +08:00
Liujian 044bd550c9 update service publish rule 2024-11-27 19:05:11 +08:00
Liujian 0a9a903d1b Merge remote-tracking branch 'origin/main' into feature/data-mask 2024-11-27 18:30:46 +08:00
杨梦洁 84d7606e12 Merge branch 'feature/v1.3/mj' into 'main'
fix: data-masking bugs

See merge request apipark/APIPark!91
2024-11-27 18:22:41 +08:00
杨梦洁 febb64b8bb fix: data-masking bugs 2024-11-27 18:21:43 +08:00
刘健 932e433c46 Merge branch 'feature/data-mask' into 'main'
add open api swagger

See merge request apipark/APIPark!90
2024-11-27 13:52:15 +08:00
Liujian b7307cd36d add open api swagger 2024-11-27 13:51:55 +08:00
刘健 7a0f3efd83 Merge branch 'feature/data-mask' into 'main'
add strategy permit

See merge request apipark/APIPark!89
2024-11-27 11:47:22 +08:00
Liujian 28af1f691c add strategy permit 2024-11-27 11:46:56 +08:00
刘健 796bc7bc15 Merge branch 'feature/data-mask' into 'main'
add api: /simple/service/apis

See merge request apipark/APIPark!88
2024-11-27 00:35:11 +08:00
Liujian 8f06073783 add api: /simple/service/apis 2024-11-27 00:34:24 +08:00
刘健 7d6251b191 Merge branch 'feature/data-mask' into 'main'
Feature/data mask

See merge request apipark/APIPark!87
2024-11-26 23:50:14 +08:00
Liujian e13fff633e system setting add site_prefix 2024-11-26 23:49:44 +08:00
Liujian d984be4b85 finish data mask strategy 2024-11-26 23:44:00 +08:00
Liujian 213bdbd9d5 Merge remote-tracking branch 'github-pro/main' into feature/data-mask 2024-11-25 10:35:21 +08:00
杨梦洁 6a59d27b84 Merge branch 'feature/v1.3/mj' into 'main'
fix: Change Dashboard Fields

See merge request apipark/APIPark!86
2024-11-21 18:38:13 +08:00
杨梦洁 ad45ab2e82 fix: Change Dashboard Fields 2024-11-21 18:37:39 +08:00
Liujian 8982a63283 Merge remote-tracking branch 'origin/feature/v1.3/mj' into feature/data-mask 2024-11-21 17:36:57 +08:00
Liujian 1b1515a8bd Initial submission of data desensitization strategy backend 2024-11-21 17:36:13 +08:00
Maggie df50e13db0 Merge pull request #135 from APIParkLab/feature/v1.3/mj
Feature/v1.3/mj
2024-11-21 17:05:48 +08:00
杨梦洁 89d91c14c9 Merge branch 'feature/v1.3/mj' into 'main'
fix: Change Vite Config

See merge request apipark/APIPark!85
2024-11-21 16:43:53 +08:00
杨梦洁 3a57c609f7 fix: Change Vite Config 2024-11-21 16:42:32 +08:00
杨梦洁 2cd331ec50 Merge branch 'feature/v1.3/mj' into 'main'
fix: Change File Name

See merge request apipark/APIPark!84
2024-11-21 16:34:31 +08:00
杨梦洁 3fa02ec65c fix: Change File Name 2024-11-21 16:32:07 +08:00
杨梦洁 f33f1965b4 Merge branch 'feature/v1.3/mj' into 'main'
Feature/v1.3/mj

See merge request apipark/APIPark!83
2024-11-21 16:26:59 +08:00
杨梦洁 a70ecea02b fix: Change File Name 2024-11-21 16:25:18 +08:00
杨梦洁 8e68eb35f3 fix: Change file name 2024-11-21 16:20:52 +08:00
杨梦洁 2893331ff5 Merge branch 'main' into feature/v1.3/mj 2024-11-21 16:12:16 +08:00
杨梦洁 1a3d14cdd6 Merge branch 'feature/v1.3/mj' into 'main'
Feature/v1.3/mj

See merge request apipark/APIPark!82
2024-11-21 16:11:42 +08:00
杨梦洁 dce9a7addb feat: Complete static pages for Phase 1 of V1.3 2024-11-21 16:08:08 +08:00
Liujian f3e7487482 add gitlab-ci 2024-11-15 14:35:27 +08:00
Liujian 86c39237dc Merge remote-tracking branch 'github-pro/main' 2024-11-15 14:21:23 +08:00
Dot.L b5ad739b93 Merge pull request #134 from PeterDaveHelloKitchen/zh-TW
Improve zh-TW Traditional Chinese locale
2024-11-15 14:19:39 +08:00
杨梦洁 0b7f0405d5 feat: Merge MF with data-masking list 2024-11-15 13:42:30 +08:00
lcx 1ab56708a5 feat: Global/Service Policy Development, Add Service Details Integration Tab 2024-11-15 10:44:16 +08:00
杨梦洁 522489c9e9 feat: plugin system 2024-11-14 19:09:46 +08:00
Peter Dave Hello a9eb2a790f Improve zh-TW Traditional Chinese locale 2024-11-14 05:49:34 +08:00
maggieyyy a092ed1108 fix: Move plugin code to common 2024-11-06 14:26:38 +08:00
maggieyyy 503515281d Merge branch 'main' into feature/mf 2024-11-06 11:33:21 +08:00
maggieyyy 2326d4dfb5 feat: plugin system 2024-11-05 16:20:40 +08:00
Dot.L 42e8030cf7 Merge pull request #130 from APIParkLab/feature/permit
Feature/permit
2024-11-04 11:06:03 +08:00
Liujian 17b4ede566 Merge branch 'main-github' into feature/permit
# Conflicts:
#	go.mod
2024-11-04 11:05:30 +08:00
Liujian bbc3fea848 update go.mod 2024-11-04 11:03:40 +08:00
Liujian 82e46b872b Merge remote-tracking branch 'origin/main' into main-github 2024-11-04 11:00:05 +08:00
Liujian 0f6a091c73 fix service info no prefix 2024-11-04 10:59:13 +08:00
Liujian 4e87adb4b3 Merge remote-tracking branch 'origin/main' into main-github 2024-11-04 10:49:05 +08:00
杨梦洁 320a2b6cf8 Merge branch 'feature/v1.2' into 'main'
fix: Modify service inside access

See merge request apipark/APIPark!81
2024-11-04 10:46:21 +08:00
maggieyyy b216556867 fix: Modify service inside access 2024-11-04 10:44:30 +08:00
Liujian 7fbb98a2a9 Merge remote-tracking branch 'github-pro/main' into main-github 2024-11-01 19:03:17 +08:00
Liujian 461a8edbea update .gitignore 2024-11-01 19:01:50 +08:00
刘健 cdef179bed Merge branch 'feature/permit' into 'main'
Feature/permit

See merge request apipark/APIPark!80
2024-11-01 18:53:22 +08:00
Liujian a067388d79 update ai permit 2024-11-01 18:52:50 +08:00
杨梦洁 2d5e541593 Merge branch 'feature/v1.2' into 'main'
fix: Modify link access

See merge request apipark/APIPark!79
2024-11-01 18:42:40 +08:00
Maggie be40186ad3 Merge pull request #129 from APIParkLab/feature/v1.2
fix: Modify link access
2024-11-01 18:42:26 +08:00
maggieyyy c9ae05b22e fix: Modify link access 2024-11-01 18:41:16 +08:00
maggieyyy 2b874fe59f fix: Modify execute list 2024-11-01 18:17:16 +08:00
Liujian 9da5e5d6c0 Merge remote-tracking branch 'github-pro/main' into feature/permit 2024-11-01 18:05:14 +08:00
Liujian a6bfce2a5f Merge remote-tracking branch 'origin/main' into feature/permit 2024-11-01 17:59:30 +08:00
Maggie 8cbeabe917 Merge pull request #128 from APIParkLab/feature/v1.2
Feature/v1.2
2024-11-01 16:45:24 +08:00
杨梦洁 1db354077f Merge branch 'feature/v1.2' into 'main'
fix: Modify consumer list reload and subscriber option

See merge request apipark/APIPark!78
2024-11-01 16:44:48 +08:00
maggieyyy e31d41a276 fix: Modify consumer list reload and subscriber option 2024-11-01 16:42:34 +08:00
Liujian 21d2abf716 Merge remote-tracking branch 'origin/main' into feature/permit 2024-11-01 16:19:35 +08:00
Liujian 3a86a88870 update subscribe permit 2024-11-01 16:19:25 +08:00
杨梦洁 6117a840e1 Merge branch 'feature/v1.2' into 'main'
fix: Add team selector to consumer

See merge request apipark/APIPark!77
2024-11-01 15:49:53 +08:00
maggieyyy 0e20987fb8 fix: Add team selector to consumer 2024-11-01 15:48:51 +08:00
Dot.L 65e7cab772 Update readme-zh-tw.md 2024-11-01 15:26:33 +08:00
Dot.L 935f2ac766 Update readme-zh-cn.md 2024-11-01 15:26:15 +08:00
Dot.L 3e12d7eb9c Update readme-jp.md 2024-11-01 15:25:55 +08:00
Dot.L d6095269b7 Update README.md 2024-11-01 15:25:15 +08:00
Liujian 0c415b4e32 Merge remote-tracking branch 'github-pro/main' into feature/permit 2024-11-01 14:59:54 +08:00
杨梦洁 bc1819b368 Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!76
2024-11-01 14:51:56 +08:00
Maggie a9be016c87 Merge pull request #127 from APIParkLab/feature/v1.2
Feature/v1.2
2024-11-01 14:49:21 +08:00
maggieyyy 11da5e9d26 fix: consumer menu access bug 2024-11-01 14:48:18 +08:00
maggieyyy 2c6c194821 fix: consumer menu access bug 2024-11-01 14:48:10 +08:00
杨梦洁 aedd8b4cc6 Merge branch 'feature/v1.2' into 'main'
fix: consumer menu access bug

See merge request apipark/APIPark!75
2024-11-01 14:46:49 +08:00
maggieyyy fa2607e9b8 fix: consumer menu access bug 2024-11-01 14:45:32 +08:00
Liujian 3783e5ee5d Merge remote-tracking branch 'github-pro/main' into feature/permit 2024-11-01 14:26:37 +08:00
Maggie fd56b8ffed Merge pull request #126 from APIParkLab/feature/v1.2
Feature/v1.2
2024-11-01 14:24:51 +08:00
杨梦洁 07e288be16 Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!74
2024-11-01 14:24:14 +08:00
maggieyyy 46143c3fe0 fix: Modify department children type 2024-11-01 14:23:05 +08:00
maggieyyy 83e3cc85f2 fix: Modify department children type 2024-11-01 14:21:04 +08:00
刘健 a4b50ae60a Merge branch 'feature/permit' into 'main'
Feature/permit

See merge request apipark/APIPark!73
2024-11-01 14:19:48 +08:00
Liujian a30d0c37eb update permit bug 2024-11-01 14:19:17 +08:00
maggieyyy 64fdf59905 fix: Add empty to member transfer 2024-11-01 13:54:33 +08:00
maggieyyy 1aa3f2fb05 fix: plugin config 2024-11-01 13:39:52 +08:00
Liujian 68e8cb72d3 Merge remote-tracking branch 'origin/main' into feature/permit 2024-11-01 11:03:21 +08:00
Liujian a86d3cd65a update permit 2024-11-01 11:01:45 +08:00
杨梦洁 a4b4cbf60f Merge branch 'feature/v1.2' into 'main'
fix: Modify subscribe options

See merge request apipark/APIPark!72
2024-10-31 14:16:30 +08:00
maggieyyy 2dcb7ebd74 fix: Modify subscribe options 2024-10-31 14:14:44 +08:00
maggieyyy ad6b64ca74 fix: Modify config 2024-10-31 13:59:06 +08:00
maggieyyy d27a2b8cf3 Merge branch 'main' into feature/mf 2024-10-31 09:27:08 +08:00
杨梦洁 8fa1985feb Merge branch 'feature/v1.2' into 'main'
fix: delete log

See merge request apipark/APIPark!71
2024-10-31 09:25:28 +08:00
Maggie 451efb8d3e Merge pull request #124 from APIParkLab/feature/v1.2
fix: delete log
2024-10-31 09:25:15 +08:00
maggieyyy d6bf3139ff fix: delete log 2024-10-31 09:14:24 +08:00
Maggie 309b9ea937 Merge pull request #123 from APIParkLab/feature/v1.2
fix: Modify access
2024-10-30 18:40:10 +08:00
杨梦洁 554bff38c6 Merge branch 'feature/v1.2' into 'main'
fix: Modify access

See merge request apipark/APIPark!70
2024-10-30 18:39:59 +08:00
maggieyyy d5e6062ec9 fix: Modify access 2024-10-30 18:38:39 +08:00
maggieyyy bcb68d552f fix: Modify mock plugin config 2024-10-30 17:52:41 +08:00
maggieyyy 3de87723ae fix: Modify mock plugin config 2024-10-30 17:51:10 +08:00
maggieyyy 535d70ac5a merge 2024-10-30 14:24:16 +08:00
Maggie d588d43aa1 Merge pull request #122 from APIParkLab/feature/v1.2
fix: Modify local path
2024-10-30 10:59:54 +08:00
Liujian f455cecb54 update ap-account version 2024-10-30 10:45:58 +08:00
杨梦洁 b88c0a9305 Merge branch 'feature/v1.2' into 'main'
fix: Modify local path

See merge request apipark/APIPark!69
2024-10-30 09:08:35 +08:00
maggieyyy 9030cff8ba fix: Modify local path 2024-10-29 18:32:45 +08:00
杨梦洁 98f73f799a Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!68
2024-10-29 17:46:36 +08:00
JackLiu d2e428ada8 Merge pull request #121 from rolealiu/main
Update v1.2 translation
2024-10-29 17:29:21 +08:00
JackLiu 74156ec84c Merge branch 'APIParkLab:main' into main 2024-10-29 17:28:51 +08:00
HaoZhen Liu f9e6bc92d7 update v1.2 translation 2024-10-29 17:28:41 +08:00
Maggie 9884586cc9 Merge pull request #120 from APIParkLab/feature/v1.2
Feature/v1.2
2024-10-29 17:05:29 +08:00
杨梦洁 ac717a6efb Merge branch 'feature/v1.2' into 'main'
fix: Add psw to account

See merge request apipark/APIPark!67
2024-10-29 16:47:44 +08:00
maggieyyy 2b7c1ded15 fix: Add psw to account 2024-10-29 16:44:31 +08:00
杨梦洁 daac27712d Merge branch 'feature/v1.2' into 'main'
fix: Modify member transfer icon and department

See merge request apipark/APIPark!66
2024-10-29 15:13:44 +08:00
maggieyyy 1080f33282 fix: Modify member transfer icon and department 2024-10-29 15:12:43 +08:00
Dot.L cc5c0a0a89 Merge pull request #119 from APIParkLab/feature/permission
update i18n
2024-10-29 14:20:19 +08:00
Liujian 85971447e1 update i18n 2024-10-29 14:19:36 +08:00
杨梦洁 256e8ef275 Merge branch 'feature/v1.2' into 'main'
fix: update translation

See merge request apipark/APIPark!65
2024-10-29 13:54:49 +08:00
Maggie c07e54ec03 Merge pull request #118 from APIParkLab/feature/v1.2
fix: update translation
2024-10-29 13:54:42 +08:00
maggieyyy 70f834e1cf fix: update translation 2024-10-29 13:52:48 +08:00
Maggie c320c6f2a2 Merge pull request #117 from APIParkLab/feature/v1.2
Feature/v1.2
2024-10-29 13:39:34 +08:00
杨梦洁 256eb60df8 Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!64
2024-10-29 13:39:06 +08:00
maggieyyy 5f03973bf2 fix: Modify guide link 2024-10-29 13:37:38 +08:00
maggieyyy ab2a0d8ae2 fix: Modify member transfer 2024-10-29 11:58:09 +08:00
Maggie 7e48402591 Merge pull request #116 from APIParkLab/feature/v1.2
fix: account translation
2024-10-29 09:21:36 +08:00
杨梦洁 aa85f6fcd1 Merge branch 'feature/v1.2' into 'main'
fix: account translation

See merge request apipark/APIPark!62
2024-10-29 09:21:19 +08:00
maggieyyy cd441ccfe7 fix: account translation 2024-10-29 09:19:35 +08:00
Dot.L 47f6519006 Merge pull request #114 from APIParkLab/feature/permission
update remark
2024-10-28 18:41:17 +08:00
Maggie 9679376cb2 Merge pull request #113 from APIParkLab/feature/v1.2
fix: table fields
2024-10-28 18:40:48 +08:00
杨梦洁 efa97c3bbc Merge branch 'feature/v1.2' into 'main'
fix: table fields

See merge request apipark/APIPark!61
2024-10-28 18:40:09 +08:00
Liujian 087e598be0 update remark 2024-10-28 18:39:55 +08:00
Dot.L 31aa8243ee Merge pull request #112 from APIParkLab/feature/permission
Feature/permission
2024-10-28 18:34:19 +08:00
Liujian d3d05ef539 Merge remote-tracking branch 'github-pro/main' into feature/permission
# Conflicts:
#	resources/access/access.yaml
2024-10-28 18:33:42 +08:00
maggieyyy d6062ea4e7 fix: table fields 2024-10-28 18:32:20 +08:00
Liujian 7949748951 update permission 2024-10-28 18:27:37 +08:00
Liujian 94f7392060 update access 2024-10-28 16:48:18 +08:00
Maggie 0d737bad57 Merge pull request #111 from APIParkLab/feature/v1.2
Feature/v1.2
2024-10-28 16:22:58 +08:00
杨梦洁 1225db50c9 Merge branch 'feature/v1.2' into 'main'
fix: Modify access fields

See merge request apipark/APIPark!59
2024-10-28 16:17:09 +08:00
maggieyyy e5d85bb3df fix: Modify access fields 2024-10-28 16:15:38 +08:00
杨梦洁 0f09e5c236 Merge branch 'feature/v1.2' into 'main'
fix: cosumer list bug

See merge request apipark/APIPark!58
2024-10-28 16:01:48 +08:00
maggieyyy 0f0204b647 fix: cosumer list bug 2024-10-28 16:00:15 +08:00
杨梦洁 94421d2622 Merge branch 'feature/v1.2' into 'main'
fix: Consumer list bugs

See merge request apipark/APIPark!57
2024-10-28 15:45:11 +08:00
maggieyyy 44f0b70461 fix: Consumer list bugs 2024-10-28 15:44:10 +08:00
刘健 f58768237b Merge branch 'feature/translate' into 'main'
Feature/translate

See merge request apipark/APIPark!56
2024-10-28 15:15:00 +08:00
Dot.L 588cf839e3 Merge pull request #110 from APIParkLab/feature/translate
Feature/translate
2024-10-28 15:07:35 +08:00
Liujian 76872b3a97 Merge remote-tracking branch 'github-pro/main' into feature/translate
# Conflicts:
#	.github/ISSUE_TEMPLATE/bug_report.yml
#	.github/ISSUE_TEMPLATE/request_help.yml
2024-10-28 15:06:24 +08:00
Dot.L d253d68a12 Merge pull request #96 from sunanzhi/main
fix:issue template content
2024-10-28 15:03:56 +08:00
Liujian 4de0d29f30 update access 2024-10-28 15:03:04 +08:00
Maggie 0f0480db63 Merge pull request #108 from APIParkLab/feature/v1.2
Feature/v1.2
2024-10-28 15:02:37 +08:00
杨梦洁 aa40b62e0a Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!55
2024-10-28 15:02:10 +08:00
maggieyyy 68309ac582 fix: Modify access 2024-10-28 15:00:59 +08:00
maggieyyy 522183eb4d fix: update message 2024-10-28 13:39:47 +08:00
maggieyyy 4f6fee4d73 fix: Delete operation from team & Delete comments from publish 2024-10-28 11:52:51 +08:00
Liujian c5fecf73c8 update action 2024-10-28 10:53:28 +08:00
Liujian ee8e93d1e8 update action 2024-10-28 10:46:17 +08:00
Liujian 9d0fa5ea4a Merge remote-tracking branch 'origin/main' into feature/translate 2024-10-28 10:43:23 +08:00
Dot.L b0b9affbe7 Merge pull request #1 from Dot-Liu/feature/translate
Feature/translate
2024-10-28 10:01:46 +08:00
Liujian 1d1c7c78e8 update go.mod 2024-10-28 10:00:34 +08:00
杨梦洁 d7f5b87e70 Merge branch 'feature/v1.2' into 'main'
fix: Modify publish and title & add debug

See merge request apipark/APIPark!54
2024-10-28 09:59:00 +08:00
maggieyyy 1048666972 fix: Modify publish and title & add debug 2024-10-28 09:58:04 +08:00
Liujian 217f5d61c7 Merge remote-tracking branch 'origin/main' into feature/translate 2024-10-27 00:48:03 +08:00
Liujian 2f1677f581 role update 2024-10-27 00:47:48 +08:00
刘健 afd7ea6a1c Merge branch 'feature/translate' into 'main'
create ai service add provider

See merge request apipark/APIPark!53
2024-10-26 21:25:40 +08:00
Liujian 7155e14e64 create ai service add provider 2024-10-26 21:25:13 +08:00
刘健 68e5b49f46 Merge branch 'feature/translate' into 'main'
update translate

See merge request apipark/APIPark!52
2024-10-25 18:47:48 +08:00
Liujian c01e95c716 update translate 2024-10-25 18:45:38 +08:00
杨梦洁 ffa7e5130e Merge branch 'feature/v1.2' into 'main'
fix: update translation

See merge request apipark/APIPark!51
2024-10-25 16:35:30 +08:00
Maggie 7ce33bfd4c Merge pull request #101 from APIParkLab/feature/v1.2
fix: update translation
2024-10-25 16:35:27 +08:00
maggieyyy 0c64fa9986 fix: update translation 2024-10-25 16:34:06 +08:00
Maggie 07e8b347e1 Merge pull request #100 from APIParkLab/feature/v1.2
fix: update translation
2024-10-25 16:20:02 +08:00
杨梦洁 38a402adef Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!50
2024-10-25 16:18:59 +08:00
maggieyyy 7ab2e29101 fix: update translation 2024-10-25 16:16:15 +08:00
Maggie b96796793b Merge pull request #99 from APIParkLab/docs-update-frontend-README
docs: Update README.md
2024-10-25 15:48:21 +08:00
Scarqin baa073b05d Update README.md 2024-10-25 15:44:42 +08:00
Maggie c944f9de52 Merge pull request #95 from APIParkLab/feature/v1.2
fix: Remove guest's account setting & update translation
2024-10-25 15:24:37 +08:00
杨梦洁 5567a65e4b Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!49
2024-10-25 15:23:51 +08:00
maggieyyy c6c775f436 fix: Remove guest's account setting & update translation 2024-10-25 15:22:06 +08:00
sunanzhi c0c43be6b9 fix:issue template content 2024-10-25 15:16:47 +08:00
刘健 9b6074318a Merge branch 'feature/issue-templete' into 'main'
update ao-account dependent version

See merge request apipark/APIPark!48
2024-10-25 14:32:33 +08:00
Liujian ada121015d update ao-account dependent version 2024-10-25 14:32:11 +08:00
JackLiu 9d7b6c4bc6 Merge pull request #86 from rolealiu/main
update translation
2024-10-25 14:31:56 +08:00
JackLiu c262fc4f4d Merge branch 'APIParkLab:main' into main 2024-10-25 14:31:26 +08:00
刘健 335317bd86 Merge branch 'feature/issue-templete' into 'main'
update ao-account dependent version

See merge request apipark/APIPark!47
2024-10-25 14:31:21 +08:00
Liujian 396464c573 update ao-account dependent version 2024-10-25 14:30:58 +08:00
HaoZhen Liu d93c76ca36 update translation 2024-10-25 14:30:57 +08:00
刘健 60ad6007e5 Merge branch 'feature/issue-templete' into 'main'
Feature/issue templete

See merge request apipark/APIPark!46
2024-10-25 14:22:21 +08:00
Liujian ceee520102 tmp commit 2024-10-25 14:21:50 +08:00
Maggie a053557000 Merge pull request #83 from APIParkLab/feature/v1.2
fix: AI Model config bug
2024-10-25 14:07:39 +08:00
杨梦洁 e7c14b0817 Merge branch 'feature/v1.2' into 'main'
fix: AI Model config bug

See merge request apipark/APIPark!45
2024-10-25 14:04:00 +08:00
maggieyyy 727fd0bd36 fix: AI Model config bug 2024-10-25 14:02:23 +08:00
Maggie 4ba2b0c9ab Merge pull request #70 from APIParkLab/feature/v1.2
fix: update translation
2024-10-25 09:28:32 +08:00
杨梦洁 b3672b8c66 Merge branch 'feature/v1.2' into 'main'
Feature/v1.2

See merge request apipark/APIPark!44
2024-10-25 09:28:13 +08:00
maggieyyy b33dda4a32 fix: update translation 2024-10-25 09:27:00 +08:00
刘健 450e565718 Merge branch 'feature/ai-service' into 'main'
Feature/ai service

See merge request apipark/APIPark!43
2024-10-24 23:43:36 +08:00
Liujian 00bd72c1e4 fix get llm error 2024-10-24 23:42:55 +08:00
Dot.L 81da1f45f1 Merge pull request #68 from APIParkLab/feature/ai-service
Feature/ai service
2024-10-24 23:31:32 +08:00
Liujian fd3d6433e2 finish fake ai 2024-10-24 23:30:25 +08:00
Liujian 9592a0ddda Merge remote-tracking branch 'origin/main' into feature/ai-service 2024-10-23 18:41:14 +08:00
maggieyyy bf2aefe2da fix: route config 2024-10-22 14:05:12 +08:00
maggieyyy 540a31f237 fix: aiservice router 2024-10-22 10:20:34 +08:00
maggieyyy 0505045c81 fix: router config 2024-10-21 17:41:30 +08:00
maggieyyy 1a18d79d94 fix: Add MF 2024-10-21 14:41:49 +08:00
595 changed files with 42089 additions and 25676 deletions
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
echo "Build frontend..." echo "Build frontend..."
cd ./frontend && pnpm run build cd ./frontend && pnpm run build
- name: upload frontend release - name: upload frontend release
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: frontend-package name: frontend-package
path: frontend/dist path: frontend/dist
@@ -41,7 +41,7 @@ jobs:
- name: Checkout #Checkout代码 - name: Checkout #Checkout代码
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: download frontend release - name: download frontend release
uses: actions/download-artifact@v2 uses: actions/download-artifact@v3
with: with:
name: frontend-package name: frontend-package
path: frontend/dist path: frontend/dist
@@ -71,7 +71,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: download frontend release - name: download frontend release
uses: actions/download-artifact@v2 uses: actions/download-artifact@v3
with: with:
name: frontend-package name: frontend-package
path: frontend/dist path: frontend/dist
+1 -1
View File
@@ -3,4 +3,4 @@
/config.yml /config.yml
/build/ /build/
/apipark /apipark
/aoplatform .gitlab-ci.yml
+1 -1
View File
@@ -2,7 +2,7 @@ variables:
PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
GOROOT: /opt/go-1.21/go GOROOT: /opt/go-1.21/go
GOPROXY: https://goproxy.cn GOPROXY: https://goproxy.cn
VERSION: $CI_COMMIT_SHORT_SHA VERSION: $CI_COMMIT_SHORT_SHA
APP: apipark APP: apipark
APP_PRE: ${APP}_${VERSION} APP_PRE: ${APP}_${VERSION}
BUILD_DIR: ${APP}-build BUILD_DIR: ${APP}-build
+1 -1
View File
@@ -197,7 +197,7 @@ To achieve this goal, we plan to add new features to APIPark, including:
<br> <br>
# 📕 Documentation # 📕 Documentation
Visit [APIPark Documentation](https://docs.apipark.com/docs/install) for detailed installation guides, API references, and usage instructions. Visit [APIPark Documentation](https://docs.apipark.com/docs/deploy) for detailed installation guides, API references, and usage instructions.
<br> <br>
+7
View File
@@ -40,7 +40,14 @@ func (c *Config) Check(cfg string) error {
} }
func (c *Config) GenConfig(target string, origin string) (string, error) { func (c *Config) GenConfig(target string, origin string) (string, error) {
if target == "" {
target = "{}"
}
if origin == "" {
origin = "{}"
}
var targetData map[string]interface{} var targetData map[string]interface{}
err := json.Unmarshal([]byte(target), &targetData) err := json.Unmarshal([]byte(target), &targetData)
if err != nil { if err != nil {
return "", err return "", err
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 512 512" xml:space="preserve">
<style type="text/css">
<![CDATA[
.st0{fill:#000000;}
]]>
</style>
<g>
<path class="st0" d="M485.016,220.691c12.422-8.422,22.188-57.266,7.797-87.016c-26.859,28.125-80.109,49.859-142,72.922
c-64.656,24.078-71.531,41.859-94.813,42.172c-23.297-0.313-30.156-18.094-94.813-42.172c-61.891-23.063-115.141-44.797-142-72.922
c-14.406,29.75-4.625,78.594,7.781,87.016C13.484,214.332,0,210.051,0,210.051s3.813,68.172,33.25,106.563
c24.953,32.547,86.984,72.906,145.828,58.828c62.219-14.875,62.344-41.406,76.922-41.266c14.563-0.141,14.688,26.391,76.906,41.266
c58.844,14.078,120.875-26.281,145.844-58.828C508.156,278.223,512,210.051,512,210.051S498.5,214.332,485.016,220.691z
M131.438,318.848c-25.109-11.641-46.063-37.422-43.188-73.891c12.813,5.078,48.844,9.406,69.094,17.281
c23,8.953,46.031,23.984,48.922,41.25C205.313,317.879,170.75,337.066,131.438,318.848z M380.563,318.848
c-39.344,18.219-73.875-0.969-74.828-15.359c2.891-17.266,25.891-32.297,48.922-41.25c20.25-7.875,56.281-12.203,69.094-17.281
C426.625,281.426,405.656,307.207,380.563,318.848z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 512 512" xml:space="preserve">
<style type="text/css">
<![CDATA[
.st0{fill:#000000;}
]]>
</style>
<g>
<path class="st0" d="M485.016,220.691c12.422-8.422,22.188-57.266,7.797-87.016c-26.859,28.125-80.109,49.859-142,72.922
c-64.656,24.078-71.531,41.859-94.813,42.172c-23.297-0.313-30.156-18.094-94.813-42.172c-61.891-23.063-115.141-44.797-142-72.922
c-14.406,29.75-4.625,78.594,7.781,87.016C13.484,214.332,0,210.051,0,210.051s3.813,68.172,33.25,106.563
c24.953,32.547,86.984,72.906,145.828,58.828c62.219-14.875,62.344-41.406,76.922-41.266c14.563-0.141,14.688,26.391,76.906,41.266
c58.844,14.078,120.875-26.281,145.844-58.828C508.156,278.223,512,210.051,512,210.051S498.5,214.332,485.016,220.691z
M131.438,318.848c-25.109-11.641-46.063-37.422-43.188-73.891c12.813,5.078,48.844,9.406,69.094,17.281
c23,8.953,46.031,23.984,48.922,41.25C205.313,317.879,170.75,337.066,131.438,318.848z M380.563,318.848
c-39.344,18.219-73.875-0.969-74.828-15.359c2.891-17.266,25.891-32.297,48.922-41.25c20.25-7.875,56.281-12.203,69.094-17.281
C426.625,281.426,405.656,307.207,380.563,318.848z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,30 @@
provider: fakegpt
label:
en_US: FakeGPT
description:
en_US: FakeGPT is a fake model provider that does not actually connect to any model service. It is useful for testing and development purposes.
icon_small:
en_US: icon_s_en.svg
icon_large:
en_US: icon_l_en.svg
background: "#FCFDFF"
help:
title:
en_US: Get your Access Details from Google
url:
en_US: https://apipark.com
supported_model_types:
- llm
- text-embedding
configurate_methods:
- predefined-model
provider_credential_schema:
credential_form_schemas:
- variable: apikey
label:
en_US: API Key
type: secret-input
required: true
placeholder:
en_US: Enter your API key
address: https://apipark.com
@@ -0,0 +1,51 @@
model: fakegpt-1.0
label:
en_US: FakeGPT 1.0
model_type: llm
features:
- agent-thought
- vision
model_properties:
mode: chat
context_size: 200000
parameter_rules:
- name: max_tokens
use_template: max_tokens
required: true
type: int
default: 4096
min: 1
max: 4096
help:
zh_Hans: 停止前生成的最大令牌数。
en_US: The maximum number of tokens to generate before stopping.
- name: temperature
use_template: temperature
required: false
type: float
default: 1
min: 0.0
max: 1.0
help:
zh_Hans: 生成内容的随机性。
en_US: The amount of randomness injected into the response.
- name: top_p
required: false
type: float
default: 0.999
min: 0.000
max: 1.000
help:
zh_Hans: 在核采样中,按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
en_US: In nucleus sampling, computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
- name: top_k
required: false
type: int
default: 0
min: 0
# tip docs from aws has error, max value is 500
max: 500
help:
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
@@ -69,15 +69,6 @@ provider_credential_schema:
placeholder: placeholder:
zh_Hans: 在此输入您的 API Key zh_Hans: 在此输入您的 API Key
en_US: Enter your API Key en_US: Enter your API Key
- variable: openai_organization
label:
zh_Hans: 组织 ID
en_US: Organization
type: text-input
required: false
placeholder:
zh_Hans: 在此输入您的组织 ID
en_US: Enter your Organization ID
- variable: openai_api_base - variable: openai_api_base
label: label:
zh_Hans: API Base zh_Hans: API Base
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 2048 default: 2048
@@ -23,7 +23,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
@@ -24,7 +24,7 @@ parameter_rules:
use_template: presence_penalty use_template: presence_penalty
- name: frequency_penalty - name: frequency_penalty
use_template: frequency_penalty use_template: frequency_penalty
- name: max_output_tokens - name: max_tokens
use_template: max_tokens use_template: max_tokens
required: true required: true
default: 8192 default: 8192
+1
View File
@@ -0,0 +1 @@
/config.yml
+77
View File
@@ -0,0 +1,77 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/eolinker/go-common/autowire"
nsq "github.com/nsqio/go-nsq"
"github.com/eolinker/go-common/cftool"
_ "github.com/eolinker/go-common/store/store_mysql"
_ "github.com/go-sql-driver/mysql"
)
var (
version string
confPath string
)
func init() {
flag.StringVar(&confPath, "c", "config.yml", "`config` file path for server ")
}
type ServerConfig struct {
Port int `yaml:"port"`
}
func main() {
// 1. 连接 MySQL 数据库
cftool.Register[ServerConfig](fmt.Sprintf("root:%s", confPath))
cftool.ReadFile(confPath)
handler := &NSQHandler{}
autowire.Autowired(handler)
err := autowire.CheckComplete()
if err != nil {
log.Fatal("check autowired:", err)
return
}
// 2. 创建 NSQ 消费者
config := nsq.NewConfig()
hostname, err := os.Hostname()
if err != nil {
log.Fatalf("Failed to get hostname: %v", err)
return
}
nsqConfig := handler.nsqConfig
consumer, err := nsq.NewConsumer(fmt.Sprintf("%s_ai_event", nsqConfig.TopicPrefix), hostname, config)
if err != nil {
log.Fatalf("Failed to create NSQ consumer: %v", err)
}
consumer.AddHandler(handler)
// 4. 连接到 NSQ
//nsqAddress := "172.18.166.219:9150" // NSQ 地址
err = consumer.ConnectToNSQD(nsqConfig.Addr)
if err != nil {
log.Fatalf("Failed to connect to NSQ: %v", err)
}
log.Println("Connected to NSQ")
// 5. 捕获系统信号,优雅关闭
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
// 优雅停止消费者
consumer.Stop()
<-consumer.StopChan
log.Println("NSQ Consumer stopped")
}
+161
View File
@@ -0,0 +1,161 @@
package main
import (
"context"
"encoding/json"
"log"
"strings"
"time"
"github.com/eolinker/go-common/cftool"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/store"
"github.com/APIParkLab/APIPark/service/ai"
ai_key "github.com/APIParkLab/APIPark/service/ai-key"
nsq "github.com/nsqio/go-nsq"
ai_api "github.com/APIParkLab/APIPark/service/ai-api"
)
func init() {
cftool.Register[NSQConfig]("nsq")
}
type NSQConfig struct {
Addr string `json:"addr"`
TopicPrefix string `json:"topic_prefix"`
}
// 定义 NSQ 消息结构
type AIProviderStatus struct {
Provider string `json:"provider"`
Model string `json:"model"`
Key string `json:"key"`
Status string `json:"status"`
}
type AIInfo struct {
Model string `json:"ai_model"`
Cost interface{} `json:"ai_model_cost"`
InputToken interface{} `json:"ai_model_input_token"`
OutputToken interface{} `json:"ai_model_output_token"`
TotalToken interface{} `json:"ai_model_total_token"`
Provider string `json:"ai_provider"`
ProviderStats []AIProviderStatus `json:"ai_provider_statuses"`
}
type NSQMessage struct {
AI AIInfo `json:"ai"`
API string `json:"api"`
Provider string `json:"provider"`
RequestID string `json:"request_id"`
TimeISO8601 string `json:"time_iso8601"`
}
// NSQHandler 处理 NSQ 消息并写入 MySQL
type NSQHandler struct {
apiUseService ai_api.IAPIUseService `autowired:""`
aiKeyService ai_key.IKeyService `autowired:""`
aiService ai.IProviderService `autowired:""`
transaction store.ITransaction `autowired:""`
nsqConfig *NSQConfig `autowired:""`
ctx context.Context
}
func convertInt(value interface{}) int {
switch v := value.(type) {
case int:
return v
case float64:
return int(v)
default:
return 0
}
}
// HandleMessage 处理从 NSQ 读取的消息
func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
log.Printf("Received message: %s", string(message.Body))
// 解析消息为结构体
var data NSQMessage
err := json.Unmarshal(message.Body, &data)
if err != nil {
log.Printf("Failed to unmarshal message: %v", err)
return err
}
// 将时间字符串转换为 time.Time
timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601)
if err != nil {
log.Printf("Failed to parse timestamp: %v", err)
return err
}
day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location())
hour := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), 0, 0, 0, timestamp.Location())
minute := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), 0, 0, timestamp.Location())
return h.transaction.Transaction(context.Background(), func(ctx context.Context) error {
finalStatus := &AIProviderStatus{}
for _, s := range data.AI.ProviderStats {
status := ToKeyStatus(s.Status).Int()
keys := strings.Split(s.Key, "@")
key := keys[0]
err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{
Status: &status,
})
if err != nil {
log.Printf("Failed to save AI key: %v", err)
return err
}
if s.Provider != data.AI.Provider {
pStatus := ai_dto.ProviderAbnormal.Int()
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
Status: &pStatus,
})
} else {
pStatus := ai_dto.ProviderEnabled.Int()
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
Status: &pStatus,
})
}
finalStatus = &s
}
if finalStatus != nil {
keys := strings.Split(finalStatus.Key, "@")
err = h.aiKeyService.IncrUseToken(ctx, keys[0], convertInt(data.AI.TotalToken))
if err != nil {
log.Printf("Failed to increment AI key token: %v", err)
return err
}
}
// 调用 AI API 接口
err = h.apiUseService.Incr(context.Background(), &ai_api.IncrAPIUse{
API: data.API,
Service: data.Provider,
Provider: data.AI.Provider,
Model: data.AI.Model,
Day: day.Unix(),
Hour: hour.Unix(),
Minute: minute.Unix(),
InputToken: convertInt(data.AI.InputToken),
OutputToken: convertInt(data.AI.OutputToken),
TotalToken: convertInt(data.AI.TotalToken),
})
if err != nil {
log.Printf("Failed to call AI API: %v", err)
return err
}
log.Printf("Message processed and saved to MySQL: %+v", data)
return nil
})
}
+34
View File
@@ -0,0 +1,34 @@
package main
import ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
var (
StatusNormal = "normal"
StatusInvalidRequest = "invalid request"
StatusQuotaExhausted = "quota exhausted"
StatusExpired = "expired"
StatusExceeded = "exceeded"
StatusInvalid = "invalid"
StatusTimeout = "timeout"
)
func ToKeyStatus(status string) ai_key_dto.KeyStatus {
switch status {
case StatusNormal:
return ai_key_dto.KeyNormal
case StatusInvalidRequest:
return ai_key_dto.KeyNormal
case StatusQuotaExhausted:
return ai_key_dto.KeyExceed
case StatusExpired:
return ai_key_dto.KeyExpired
case StatusExceeded:
return ai_key_dto.KeyNormal
case StatusInvalid:
return ai_key_dto.KeyError
case StatusTimeout:
return ai_key_dto.KeyError
default:
return ai_key_dto.KeyNormal
}
}
+9 -10
View File
@@ -2,7 +2,6 @@ package ai_api
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"github.com/APIParkLab/APIPark/model/plugin_model" "github.com/APIParkLab/APIPark/model/plugin_model"
@@ -27,7 +26,7 @@ type imlAPIController struct {
} }
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) { func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) {
info, err := i.serviceModule.Get(ctx, serviceId) _, err := i.serviceModule.Get(ctx, serviceId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -52,7 +51,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
plugins["ai_formatter"] = api.PluginSetting{ plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{ Config: plugin_model.ConfigType{
"model": input.AiModel.Id, "model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), "provider": input.AiModel.Provider,
"config": input.AiModel.Config, "config": input.AiModel.Config,
}, },
} }
@@ -73,8 +72,8 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
Retry: input.Retry, Retry: input.Retry,
Plugins: plugins, Plugins: plugins,
}, },
Upstream: info.Provider.Id, //Upstream: input.AiModel.Provider,
Disable: false, Disable: false,
}) })
return err return err
@@ -86,7 +85,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
} }
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) { func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) {
info, err := i.serviceModule.Get(ctx, serviceId) _, err := i.serviceModule.Get(ctx, serviceId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -101,16 +100,16 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
Retry: apiInfo.Proxy.Retry, Retry: apiInfo.Proxy.Retry,
Plugins: apiInfo.Proxy.Plugins, Plugins: apiInfo.Proxy.Plugins,
} }
var upstream *string //var upstream *string
if input.AiModel != nil { if input.AiModel != nil {
proxy.Plugins["ai_formatter"] = api.PluginSetting{ proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{ Config: plugin_model.ConfigType{
"model": input.AiModel.Id, "model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), "provider": input.AiModel.Provider,
"config": input.AiModel.Config, "config": input.AiModel.Config,
}, },
} }
upstream = &info.Provider.Id //upstream = &input.AiModel.Provider
} }
if input.AiPrompt != nil { if input.AiPrompt != nil {
@@ -128,7 +127,7 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
Path: input.Path, Path: input.Path,
Disable: input.Disable, Disable: input.Disable,
Methods: &apiInfo.Methods, Methods: &apiInfo.Methods,
Upstream: upstream, //Upstream: upstream,
}) })
if err != nil { if err != nil {
return err return err
+26
View File
@@ -0,0 +1,26 @@
package ai_key
import (
"reflect"
ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IKeyController interface {
Create(ctx *gin.Context, providerId string, input *ai_key_dto.Create) error
Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error
Delete(ctx *gin.Context, providerId string, id string) error
Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error)
List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error)
Enable(ctx *gin.Context, providerId string, id string) error
Disable(ctx *gin.Context, providerId string, id string) error
Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error
}
func init() {
autowire.Auto[IKeyController](func() reflect.Value {
return reflect.ValueOf(new(imlAIKeyController))
})
}
+69
View File
@@ -0,0 +1,69 @@
package ai_key
import (
"encoding/json"
"strconv"
ai_key "github.com/APIParkLab/APIPark/module/ai-key"
ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
"github.com/gin-gonic/gin"
)
var _ IKeyController = &imlAIKeyController{}
type imlAIKeyController struct {
module ai_key.IKeyModule `autowired:""`
}
func (i *imlAIKeyController) Enable(ctx *gin.Context, providerId string, id string) error {
return i.module.UpdateKeyStatus(ctx, providerId, id, true)
}
func (i *imlAIKeyController) Disable(ctx *gin.Context, providerId string, id string) error {
return i.module.UpdateKeyStatus(ctx, providerId, id, false)
}
func (i *imlAIKeyController) Create(ctx *gin.Context, providerId string, input *ai_key_dto.Create) error {
return i.module.Create(ctx, providerId, input)
}
func (i *imlAIKeyController) Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error {
return i.module.Edit(ctx, providerId, id, input)
}
func (i *imlAIKeyController) Delete(ctx *gin.Context, providerId string, id string) error {
return i.module.Delete(ctx, providerId, id)
}
func (i *imlAIKeyController) Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error) {
return i.module.Get(ctx, providerId, id)
}
func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error) {
p, err := strconv.Atoi(page)
if err != nil {
if page != "" {
return nil, 0, err
}
p = 1
}
ps, err := strconv.Atoi(pageSize)
if err != nil {
if pageSize != "" {
return nil, 0, err
}
ps = 20
}
ss := make([]string, 0)
if statuses != "" {
err = json.Unmarshal([]byte(statuses), &ss)
if err != nil {
return nil, 0, err
}
}
return i.module.List(ctx, providerId, keyword, p, ps, ss)
}
func (i *imlAIKeyController) Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error {
return i.module.Sort(ctx, providerId, input)
}
+14 -2
View File
@@ -1,25 +1,37 @@
package ai package ai
import ( import (
"reflect"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/autowire" "github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"reflect"
) )
type IProviderController interface { type IProviderController interface {
Providers(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error)
UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error)
SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error) SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error)
SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error)
Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error)
SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error)
LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error)
Enable(ctx *gin.Context, id string) error Enable(ctx *gin.Context, id string) error
Disable(ctx *gin.Context, id string) error Disable(ctx *gin.Context, id string) error
UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error
UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error
Sort(ctx *gin.Context, input *ai_dto.Sort) error
}
type IStatisticController interface {
APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error)
} }
func init() { func init() {
autowire.Auto[IProviderController](func() reflect.Value { autowire.Auto[IProviderController](func() reflect.Value {
return reflect.ValueOf(&imlProviderController{}) return reflect.ValueOf(&imlProviderController{})
}) })
autowire.Auto[IStatisticController](func() reflect.Value {
return reflect.ValueOf(&imlStatisticController{})
})
} }
+72 -5
View File
@@ -1,6 +1,9 @@
package ai package ai
import ( import (
"encoding/json"
"strconv"
"github.com/APIParkLab/APIPark/module/ai" "github.com/APIParkLab/APIPark/module/ai"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto" ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -14,28 +17,46 @@ type imlProviderController struct {
module ai.IProviderModule `autowired:""` module ai.IProviderModule `autowired:""`
} }
func (i *imlProviderController) Sort(ctx *gin.Context, input *ai_dto.Sort) error {
return i.module.Sort(ctx, input)
}
func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) {
return i.module.ConfiguredProviders(ctx)
}
func (i *imlProviderController) UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) {
return i.module.UnConfiguredProviders(ctx)
}
func (i *imlProviderController) SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error) { func (i *imlProviderController) SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error) {
return i.module.SimpleProviders(ctx) return i.module.SimpleProviders(ctx)
} }
func (i *imlProviderController) Providers(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) { func (i *imlProviderController) SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) {
return i.module.Providers(ctx) return i.module.SimpleConfiguredProviders(ctx)
} }
func (i *imlProviderController) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) { func (i *imlProviderController) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) {
return i.module.Provider(ctx, id) return i.module.Provider(ctx, id)
} }
func (i *imlProviderController) SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error) {
return i.module.SimpleProvider(ctx, id)
}
func (i *imlProviderController) LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) { func (i *imlProviderController) LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) {
return i.module.LLMs(ctx, driver) return i.module.LLMs(ctx, driver)
} }
func (i *imlProviderController) Enable(ctx *gin.Context, id string) error { func (i *imlProviderController) Enable(ctx *gin.Context, id string) error {
return i.module.UpdateProviderStatus(ctx, id, true) //return i.module.UpdateProviderStatus(ctx, id, true)
return nil
} }
func (i *imlProviderController) Disable(ctx *gin.Context, id string) error { func (i *imlProviderController) Disable(ctx *gin.Context, id string) error {
return i.module.UpdateProviderStatus(ctx, id, false) //return i.module.UpdateProviderStatus(ctx, id, false)
return nil
} }
func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error { func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error {
@@ -43,5 +64,51 @@ func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string
} }
func (i *imlProviderController) UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error { func (i *imlProviderController) UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error {
return i.module.UpdateProviderDefaultLLM(ctx, id, input) //return i.module.UpdateProviderDefaultLLM(ctx, id, input)
return nil
}
var _ IStatisticController = (*imlStatisticController)(nil)
type imlStatisticController struct {
module ai.IAIAPIModule `autowired:""`
}
func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) {
s, err := strconv.ParseInt(start, 10, 64)
if err != nil {
return nil, nil, 0, err
}
e, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return nil, nil, 0, err
}
p, err := strconv.Atoi(page)
if err != nil {
if page != "" {
return nil, nil, 0, err
}
p = 1
}
ps, err := strconv.Atoi(pageSize)
if err != nil {
if pageSize != "" {
return nil, nil, 0, err
}
ps = 20
}
ms := make([]string, 0)
if models != "" {
json.Unmarshal([]byte(models), &ms)
ms = append(ms, models)
}
ss := make([]string, 0)
if services != "" {
json.Unmarshal([]byte(services), &ss)
ss = append(ss, services)
}
return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true", ms, ss)
} }
+2 -2
View File
@@ -45,8 +45,8 @@ func (p *imlCluster) Check(ctx *gin.Context, input *cluster_dto.CheckCluster) ([
// return id, nil // return id, nil
//} //}
// //
//func (p *imlCluster) Search(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) { //func (p *imlCluster) SearchByDriver(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
// return p.module.Search(ctx, keyword) // return p.module.SearchByDriver(ctx, keyword)
//} //}
// //
//func (p *imlCluster) Simple(ctx *gin.Context) ([]*parition_dto.Simple, error) { //func (p *imlCluster) Simple(ctx *gin.Context) ([]*parition_dto.Simple, error) {
+21
View File
@@ -0,0 +1,21 @@
package log
import (
"reflect"
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type ILogController interface {
Save(ctx *gin.Context, driver string, input *log_dto.Save) error
Get(ctx *gin.Context, driver string) (*log_dto.LogSource, error)
}
func init() {
logController := &imlLogController{}
autowire.Auto[ILogController](func() reflect.Value {
return reflect.ValueOf(logController)
})
}
+19
View File
@@ -0,0 +1,19 @@
package log
import (
"github.com/APIParkLab/APIPark/module/log"
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
"github.com/gin-gonic/gin"
)
type imlLogController struct {
module log.ILogModule `autowired:""`
}
func (c *imlLogController) Save(ctx *gin.Context, driver string, input *log_dto.Save) error {
return c.module.Save(ctx, driver, input)
}
func (c *imlLogController) Get(ctx *gin.Context, driver string) (*log_dto.LogSource, error) {
return c.module.Get(ctx, driver)
}
+64
View File
@@ -17,6 +17,70 @@ type imlMonitorStatisticController struct {
module monitor.IMonitorStatisticModule `autowired:""` module monitor.IMonitorStatisticModule `autowired:""`
} }
func (i *imlMonitorStatisticController) Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) {
switch dataType {
case monitor_dto.DataTypeApi:
return i.module.ApiStatistics(ctx, input)
case monitor_dto.DataTypeProvider:
return i.module.ProviderStatistics(ctx, input)
case monitor_dto.DataTypeSubscriber:
return i.module.SubscriberStatistics(ctx, input)
default:
return nil, fmt.Errorf("unsupported data type: %s", dataType)
}
}
func (i *imlMonitorStatisticController) InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
switch dataType {
case monitor_dto.DataTypeApi:
return i.module.APITrend(ctx, id, input)
case monitor_dto.DataTypeProvider:
return i.module.ProviderTrend(ctx, id, input)
case monitor_dto.DataTypeSubscriber:
return i.module.SubscriberTrend(ctx, id, input)
default:
return nil, "", fmt.Errorf("unsupported data type: %s", dataType)
}
}
func (i *imlMonitorStatisticController) InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeSubscriber || dataType == monitor_dto.DataTypeSubscriber && typ == monitor_dto.DataTypeApi {
return i.module.InvokeTrendWithSubscriberAndApi(ctx, api, subscriber, input)
} else if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeProvider || dataType == monitor_dto.DataTypeProvider && typ == monitor_dto.DataTypeApi {
return i.module.InvokeTrendWithProviderAndApi(ctx, provider, api, input)
}
return nil, "", fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
}
func (i *imlMonitorStatisticController) StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error) {
switch dataType {
case monitor_dto.DataTypeApi:
switch typ {
case monitor_dto.DataTypeProvider:
return i.module.ProviderStatisticsOnApi(ctx, id, input)
case monitor_dto.DataTypeSubscriber:
return i.module.SubscriberStatisticsOnApi(ctx, id, input)
default:
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
}
case monitor_dto.DataTypeProvider:
switch typ {
case monitor_dto.DataTypeApi:
return i.module.ApiStatisticsOnProvider(ctx, id, input)
default:
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
}
case monitor_dto.DataTypeSubscriber:
switch typ {
case monitor_dto.DataTypeApi:
return i.module.ApiStatisticsOnSubscriber(ctx, id, input)
default:
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
}
}
return nil, fmt.Errorf("unsupported data type: %s", dataType)
}
func (i *imlMonitorStatisticController) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) { func (i *imlMonitorStatisticController) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) {
trend, timeInterval, err := i.module.MessageTrend(ctx, input) trend, timeInterval, err := i.module.MessageTrend(ctx, input)
if err != nil { if err != nil {
+6 -1
View File
@@ -16,7 +16,12 @@ type IMonitorStatisticController interface {
OverviewInvokeTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []int64, []int64, []int64, []int64, []float64, []float64, string, error) OverviewInvokeTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []int64, []int64, []int64, []int64, []float64, []float64, string, error)
OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error)
//Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error)
InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error)
} }
type IMonitorConfigController interface { type IMonitorConfigController interface {
+6 -1
View File
@@ -1,12 +1,13 @@
package router package router
import ( import (
"io"
api_doc "github.com/APIParkLab/APIPark/module/api-doc" api_doc "github.com/APIParkLab/APIPark/module/api-doc"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto" api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/APIParkLab/APIPark/module/router" "github.com/APIParkLab/APIPark/module/router"
router_dto "github.com/APIParkLab/APIPark/module/router/dto" router_dto "github.com/APIParkLab/APIPark/module/router/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io"
) )
var _ IRouterController = (*imlAPIController)(nil) var _ IRouterController = (*imlAPIController)(nil)
@@ -15,6 +16,10 @@ type imlAPIController struct {
module router.IRouterModule `autowired:""` module router.IRouterModule `autowired:""`
} }
func (i *imlAPIController) Simple(ctx *gin.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error) {
return i.module.SimpleAPIs(ctx, input)
}
func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*router_dto.Detail, error) { func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*router_dto.Detail, error) {
return i.module.Detail(ctx, serviceId, apiId) return i.module.Detail(ctx, serviceId, apiId)
} }
+3 -1
View File
@@ -1,9 +1,10 @@
package router package router
import ( import (
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"reflect" "reflect"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire" "github.com/eolinker/go-common/autowire"
@@ -24,6 +25,7 @@ type IRouterController interface {
Delete(ctx *gin.Context, serviceId string, apiId string) error Delete(ctx *gin.Context, serviceId string, apiId string) error
// Prefix 获取API前缀 // Prefix 获取API前缀
Prefix(ctx *gin.Context, serviceId string) (string, bool, error) Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
Simple(ctx *gin.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error)
} }
type IAPIDocController interface { type IAPIDocController interface {
+164 -35
View File
@@ -5,6 +5,19 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/eolinker/go-common/pm3"
"github.com/APIParkLab/APIPark/module/system"
"github.com/getkin/kin-openapi/openapi3"
api_doc "github.com/APIParkLab/APIPark/module/api-doc"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/eolinker/eosc/log"
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization" application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto" application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
@@ -22,7 +35,6 @@ import (
"github.com/APIParkLab/APIPark/module/service" "github.com/APIParkLab/APIPark/module/service"
service_dto "github.com/APIParkLab/APIPark/module/service/dto" service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/APIParkLab/APIPark/module/upstream" "github.com/APIParkLab/APIPark/module/upstream"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/eolinker/go-common/store" "github.com/eolinker/go-common/store"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
@@ -39,29 +51,102 @@ type imlServiceController struct {
docModule service.IServiceDocModule `autowired:""` docModule service.IServiceDocModule `autowired:""`
aiAPIModule ai_api.IAPIModule `autowired:""` aiAPIModule ai_api.IAPIModule `autowired:""`
routerModule router.IRouterModule `autowired:""` routerModule router.IRouterModule `autowired:""`
apiDocModule api_doc.IAPIDocModule `autowired:""`
providerModule ai.IProviderModule `autowired:""` providerModule ai.IProviderModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""` upstreamModule upstream.IUpstreamModule `autowired:""`
settingModule system.ISettingModule `autowired:""`
transaction store.ITransaction `autowired:""` transaction store.ITransaction `autowired:""`
} }
func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream { var (
return &upstream_dto.Upstream{ loader = openapi3.NewLoader()
Type: "http", )
Balance: "round-robin",
Timeout: 300000, func (i *imlServiceController) swagger(ctx *gin.Context, id string) (*openapi3.T, error) {
Retry: 0, doc, err := i.apiDocModule.GetDoc(ctx, id)
Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider), if err != nil {
LimitPeerSecond: 0, return nil, err
ProxyHeaders: nil,
Scheme: uri.Scheme(),
PassHost: "node",
Nodes: []*upstream_dto.NodeConfig{
{
Address: uri.Host(),
Weight: 100,
},
},
} }
tmp, err := loader.LoadFromData([]byte(doc.Content))
if err != nil {
return nil, err
}
cfg := i.settingModule.Get(ctx)
tmp.AddServer(&openapi3.Server{
URL: cfg.InvokeAddress,
})
return tmp, nil
}
func (i *imlServiceController) ExportSwagger(ctx *gin.Context) {
id, has := ctx.Params.Get("id")
if !has {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: fmt.Sprintf("id is required"),
})
return
}
s, err := i.module.Get(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
tmp, err := i.swagger(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
data, _ := tmp.MarshalJSON()
ctx.Status(200)
// 设置响应头
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.json", strings.Replace(s.Name, " ", "_", -1)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.Header("Content-Transfer-Encoding", "binary")
ctx.Writer.Write(data)
return
}
func (i *imlServiceController) Swagger(ctx *gin.Context) {
id, has := ctx.Params.Get("id")
if !has {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: fmt.Sprintf("id is required"),
})
return
}
tmp, err := i.swagger(ctx, id)
if err != nil {
ctx.JSON(200, &pm3.Response{
Code: -1,
Success: "fail",
Message: err.Error(),
})
return
}
ctx.JSON(200, tmp)
return
}
func (i *imlServiceController) Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
return i.module.Simple(ctx)
}
func (i *imlServiceController) MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
return i.module.MySimple(ctx)
} }
func (i *imlServiceController) editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) { func (i *imlServiceController) editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
@@ -151,8 +236,9 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}", Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
} }
aiModel := &ai_api_dto.AiModel{ aiModel := &ai_api_dto.AiModel{
Id: m.ID(), Id: m.ID(),
Config: m.DefaultConfig(), Config: m.DefaultConfig(),
Provider: *input.Provider,
} }
name := "Demo Translation API" name := "Demo Translation API"
description := "A demo that shows you how to use a prompt to create a Translation API." description := "A demo that shows you how to use a prompt to create a Translation API."
@@ -211,10 +297,7 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
if err != nil { if err != nil {
return err return err
} }
//_, err = i.upstreamModule.Save(ctx, info.Id, newAIUpstream(info.Id, *input.Provider, p.URI()))
//if err != nil {
// return err
//}
return i.docModule.SaveServiceDoc(ctx, info.Id, &service_dto.SaveServiceDoc{ return i.docModule.SaveServiceDoc(ctx, info.Id, &service_dto.SaveServiceDoc{
Doc: "The Translation API allows developers to translate text from one language to another. It supports multiple languages and enables easy integration of high-quality translation features into applications. With simple API requests, you can quickly translate content into different target languages.", Doc: "The Translation API allows developers to translate text from one language to another. It supports multiple languages and enables easy integration of high-quality translation features into applications. With simple API requests, you can quickly translate content into different target languages.",
}) })
@@ -227,27 +310,49 @@ func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string,
return i.module.SearchMyServices(ctx, teamId, keyword) return i.module.SearchMyServices(ctx, teamId, keyword)
} }
//func (i *imlServiceController) Simple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// return i.module.Simple(ctx, keyword)
//}
//
//func (i *imlServiceController) MySimple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// return i.module.MySimple(ctx, keyword)
//}
func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) { func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) {
now := time.Now()
defer func() {
log.Infof("get service %s cost %d ms", id, time.Since(now).Milliseconds())
}()
return i.module.Get(ctx, id) return i.module.Get(ctx, id)
} }
func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) { func (i *imlServiceController) Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error) {
return i.module.Search(ctx, teamID, keyword) return i.module.Search(ctx, teamIDs, keyword)
} }
func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) { func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
if input.Kind == "ai" { if input.Kind == "ai" {
return i.createAIService(ctx, teamID, input) return i.createAIService(ctx, teamID, input)
} }
return i.module.Create(ctx, teamID, input) var err error
var info *service_dto.Service
err = i.transaction.Transaction(ctx, func(txCtx context.Context) error {
info, err = i.module.Create(txCtx, teamID, input)
if err != nil {
return err
}
path := fmt.Sprintf("/%s/", strings.Trim(input.Prefix, "/"))
_, err = i.routerModule.Create(txCtx, info.Id, &router_dto.Create{
Id: uuid.New().String(),
Name: "",
Path: path + "*",
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
Description: "auto create by create service",
Protocols: []string{"http", "https"},
MatchRules: nil,
Upstream: "",
Proxy: &router_dto.InputProxy{
Path: path,
Timeout: 30000,
Retry: 0,
},
Disable: false,
})
return err
})
return info, err
} }
func (i *imlServiceController) Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) { func (i *imlServiceController) Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
@@ -278,6 +383,10 @@ type imlAppController struct {
authModule application_authorization.IAuthorizationModule `autowired:""` authModule application_authorization.IAuthorizationModule `autowired:""`
} }
func (i *imlAppController) SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SimpleAppItem, error) {
return i.module.SearchCanSubscribe(ctx, serviceId)
}
func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) { func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
return i.module.Search(ctx, teamId, keyword) return i.module.Search(ctx, teamId, keyword)
} }
@@ -326,3 +435,23 @@ func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto.
func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error { func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error {
return i.module.DeleteApp(ctx, appId) return i.module.DeleteApp(ctx, appId)
} }
func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream {
return &upstream_dto.Upstream{
Type: "http",
Balance: "round-robin",
Timeout: 300000,
Retry: 0,
Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider),
LimitPeerSecond: 0,
ProxyHeaders: nil,
Scheme: uri.Scheme(),
PassHost: "node",
Nodes: []*upstream_dto.NodeConfig{
{
Address: uri.Host(),
Weight: 100,
},
},
}
}
+6 -6
View File
@@ -15,7 +15,7 @@ type IServiceController interface {
Get(ctx *gin.Context, id string) (*service_dto.Service, error) Get(ctx *gin.Context, id string) (*service_dto.Service, error)
// SearchMyServices 搜索服务 // SearchMyServices 搜索服务
SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error)
// Create 创建 // Create 创建
Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error)
// Edit 编辑 // Edit 编辑
@@ -24,12 +24,11 @@ type IServiceController interface {
Delete(ctx *gin.Context, id string) error Delete(ctx *gin.Context, id string) error
ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error) ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error)
SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error
Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error)
MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error)
//createAIService(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) Swagger(ctx *gin.Context)
//editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) ExportSwagger(ctx *gin.Context)
//DeleteAIService(ctx *gin.Context, id string) error
//SearchMyAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
//SearchAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
} }
type IAppController interface { type IAppController interface {
@@ -42,6 +41,7 @@ type IAppController interface {
// SimpleApps 获取简易项目列表 // SimpleApps 获取简易项目列表
SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error) MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
SearchCanSubscribe(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
GetApp(ctx *gin.Context, appId string) (*service_dto.App, error) GetApp(ctx *gin.Context, appId string) (*service_dto.App, error)
DeleteApp(ctx *gin.Context, appId string) error DeleteApp(ctx *gin.Context, appId string) error
} }
+263
View File
@@ -0,0 +1,263 @@
package strategy
import (
"encoding/base64"
"encoding/json"
"fmt"
"sort"
"strconv"
"time"
"github.com/eolinker/go-common/utils"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
"github.com/APIParkLab/APIPark/module/service"
"github.com/APIParkLab/APIPark/module/strategy"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/gin-gonic/gin"
)
var _ IStrategyController = (*imlStrategyController)(nil)
type imlStrategyController struct {
strategyModule strategy.IStrategyModule `autowired:""`
serviceModule service.IServiceModule `autowired:""`
}
func (i *imlStrategyController) Restore(ctx *gin.Context, id string) error {
return i.strategyModule.Restore(ctx, id)
}
func (i *imlStrategyController) DeleteServiceStrategy(ctx *gin.Context, serviceId string, id string) error {
return i.strategyModule.DeleteServiceStrategy(ctx, serviceId, id)
}
func (i *imlStrategyController) ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error) {
list, err := i.strategyModule.ToPublish(ctx, driver)
if err != nil {
return nil, "", "", false, err
}
data, _ := json.Marshal(list)
source := base64.StdEncoding.EncodeToString(data)
return list, source, time.Now().Format("20060102150405") + "-release", len(list) > 0, nil
}
func (i *imlStrategyController) FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) {
f, has := strategy_filter.RemoteFilter(name)
if !has {
return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name)
}
scopeAllow := false
for _, s := range f.Scopes() {
if s == strategy_filter.ScopeGlobal {
scopeAllow = true
break
}
}
if !scopeAllow {
return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name)
}
list, total, err := f.RemoteList(ctx, "", nil, -1, -1)
if err != nil {
return nil, nil, 0, "", "", err
}
return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title {
return &strategy_dto.Title{
Field: l.Field,
Title: l.Title,
}
}), list, total, f.Key(), f.Key(), nil
}
func (i *imlStrategyController) FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) {
f, has := strategy_filter.RemoteFilter(name)
if !has {
return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name)
}
scopeAllow := false
for _, s := range f.Scopes() {
if s == strategy_filter.ScopeService {
scopeAllow = true
break
}
}
if !scopeAllow {
return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name)
}
list, total, err := f.RemoteList(ctx, "", map[string]interface{}{"service": serviceId}, -1, -1)
if err != nil {
return nil, nil, 0, "", "", err
}
return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title {
return &strategy_dto.Title{
Field: l.Field,
Title: l.Title,
}
}), list, total, f.Key(), "list", nil
}
func (i *imlStrategyController) filterOptions(ctx *gin.Context, scope string) ([]*strategy_dto.FilterOption, error) {
m, has := strategy_filter.Options(scope)
if !has {
return nil, fmt.Errorf("scope not found: %s", scope)
}
list := utils.MapToSlice(m, func(key string, value *strategy_filter.Option) *strategy_dto.FilterOption {
pattern := ""
if value.Pattern != nil {
pattern = value.Pattern.String()
}
return &strategy_dto.FilterOption{
Name: value.Name,
Title: value.Title,
Type: value.Type,
Pattern: pattern,
Options: value.Options,
}
})
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
return list, nil
}
func (i *imlStrategyController) FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) {
return i.filterOptions(ctx, strategy_filter.ScopeService)
}
func (i *imlStrategyController) FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) {
return i.filterOptions(ctx, strategy_filter.ScopeGlobal)
}
func (i *imlStrategyController) GetStrategy(ctx *gin.Context, id string) (*strategy_dto.Strategy, error) {
return i.strategyModule.Get(ctx, id)
}
func (i *imlStrategyController) search(ctx *gin.Context, keyword string, scope strategy_dto.Scope, target string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
p, err := strconv.Atoi(page)
if err != nil {
if page != "" {
return nil, 0, fmt.Errorf("page error: %s", err)
}
p = 1
}
ps, err := strconv.Atoi(pageSize)
if err != nil {
if pageSize != "" {
return nil, 0, fmt.Errorf("page size error: %s", err)
}
ps = 20
}
ss := make([]string, 0)
json.Unmarshal([]byte(sort), &ss)
fs := make([]string, 0)
json.Unmarshal([]byte(filters), &fs)
list, total, err := i.strategyModule.Search(ctx, keyword, driver, scope, target, p, ps, fs, ss...)
if err != nil {
return nil, 0, err
}
return list, total, nil
}
func (i *imlStrategyController) GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeGlobal), "", driver, page, pageSize, order, sort, filters)
}
func (i *imlStrategyController) CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error {
input.Driver = driver
input.Scope = strategy_dto.ToScope(strategy_dto.ScopeGlobal)
return i.strategyModule.Create(ctx, input)
}
func (i *imlStrategyController) PublishGlobalStrategy(ctx *gin.Context, driver string) error {
return i.strategyModule.Publish(ctx, driver, strategy_dto.ScopeGlobal, "")
}
func (i *imlStrategyController) ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeService), serviceId, driver, page, pageSize, order, sort, filters)
}
func (i *imlStrategyController) CreateServiceStrategy(ctx *gin.Context, serviceId string, driver string, input *strategy_dto.Create) error {
_, err := i.serviceModule.Get(ctx, serviceId)
if err != nil {
return fmt.Errorf("create service strategy error: %s", err)
}
input.Driver = driver
input.Scope = strategy_dto.ToScope(strategy_dto.ScopeService)
input.Target = serviceId
return i.strategyModule.Create(ctx, input)
}
func (i *imlStrategyController) EditStrategy(ctx *gin.Context, id string, input *strategy_dto.Edit) error {
return i.strategyModule.Edit(ctx, id, input)
}
func (i *imlStrategyController) EnableStrategy(ctx *gin.Context, id string) error {
return i.strategyModule.Enable(ctx, id)
}
func (i *imlStrategyController) DisableStrategy(ctx *gin.Context, id string) error {
return i.strategyModule.Disable(ctx, id)
}
func (i *imlStrategyController) DeleteStrategy(ctx *gin.Context, id string) error {
return i.strategyModule.Delete(ctx, id)
}
func genTime(t string, defaultValue time.Time) (time.Time, error) {
if t == "" {
return defaultValue, nil
}
s, err := strconv.ParseInt(t, 10, 64)
if err != nil {
return time.Time{}, err
}
return time.Unix(s, 0), nil
}
func (i *imlStrategyController) GetStrategyLogs(ctx *gin.Context, keyword string, strategyId string, start string, end string, limit string, offset string) ([]*strategy_dto.LogItem, int64, error) {
now := time.Now()
s, err := genTime(start, now.Add(-time.Hour*24*30))
if err != nil {
return nil, 0, fmt.Errorf("start time error: %s", err)
}
e, err := genTime(end, now)
if err != nil {
return nil, 0, fmt.Errorf("end time error: %s", err)
}
if s.After(e) {
return nil, 0, fmt.Errorf("start time must be less than end time")
}
l, err := strconv.ParseInt(limit, 10, 64)
if err != nil && limit != "" {
return nil, 0, err
}
o, err := strconv.ParseInt(offset, 10, 64)
if err != nil && offset != "" {
return nil, 0, err
}
if l < 1 {
l = 15
}
if o < 1 {
o = 1
}
return i.strategyModule.GetStrategyLogs(ctx, keyword, strategyId, s, e, l, o)
}
func (i *imlStrategyController) LogInfo(ctx *gin.Context, id string) (*strategy_dto.LogInfo, error) {
return i.strategyModule.StrategyLogInfo(ctx, id)
}
+48
View File
@@ -0,0 +1,48 @@
package strategy
import (
"reflect"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
)
type IStrategyController interface {
GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error)
CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error
PublishGlobalStrategy(ctx *gin.Context, driver string) error
ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error)
CreateServiceStrategy(ctx *gin.Context, serviceId string, driver string, input *strategy_dto.Create) error
EditStrategy(ctx *gin.Context, id string, input *strategy_dto.Edit) error
GetStrategy(ctx *gin.Context, id string) (*strategy_dto.Strategy, error)
EnableStrategy(ctx *gin.Context, id string) error
DisableStrategy(ctx *gin.Context, id string) error
DeleteStrategy(ctx *gin.Context, id string) error
DeleteServiceStrategy(ctx *gin.Context, serviceId string, id string) error
Restore(ctx *gin.Context, id string) error
FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error)
FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error)
FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error)
FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error)
ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error)
GetStrategyLogs(ctx *gin.Context, keyword string, strategyId string, start string, end string, limit string, offset string) ([]*strategy_dto.LogItem, int64, error)
LogInfo(ctx *gin.Context, id string) (*strategy_dto.LogInfo, error)
}
type IStrategyCommonController interface {
}
func init() {
autowire.Auto[IStrategyController](func() reflect.Value {
return reflect.ValueOf(&imlStrategyController{})
})
}
+32 -10
View File
@@ -265,7 +265,8 @@ func (i *imlInitController) OnInit() {
return fmt.Errorf("create default team error: %v", err) return fmt.Errorf("create default team error: %v", err)
} }
// 创建Rest服务 // 创建Rest服务
_, err = i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{ restPath := "/rest-demo"
serviceInfo, err := i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{
Name: "REST Demo Service", Name: "REST Demo Service",
Prefix: "/rest-demo", Prefix: "/rest-demo",
Description: "Auto created By APIPark", Description: "Auto created By APIPark",
@@ -277,6 +278,26 @@ func (i *imlInitController) OnInit() {
if err != nil { if err != nil {
return fmt.Errorf("create default service error: %v", err) return fmt.Errorf("create default service error: %v", err)
} }
path := fmt.Sprintf("/%s/", strings.Trim(restPath, "/"))
_, err = i.routerModule.Create(ctx, serviceInfo.Id, &router_dto.Create{
Id: uuid.NewString(),
Name: "",
Path: path + "*",
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
Description: "auto create by create service",
Protocols: []string{"http", "https"},
MatchRules: nil,
Upstream: "",
Proxy: &router_dto.InputProxy{
Path: path,
Timeout: 30000,
Retry: 0,
},
Disable: false,
})
if err != nil {
return fmt.Errorf("create default router error: %v", err)
}
// 创建AI服务 // 创建AI服务
err = i.createAIService(ctx, info.Id, &service_dto.CreateService{ err = i.createAIService(ctx, info.Id, &service_dto.CreateService{
Name: "AI Demo Service", Name: "AI Demo Service",
@@ -319,12 +340,12 @@ func (i *imlInitController) OnInit() {
} }
func (i *imlInitController) createAIService(ctx context.Context, teamID string, input *service_dto.CreateService) error { func (i *imlInitController) createAIService(ctx context.Context, teamID string, input *service_dto.CreateService) error {
providerId := "openai" providerId := "fakegpt"
err := i.providerModule.UpdateProviderConfig(ctx, "openai", &ai_dto.UpdateConfig{ err := i.providerModule.UpdateProviderConfig(ctx, providerId, &ai_dto.UpdateConfig{
Config: "{\n \"openai_api_base\": \"API Base\",\n \"openai_api_key\": \"API Key\",\n \"openai_organization\": \"Organization\"\n}", Config: "{\n \"apikey\": \"xxx\" \n}",
}) })
if err != nil { if err != nil {
return fmt.Errorf("update openai config error: %v", err) return fmt.Errorf("update %s config error: %v", providerId, err)
} }
input.Provider = &providerId input.Provider = &providerId
if input.Id == "" { if input.Id == "" {
@@ -381,8 +402,9 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}", Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
} }
aiModel := &ai_api_dto.AiModel{ aiModel := &ai_api_dto.AiModel{
Id: m.ID(), Id: m.ID(),
Config: m.DefaultConfig(), Config: m.DefaultConfig(),
Provider: providerId,
} }
name := "Demo Translation API" name := "Demo Translation API"
description := "A demo that shows you how to use a prompt to create a Translation API." description := "A demo that shows you how to use a prompt to create a Translation API."
@@ -415,7 +437,7 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
plugins["ai_formatter"] = api.PluginSetting{ plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{ Config: plugin_model.ConfigType{
"model": aiModel.Id, "model": aiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), "provider": info.Provider.Id,
"config": aiModel.Config, "config": aiModel.Config,
}, },
} }
@@ -435,8 +457,8 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
Retry: retry, Retry: retry,
Plugins: plugins, Plugins: plugins,
}, },
Disable: false, Disable: false,
Upstream: info.Provider.Id, //Upstream: info.Provider.Id,
}) })
if err != nil { if err != nil {
return err return err
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
build
coverage
.next
*.d.ts
*.js
+39
View File
@@ -0,0 +1,39 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
"rules": {
"react/react-in-jsx-scope": "off",
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
]
},
"settings": {
"react": {
"version": "detect"
}
}
}
+1
View File
@@ -27,4 +27,5 @@ packages/core/public/tinymce/
*.sln *.sln
*.sw? *.sw?
/pnpm-lock.yaml /pnpm-lock.yaml
+11
View File
@@ -0,0 +1,11 @@
{
"printWidth": 120,
"useTabs": false,
"tabWidth": 2,
"semi": false,
"bracketSpacing": true,
"arrowParens": "always",
"trailingComma": "none",
"singleQuote": true,
"bracketLine": true
}
+45
View File
@@ -0,0 +1,45 @@
Create detailed components with these requirements:
1. Use 'use client' directive for client-side components
2. Style with Tailwind CSS utility classes for responsive design
3. Use React Router for navigation
4. Use Ant Design for UI components
5. Use iconify React for icons (from @iconify/react package). Do NOT use other UI libraries unless requested
6. Use local photos from public folder where appropriate, only valid URLs you know exist
7. Create root layout.tsx page that wraps necessary navigation items to all pages
8. MUST implement the navigation elements items in their rightful place i.e. Left sidebar, Top header
9. Accurately implement necessary grid layouts
10. Follow proper import practices:
- Use @/ path aliases
- Keep component imports organized
- Update current packages/core/src/pages/Root.tsx with new comprehensive code
- Don't forget root route (page.tsx) handling
- You MUST complete the entire prompt before stopping
11. Table component should use `import PageList from "@common/components/aoplatform/PageList.tsx"`
12. PageList component MUST use addNewBtnTitle for add button, NOT toolBarRender. Example:
<PageList
id="global_team"
className="pl-btnbase"
ref={pageListRef}
columns = {[...columns]}
request = {()=>getTeamList()}
showPagination={false}
addNewBtnTitle={$t('添加团队')}
addNewBtnAccess = "system.organization.team.add"
searchPlaceholder={$t("输入名称、ID、负责人查找团队")}
onAddNewBtnClick={()=>{openModal('add')}}
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))}
/>
13. use `const { fetchData } = useFetch()` to fetch http data,such as
```tsx
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setUserInfo(data.profile)
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
```
14. can't not import new package!
+12 -5
View File
@@ -1,10 +1,17 @@
# 部署 # 部署
## 安装依赖 ## 安装依赖
建议使用pnpm 建议使用 pnpm
`npm install -g pnpm` ```
使用pnpm安装依赖 npm install -g pnpm
`pnpm install` ```
使用pnpm安装依赖
```
pnpm install
```
## 编译 ## 编译
`pnpm run build` ```
pnpm run build
```
-17
View File
@@ -1,17 +0,0 @@
# 部署
## 代码同步
packages目录下,部分子项目为企业版独有,不要同步到开源版:
packages/businessEntry, packages/openApi, packages/systemRunning, README.pro.md
## 安装依赖
建议使用pnpm
`npm install -g pnpm`
使用pnpm安装依赖
`pnpm install`
## 编译
### 开源版本
`pnpm run build`
### 企业版本
`pnpm run build:pro`
+14 -18
View File
@@ -6,7 +6,7 @@ const systemLanguage = {
en_US: 'en-US', en_US: 'en-US',
zh_CN: 'zh-CN', zh_CN: 'zh-CN',
ja_JP: 'ja-JP', ja_JP: 'ja-JP',
zh_TW: 'zh-TW' zh_TW: 'zh-TW',
}; };
const localesDir = 'packages/common/src/locales/scan'; const localesDir = 'packages/common/src/locales/scan';
const newJsonDir = 'packages/common/src/locales/scan/newJson'; const newJsonDir = 'packages/common/src/locales/scan/newJson';
@@ -19,7 +19,7 @@ fs.readdirSync(localesDir).forEach(file => {
const lang = path.basename(file, '.json'); const lang = path.basename(file, '.json');
const filePath = path.join(localesDir, file); const filePath = path.join(localesDir, file);
try { try {
console.log('Current working directory:', process.cwd(),filePath); console.log('Current working directory:', process.cwd(), filePath);
const existJsonData = fs.readFileSync(filePath); const existJsonData = fs.readFileSync(filePath);
existData[lang] = JSON.parse(existJsonData); existData[lang] = JSON.parse(existJsonData);
} catch (error) { } catch (error) {
@@ -36,20 +36,18 @@ fs.readdirSync(localesDir).forEach(file => {
const keyList = Object.keys(existData); const keyList = Object.keys(existData);
// 清空 newJson 目录下的所有语言文件 // 清空 newJson 目录下的所有语言文件
Object.values(systemLanguage).forEach(lng => { Object.values(systemLanguage).forEach(lng => {
const newJsonPath = path.join(newJsonDir, `${lng}.json`); const newJsonPath = path.join(newJsonDir, `${lng}.json`);
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件 fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
}); });
module.exports = { module.exports = {
input: [ input: [
'packages/*/src/**/*.{js,jsx,tsx,ts}', 'packages/*/src/**/*.{js,jsx,tsx,ts}',
// 不需要扫描的文件加! // 不需要扫描的文件加!
'!packages/*/src/locales/**', '!packages/*/src/locales/**',
'!**/node_modules/**' '!**/node_modules/**',
], ],
output: 'packages/common/src/locales/scan', // 输出目录 output: 'packages/common/src/locales/scan', // 输出目录
options: { options: {
@@ -62,15 +60,15 @@ module.exports = {
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录) loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key) savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
jsonIndent: 2, jsonIndent: 2,
lineEnding: '\n' lineEnding: '\n',
}, },
removeUnusedKeys: true, removeUnusedKeys: true,
nsSeparator: false, // namespace separator nsSeparator: false, // namespace separator
keySeparator: false, // key separator keySeparator: false, // key separator
interpolation: { interpolation: {
prefix: '{{', prefix: '{{',
suffix: '}}' suffix: '}}',
} },
}, },
// 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换. // 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
transform: function (file, enc, done) { transform: function (file, enc, done) {
@@ -80,11 +78,10 @@ module.exports = {
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => { parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
options.defaultValue = key; options.defaultValue = key;
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式 const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
keyHashMap[key] = hashKey; keyHashMap[key] = hashKey;
// 遍历每种语言,逐个语言检查翻译是否存在 // 遍历每种语言,逐个语言检查翻译是否存在
keyList.forEach((lng) => { keyList.forEach(lng => {
const langData = existData[lng] || {}; const langData = existData[lng] || {};
// 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中 // 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中
@@ -116,13 +113,12 @@ module.exports = {
}); });
done(); done();
}, },
flush: function(done) { flush: function (done) {
// 将 keyHashMap 写入文件 // 将 keyHashMap 写入文件
fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2)); fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2));
// 遍历每种语言,处理旧字段 // 遍历每种语言,处理旧字段
keyList.forEach((lng) => { keyList.forEach(lng => {
const localeFilePath = path.join(localesDir, `${lng}.json`); const localeFilePath = path.join(localesDir, `${lng}.json`);
const oldJsonPath = path.join(oldJsonDir, `${lng}.json`); const oldJsonPath = path.join(oldJsonDir, `${lng}.json`);
const langData = existData[lng] || {}; const langData = existData[lng] || {};
@@ -132,7 +128,7 @@ module.exports = {
// 将不存在于 keyHashMap 中的键移动到 oldJson 文件中 // 将不存在于 keyHashMap 中的键移动到 oldJson 文件中
Object.keys(langData).forEach(hashKey => { Object.keys(langData).forEach(hashKey => {
if (!Object.values(keyHashMap).includes(hashKey)) { if (!Object.values(keyHashMap).includes(hashKey)) {
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中 oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
} }
}); });
@@ -142,5 +138,5 @@ module.exports = {
} }
}); });
done(); done();
} },
}; };
+1 -8
View File
@@ -1,10 +1,3 @@
/*
* @Date: 2024-05-10 14:19:56
* @LastEditors: maggieyyy
* @LastEditTime: 2024-05-10 15:55:29
* @FilePath: \frontend\jest.config.js
*/
module.exports = { module.exports = {
roots: ['<rootDir>/packages'], roots: ['<rootDir>/packages'],
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
@@ -15,4 +8,4 @@ module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
testPathIgnorePatterns: ['/node_modules/', '/dist/'], testPathIgnorePatterns: ['/node_modules/', '/dist/'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
}; };
+1 -7
View File
@@ -1,7 +1 @@
/* // import '@testing-library/jest-dom/extend-expect';
* @Date: 2024-05-10 14:22:41
* @LastEditors: maggieyyy
* @LastEditTime: 2024-05-10 15:49:31
* @FilePath: \frontend\jest.setup.js
*/
// import '@testing-library/jest-dom/extend-expect';
+1
View File
@@ -3,4 +3,5 @@
"packages/*" "packages/*"
], ],
"version": "independent" "version": "independent"
} }
+8 -3
View File
@@ -9,13 +9,13 @@
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ", "build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
"build:pro": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=business-entry --stream --verbose ",
"serve": "lerna run preview --parallel", "serve": "lerna run preview --parallel",
"serve:remotes": "lerna run serve --scope=remote --parallel", "serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream", "dev": "lerna run dev --scope=core --stream",
"dev:pro": "lerna run dev --scope=business-entry --stream",
"stop": "kill-port --port 5000", "stop": "kill-port --port 5000",
"scan": "i18next-scanner --config i18next-scanner.config.js" "scan": "i18next-scanner --config i18next-scanner.config.js",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -67,8 +67,12 @@
"antd": "^5.19.4", "antd": "^5.19.4",
"babel-jest": "^29.7.0", "babel-jest": "^29.7.0",
"eslint": "^8.53.0", "eslint": "^8.53.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.2",
"eslint-plugin-react": "7.37.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4", "eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-unused-imports": "^4.1.4",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next-scanner": "^4.5.0", "i18next-scanner": "^4.5.0",
"jest": "^29.7.0", "jest": "^29.7.0",
@@ -80,6 +84,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"postcss-nested": "^6.0.1", "postcss-nested": "^6.0.1",
"prettier": "^3.1.1",
"react-test-renderer": "^18.3.1", "react-test-renderer": "^18.3.1",
"ts-jest": "^29.1.2", "ts-jest": "^29.1.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",
-5
View File
@@ -1,5 +0,0 @@
// .env.pro
VITE_APP_MODE=pro
VITE_APP_TITLE=My Production App
VITE_API_BASE_URL=https://api.production.example.com
@@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs','public','code-snippet','ace-editor'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
@@ -1,26 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
public/tinymce
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-11
View File
@@ -1,11 +0,0 @@
# `businessEntry`
> TODO: description
## Usage
```
const businessEntry = require('businessEntry');
// TODO: DEMONSTRATE API
```
@@ -1,7 +0,0 @@
'use strict';
const businessEntry = require('..');
const assert = require('assert').strict;
assert.strictEqual(businessEntry(), 'Hello from businessEntry');
console.info('businessEntry tests passed');
@@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>APIPark - 企业API数据开放平台</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="/frontend/iconpark_eolink.js"></script>
<script src="/frontend/iconpark_apinto.js"></script>
</body>
</html>
@@ -1,17 +0,0 @@
{
"name": "business-entry",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": " vite --port 5000 --strictPort",
"build": "vite build ",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview --port 5000 --strictPort",
"serve": "vite preview --port 5000 --strictPort"
},
"dependencies": {
},
"devDependencies": {
}
}
@@ -1,15 +0,0 @@
/*
* @Date: 2023-11-27 17:31:54
* @LastEditors: maggieyyy
* @LastEditTime: 2024-06-05 10:42:18
* @FilePath: \frontend\packages\core\postcss.config.js
*/
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
},
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-158
View File
@@ -1,158 +0,0 @@
import '@core/App.css'
import { ConfigProvider } from 'antd';
import RenderRoutes from '@businessEntry/components/aoplatform/RenderRoutes';
import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx";
import { StyleProvider } from '@ant-design/cssinjs';
import zhCN from 'antd/locale/zh_CN';
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import ThemeSwitcher from '@common/components/aoplatform/ThemeSwitcher'
const antdComponentThemeToken = {
token: {
// Seed Token,影响范围大
colorPrimary: '#3D46F2',
colorLink:'#3D46F2',
colorBorder:'#ededed',
colorText:'#333',
borderRadius: 4,
// 派生变量,影响范围小
colorBgContainer: '#fff',
colorPrimaryBg:'#EBEEF2',
colorTextQuaternary:'#BBB',
colorTextTertiary:'#999'
},
components:{
// 派生变量,影响范围小
Input:{
activeShadow:'none'
},
Select:{
activeShadow:'none'
},
Checkbox:{
activeShadow:'none'
},
Cascader:{
activeShadow:'none',
optionSelectedBg:'#EBEEF2',
optionHoverBg:'#EBEEF2'
},
Layout: {
bodyBg: '#17163E',
headerBg: 'transparent',
headerColor: '#333',
headerPadding: '10 20px',
lightSiderBg: 'transparent',
siderBg: 'transparent',
},
Breadcrumb:{
itemColor:'#666',
linkColor:'#666',
lastItemColor:'#333',
},
Table:{
headerBorderRadius:0,
headerSplitColor:'#ededed',
borderColor:'#ededed',
cellPaddingBlockMD:'10px',
cellPaddingInlineMD:'12px',
cellPaddingBlockSM:'8px',
cellPaddingInlineSM:'12px',
headerFilterHoverBg:'#EBEEF2',
headerSortActiveBg:'#F7F8FA',
headerSortHoverBg:'#F7F8FA',
fixedHeaderSortActiveBg:'#F7F8FA',
headerBg:'#F7F8FA',
rowHoverBg:'#EBEEF2'
},
Segmented:{
itemColor:'#333',
itemSelectedColor:'#333',
trackBg:'#f7f8fa',
trackPadding:0,
// itemHoverColor:'#EBEEF2',
itemActiveBg:'#EBEEF2',
itemHoverBg:'#EBEEF2',
itemSelectedBg:'#EBEEF2',
},
Tree:{
// titleHeight:30,
// fontSize:12,
directoryNodeSelectedBg:'#EBEEF2',
directoryNodeSelectedColor:'#333',
nodeSelectedBg:'#EBEEF2',
nodeHoverBg:'#EBEEF2'
},
Collapse:{
headerBg:'#f7f8fa',
headerPadding:"12px",
contentPadding:"0 10px 12px 10px"
},
Button:{
// paddingInline:8,
dangerShadow:'none',
defaultShadow:'none',
primaryShadow:'none'
},
Tabs:{
cardBg:'#EBEEF2',
cardHeight:42,
horizontalItemGutter:8,
horizontalItemPaddingSM:'12px 8px 8px 8px',
horizontalItemPadding:'12px 8px 8px 8px',
},
Menu:{
// itemBg:'#F7F8FA',
// subMenuItemBg:'#F7F8FA',
// itemMarginBlock:0,
// activeBarBorderWidth:0,
// itemSelectedColor:'#333',
// itemSelectedBg:'#EBEEF2',
// itemHoverBg:'#EBEEF2'
// itemHeight:'72px',
// darkItemBg:'transparent',
// itemBg:'transparent',
// itemSelectedBg:'transparent',
// darkItemSelectedBg:'transparent',
// subMenuItemBg:'transparent',
// itemActiveBg:'transparent',
// darkSubMenuItemBg:'transparent',
// activeBarHeight:'2px',
// activeBarBorderWidth:2
},
List:{
itemPadding:'8px 0'
},
Form:{
itemMarginBottom:10,
},
Alert:{
defaultPadding:'12px 16px'
},
Tag:{
defaultBg:"#f7f8fa"
},
}
}
function App() {
useInitializeMonaco()
return (
<StyleProvider hashPriority={"high"}>
<ConfigProvider
locale={zhCN}
wave={{disabled:true}}
theme={antdComponentThemeToken}>
<ThemeSwitcher />
<BreadcrumbProvider>
<RenderRoutes />
</BreadcrumbProvider>
</ConfigProvider>
</StyleProvider>
);
}
export default App
@@ -1,484 +0,0 @@
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Login from "@core/pages/Login.tsx"
import BasicLayout from '@common/components/aoplatform/BasicLayout';
import {createElement, ReactElement,ReactNode,Suspense} from 'react';
import { v4 as uuidv4 } from 'uuid'
import {App, Skeleton} from "antd";
import ApprovalPage from "@core/pages/approval/ApprovalPage.tsx";
import {SystemProvider} from "@core/contexts/SystemContext.tsx";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import {FC,lazy} from 'react';
import { TeamProvider } from '@core/contexts/TeamContext.tsx';
import SystemOutlet from '@core/pages/system/SystemOutlet.tsx';
import { DashboardProvider } from '@core/contexts/DashboardContext.tsx';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
type RouteConfig = {
path:string
component?:ReactElement
children?:(RouteConfig|false)[]
key:string
provider?:FC<{ children: ReactNode; }>
lazy?:unknown
}
const APP_MODE = import.meta.env.VITE_APP_MODE;
export type RouterParams = {
teamId:string
apiId:string
serviceId:string
clusterId:string;
memberGroupId:string
userGroupId:string
pluginName:string
moduleId:string
accessType:'project'|'team'|'service'
categoryId:string
tagId:string
dashboardType:string
dashboardDetailId:string
topologyId:string
appId:string
roleType:string
roleId:string
}
const PUBLIC_ROUTES:RouteConfig[] = [
{
path:'/',
component:<Login/>,
key: uuidv4(),
},
{
path:'/login',
component:<Login/>,
key: uuidv4()
},
{
path:'/',
component:<ProtectedRoute/>,
key: uuidv4(),
children:[
{
path:'approval/*',
component:<ApprovalPage />,
key:uuidv4()
},
{
path:'team',
component:<Outlet/>,
key: uuidv4(),
provider: TeamProvider,
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
},
{
path:'inside/:teamId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
key: uuidv4(),
children:[
{
path:'member',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
},
]
}
]
},
{
path:'service',
component:<SystemOutlet />,
key: uuidv4(),
provider: SystemProvider,
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:'list/:teamId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:':teamId',
component:<Outlet/>,
key: uuidv4(),
children:[
{
path:'inside/:serviceId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children:[
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path:'router',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')),
},
{
path:'upstream',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
},
{
path:'document',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
},
{
path:'subscriber',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
}
]
},
{
path:'topology',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemTopology.tsx')),
key: uuidv4(),
children:[
]
},
{
path:'publish',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
}
]
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
]
}
]
}
]
},{
path:'datasourcing',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')),
},
{
path:'cluster',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
},
{
path:'cert',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
},
{
path:'serviceHub',
component:<Outlet />,
key:uuidv4(),
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
},
{
path:'detail/:serviceId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
}]
},
{
path:'commonsetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
key:uuidv4(),
},
{
path:'consumer',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:':teamId/inside/:appId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
children:[
{
path:'service',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
},
{
path:'authorization',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
},
{
path:'setting',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
},
]
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
{
path:'list/:teamId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
]
},
{
path:'member',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
},
{
path:'list/:memberGroupId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
}
]
},
{
path:'role',
key:uuidv4(),
component:<Outlet></Outlet>,
children:[
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
},
{
path:':roleType/config',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
},
{
path:':roleType/config/:roleId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}
]
},
APP_MODE === 'pro' &&{
path:'openapi',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@openApi/pages/OpenApiList.tsx')),
key:uuidv4(),
},
{
path:'assets',
component:<p></p>,
key:uuidv4()
},
APP_MODE === 'pro' &&{
path:'analytics',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key:uuidv4(),
children:[
{
path:':dashboardType',
component:<Outlet/>,
key:uuidv4(),
provider:DashboardProvider,
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardList.tsx')),
key:uuidv4()
},
{
path:'detail/:dashboardDetailId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardDetail.tsx')),
key:uuidv4()
},
]
},
]
},
{
path:'systemrunning',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@systemRunning/pages/SystemRunning.tsx')),
key:uuidv4()
},
{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
},
{
path:'logsettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
APP_MODE ==='pro' && {
path:'resourcesettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
{
path:'userProfile/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/UserProfile.tsx')),
key:uuidv4(),
children:[{
path:'changepsw',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/ChangePsw.tsx')),
key:uuidv4()
}]
}
]
},
]
const RenderRoutes = ()=> {
return (
<App className="h-full" message={{ maxCount: 1 }}>
<Router>
<Routes>
{generateRoutes(PUBLIC_ROUTES)}
</Routes>
</Router>
</App>
)
}
const generateRoutes = (routerConfig: RouteConfig[]) => {
return routerConfig?.map((route: RouteConfig) => {
let routeElement;
if (route.lazy) {
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
routeElement = (
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <LazyComponent />)
) : (
<LazyComponent />
)}
</Suspense>
);
} else {
routeElement = route.provider ? (
createElement(route.provider, {}, route.component)
) : (
route.component
);
}
return (
<Route
key={route.key}
path={route.path}
element={routeElement}
>
{route.children && generateRoutes(route.children as RouteConfig[])}
</Route>
);
}
)
}
// 保护的路由组件
function ProtectedRoute() {
const {state} = useGlobalContext()
return state.isAuthenticated? <BasicLayout project="core" /> : <Navigate to="/login" />;
}
export default RenderRoutes
@@ -1,27 +0,0 @@
import {StrictMode} from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@core/index.css'
import {GlobalProvider} from "@common/contexts/GlobalStateContext.tsx";
async function initializeApp() {
try {
// 初始化行为
// await fetchInitialConfig(); // 示例:获取初始配置
// 异步操作完成后,渲染React应用
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalProvider>
<App />
</GlobalProvider>
</StrictMode>,
);
} catch (error) {
console.error('Initialization failed:', error);
// 处理初始化失败的情况,比如渲染一个错误界面
}
}
// 执行初始化
initializeApp();
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
@@ -1,22 +0,0 @@
/*
* @Date: 2024-06-05 09:35:25
* @LastEditors: maggieyyy
* @LastEditTime: 2024-06-05 10:50:12
* @FilePath: \frontend\packages\core\start-vite.js
*/
// start-vite.js// start-vite.js
import { exec } from 'child_process';
const viteProcess = exec('pnpm run build');
viteProcess.stdout.on('data', (data) => {
console.log(data.toString());
});
viteProcess.stderr.on('data', (data) => {
console.error(data.toString());
});
viteProcess.on('close', (code) => {
console.log(`Vite process exited with code ${code}`);
});
@@ -1,33 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"paths": {
"@core/*": ["../core/src/*"],
"@common/*": ["../common/src/*"],
"@market/*": ["../market/src/*"],
"@dashboard/*": ["../dashboard/src/*"],
"@openApi/*": ["../openApi/src/*"],
"@systemRunning/*": ["../systemRunning/src/*"],
"@businessEntry/*": ["./src/*"],
},
},
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/Navigation.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
"references": [{ "path": "./tsconfig.node.json" }]
}
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
@@ -1,80 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
export default defineConfig({
cacheDir: './node_modules/.vite',
build:{
outDir:'../../dist',
sourcemap: false,
chunkSizeWarningLimit: 50000,
cacheDir: './node_modules/.vite',
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
// 针对 pnpm 和 Monorepo 特殊处理
if (id.includes('.pnpm')) {
const segments = id.split(path.sep);
const packageName = segments[segments.indexOf('.pnpm') + 1].split('@')[0];
return packageName;
}
}
},
},
css: {
postcss: {
plugins: [
tailwindcss(path.resolve(__dirname, '../common/tailwind.config.js')),
autoprefixer
],
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
modules:{
localsConvention:"camelCase",
generateScopedName:"[local]_[hash:base64:2]"
}
},
plugins: [react(),
dynamicImportVars({
include:["src"],
exclude:[],
warnOnError:false
}),
],
resolve: {
alias: [
{ find: /^~/, replacement: '' },
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
{ find: '@market', replacement: path.resolve(__dirname, '../market/src') },
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') },
{ find: '@dashboard', replacement: path.resolve(__dirname, '../dashboard/src') },
{ find: '@openApi', replacement: path.resolve(__dirname, '../openApi/src') },
{ find: '@systemRunning', replacement: path.resolve(__dirname, '../systemRunning/src') },
{ find: '@businessEntry', replacement: path.resolve(__dirname, './src') },
]
},
server: {
proxy: {
'/api/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
},
'/api2/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
}
}
},
logLevel:'info'
})
File diff suppressed because one or more lines are too long
+1 -6
View File
@@ -1,9 +1,4 @@
/*
* @Date: 2023-11-27 17:31:54
* @LastEditors: maggieyyy
* @LastEditTime: 2023-11-29 15:49:05
* @FilePath: \applatform\frontend\packages\core\postcss.config.js
*/
export default { export default {
plugins: { plugins: {
'postcss-import': {}, 'postcss-import': {},
@@ -1,39 +1,37 @@
import React from "react" import React from 'react'
import SwaggerUI from 'swagger-ui-react'; import SwaggerUI from 'swagger-ui-react'
import 'swagger-ui-react/swagger-ui.css'; import 'swagger-ui-react/swagger-ui.css'
export default function ApiDocument({spec}:{spec?:string|object}) { export default function ApiDocument({ spec }: { spec?: string | object }) {
class OperationsLayout extends React.Component {
class OperationsLayout extends React.Component { render() {
render() { const { getComponent } = this.props
const { const Operations = getComponent('operations', true)
getComponent
} = this.props return (
const Operations = getComponent("operations", true) <div className="swagger-ui">
<Operations />
return ( </div>
<div className="swagger-ui"> )
<Operations />
</div>
)
}
}
// Create the plugin that provides our layout component
const OperationsLayoutPlugin = () => {
return {
components: {
OperationsLayout: OperationsLayout
}
}
} }
}
return(
<SwaggerUI // Create the plugin that provides our layout component
spec={spec} const OperationsLayoutPlugin = () => {
supportedSubmitMethods={[]} return {
customComponents={{Header:()=>null}} components: {
layout="OperationsLayout" OperationsLayout: OperationsLayout
plugins={[OperationsLayoutPlugin ]} /> }
) }
} }
return (
<SwaggerUI
spec={spec}
supportedSubmitMethods={[]}
customComponents={{ Header: () => null }}
layout="OperationsLayout"
plugins={[OperationsLayoutPlugin]}
/>
)
}
@@ -1,290 +1,291 @@
import { import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'
ConfigProvider,
Dropdown,
MenuProps,
App,
Button} from 'antd';
import Logo from '@common/assets/layout-logo.png';
import AvatarPic from '@common/assets/default-avatar.png' import AvatarPic from '@common/assets/default-avatar.png'
import {Outlet, useLocation, useNavigate} from "react-router-dom"; import Logo from '@common/assets/layout-logo.png'
import { useEffect, useMemo, useState} from "react"; import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'; import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'; import { UserInfoType } from '@common/const/type.ts'
import { import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
ProConfigProvider, import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
ProLayout, import { useFetch } from '@common/hooks/http.ts'
} from '@ant-design/pro-components'; import { $t } from '@common/locales'
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'; import { transformMenuData } from '@common/utils/navigation'
import { UserInfoType } from '@common/const/type.ts'; import { Icon } from '@iconify/react'
import { useFetch } from '@common/hooks/http.ts'; import { App, Button, ConfigProvider, Dropdown, MenuProps } from 'antd'
import { ProjectFilled } from '@ant-design/icons'; import { useEffect, useMemo, useState } from 'react'
import { getNavItem } from '@common/utils/navigation'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import { Icon } from '@iconify/react'; import LanguageSetting from './LanguageSetting'
import { $t } from '@common/locales';
import LanguageSetting from './LanguageSetting';
const APP_MODE = import.meta.env.VITE_APP_MODE; const APP_MODE = import.meta.env.VITE_APP_MODE
export type MenuItem = Required<MenuProps>['items'][number]; export type MenuItem = Required<MenuProps>['items'][number]
const themeToken = { const themeToken = {
bgLayout:'#17163E;', bgLayout: '#17163E;',
header: { header: {
heightLayoutHeader:72 heightLayoutHeader: 72
}, },
pageContainer:{ pageContainer: {
paddingBlockPageContainerContent:0, paddingBlockPageContainerContent: 0,
paddingInlinePageContainerContent:0, paddingInlinePageContainerContent: 0
} }
} }
function BasicLayout({project = 'core'}:{project:string}){
const navigator = useNavigate()
const location = useLocation()
const currentUrl = location.pathname
const { state,accessData,checkPermission,accessInit} = useGlobalContext()
const [pathname, setPathname] = useState(currentUrl);
const mainPage = project === 'core' ?'/service/list':'/serviceHub/list'
const TOTAL_MENU_ITEMS:MenuProps['items'] = useMemo(() => [ function BasicLayout({ project = 'core' }: { project: string }) {
getNavItem($t('工作空间'), 'workspace','/guide/page',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [ const navigator = useNavigate()
getNavItem(<a>{$t('首页')}</a>, 'guide','/guide/page',<Icon icon="ic:baseline-home" width="18" height="18"/>,undefined,undefined,'all'), const location = useLocation()
getNavItem(<a>{$t('服务')}</a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,'all'), const currentUrl = location.pathname
getNavItem(<a>{$t('消费者')}</a>, 'consumer','/consumer',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,'all'), const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
getNavItem(<a>{$t('团队')}</a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'all'), useGlobalContext()
]), const [pathname, setPathname] = useState(currentUrl)
getNavItem($t('API 市场'), 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'), const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
const pluginSlotHub = usePluginSlotHub()
getNavItem($t('仪表盘'), 'mainPage', APP_MODE === 'pro' ? '/analytics' : '/analytics/total',<Icon icon="ic:baseline-bar-chart" width="18" height="18"/>,[ useEffect(() => {
getNavItem(<a >{$t('运行视图')}</a>, 'analytics',APP_MODE === 'pro' ? '/analytics' : '/analytics/total' ,<ProjectFilled />,undefined,undefined,'system.dashboard.run_view.view'), const newMenu = transformMenuData(menuList)
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,'system.dashboard.systemrunning.view') : null, setMenuItems(newMenu)
],undefined,'system.dashboard.run_view.view'), }, [menuList, state.language, accessInit])
getNavItem($t('系统设置'), 'operationCenter','/commonsetting',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem($t('系统'), 'serviceHubSetting','/commonsetting',null,[
getNavItem(<a>{$t('常规')}</a>, 'commonsetting','/commonsetting',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
getNavItem(<a>{$t('API 网关')}</a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a>{$t('AI 模型')}</a>, 'aisetting','/aisetting',<Icon icon="hugeicons:ai-network" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem($t('用户'), 'organization','/member',null,[
getNavItem(<a>{$t('账号')}</a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem(<a>{$t('角色')}</a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
],undefined,''),
getNavItem($t('集成'), 'maintenanceCenter','/datasourcing', null, [
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing','/datasourcing',<Icon icon="ic:baseline-monitor-heart" width="18" height="18"/>,undefined,undefined,'system.devops.data_source.view'),
getNavItem(<a>{$t('证书')}</a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
getNavItem(<a>{$t('日志')}</a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
APP_MODE === 'pro' ? getNavItem(<a>{$t('资源')}</a>, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null,
APP_MODE === 'pro' ? getNavItem(<a>{$t('Open API')}</a>, 'openapi','/openapi',null,undefined,undefined,'system.openapi.self.view'):null,
]),
]),
],[state.language,accessInit])
useEffect(() => {
if (currentUrl === '/') {
navigator(mainPage)
}
}, [currentUrl])
useEffect(() => { const headerMenuData = useMemo(() => {
if(currentUrl === '/'){ // 判断权限
navigator(mainPage) const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
}
}, [currentUrl]);
const headerMenuData = useMemo(() => { // 过滤菜单项
// 判断权限 const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]); return [...menu]
.filter((x) => x) // 过滤掉空数据
// 过滤菜单项 .map((item: any) => {
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => { if (item.routes && item.routes.length > 0) {
return [...menu] // 递归处理子菜单
.filter(x => x) // 过滤掉空数据 const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes)
.map((item: any) => {
if (item.routes && item.routes.length > 0) {
// 递归处理子菜单
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes);
if(filteredRoutes.length === 0){
return false
}
return {...item, routes: filteredRoutes};
}
// 处理没有 routes 的菜单项
if (item.access) {
return (item.access === 'all' || hasAccess(item.access)) ? item : null;
}
// 如果没有 access 和 routes,则保留 if (filteredRoutes.length === 0) {
return item; return false
})
.filter(x => x); // 过滤掉处理后为 null 的项
};
// 初始过滤操作
const res = [...TOTAL_MENU_ITEMS]!.filter(x => x).map((x: any) => (x.routes ? { ...x, routes: filterMenu(x.routes) } : x));
// 返回处理后的数据
return { path: '/', routes: res.map(x=> ({...x, routes: x.routes?.filter(x=> (x.access || x.routes?.length > 0))})).filter(x=> (x.access || x.routes?.length > 0)) };
}, [accessData, state.language]);
const { message } = App.useApp()
const { dispatch,resetAccess,getGlobalAccessData} = useGlobalContext()
const [userInfo,setUserInfo] = useState<UserInfoType>()
const {fetchData} = useFetch()
const navigate = useNavigate();
const getUserInfo = ()=>{
fetchData<BasicResponse<{profile:UserInfoType}>>('account/profile',{method:'GET'})
.then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setUserInfo(data.profile)
dispatch({type:'UPDATE_USERDATA',userData:data.profile})
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
} }
return { ...item, routes: filteredRoutes, name: $t(item.name) }
}
// 处理没有 routes 的菜单项
if (item.access) {
return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null
}
// 如果没有 access 和 routes,则保留
return { ...item, name: $t(item.name) }
}) })
.filter((x) => x) // 过滤掉处理后为 null 的项
} }
useEffect(() => { // 初始过滤操作
getUserInfo() const res = [...(menuItems || [])]!
getGlobalAccessData() .filter((x) => x)
}, []); .map((x: any) =>
x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) }
const logOut = ()=>{ )
fetchData<BasicResponse<null>>('account/logout',{method:'GET'}).then(response=>{ // 返回处理后的数据
const {code,msg} = response return {
if(code === STATUS_CODE.SUCCESS){ path: '/',
dispatch({type:'LOGOUT'}) routes: res
resetAccess() .map((x) => ({ ...x, routes: x.routes?.filter((x) => x.access || x.routes?.length > 0) }))
// message.success(msg || $t(RESPONSE_TIPS.logoutSuccess)) .filter((x) => x.access || x.routes?.length > 0)
navigate('/login')
}else{
message.error(msg ||$t(RESPONSE_TIPS.error))
}
})
} }
}, [accessData, state.language, menuItems])
const items: MenuProps['items'] = [ const { message } = App.useApp()
{ const [userInfo, setUserInfo] = useState<UserInfoType>()
key: '2', const { fetchData } = useFetch()
label: ( const navigate = useNavigate()
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={()=>navigator('/userProfile/changepsw')}>
{$t('账号设置')} const getUserInfo = () => {
</Button>) fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setUserInfo(data.profile)
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
useEffect(() => {
getUserInfo()
getGlobalAccessData()
}, [])
const logOut = () => {
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
dispatch({ type: 'LOGOUT' })
resetAccess()
// message.success(msg || $t(RESPONSE_TIPS.logoutSuccess))
navigate('/login')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
const items: MenuProps['items'] = useMemo(
() =>
[
userInfo?.type !== 'guest' && {
key: '2',
label: (
<Button
key="changePsw"
type="text"
className="flex items-center p-0 bg-transparent border-none"
onClick={() => navigator('/userProfile/changepsw')}
>
{$t('账号设置')}
</Button>
)
}, },
{ {
key: '3', key: '3',
label: ( label: (
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}> <Button
{$t('退出登录')} key="logout"
</Button>) type="text"
}, className="flex items-center p-0 bg-transparent border-none"
]; onClick={logOut}
>
{$t('退出登录')}
</Button>
)
}
].filter(Boolean),
[userInfo]
)
const actionRender = useMemo(() => {
return [
<LanguageSetting />,
<Button
className=" text-[#ffffffb3] hover:text-[#fff] border-none"
type="default"
ghost
onClick={() => {
window.open('https://docs.apipark.com', '_blank')
}}
>
<span className="flex items-center gap-[8px]">
{' '}
<Icon icon="ic:baseline-help" width="14" height="14" />
{$t('文档')}
</span>
</Button>,
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || [])
]
}, [pluginSlotHub.getSlot('basicLayoutAfterBtns')])
return (
return( <div
<div id="test-pro-layout"
id="test-pro-layout" style={{
style={{ height: '100vh',
height: '100vh', overflow: 'auto'
overflow: 'auto', }}
}} >
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body
}}
> >
<ProConfigProvider hashed={false}> <ProLayout
<ConfigProvider prefixCls="apipark-layout"
getTargetContainer={() => { location={{
return document.getElementById('test-pro-layout') || document.body; pathname
}}
siderWidth={220}
breakpoint={'lg'}
route={headerMenuData}
token={themeToken}
siderMenuType="group"
menu={{
type: 'group',
collapsedShowGroupTitle: true
}}
disableMobile={true}
avatarProps={{
src: AvatarPic || userInfo?.avatar,
size: 'small',
title: userInfo?.username || 'unknown',
render: (props, dom) => {
return (
<Dropdown
menu={{
items
}} }}
> >
<ProLayout <div className="avatar-dom">{dom}</div>
prefixCls="apipark-layout" </Dropdown>
location={{ )
pathname, }
}} }}
siderWidth={220} actionsRender={(props) => {
breakpoint={'lg'} if (props.isMobile) return []
route={headerMenuData} if (typeof window === 'undefined') return []
token={themeToken} return actionRender
siderMenuType="group" }}
menu={{ headerTitleRender={() => (
type: 'group', <div className="w-[192px] flex items-center">
collapsedShowGroupTitle: true, <img className="h-[20px] cursor-pointer " src={Logo} onClick={() => navigator(mainPage)} />
}} </div>
disableMobile={true} )}
avatarProps={{ logo={Logo}
src: AvatarPic || userInfo?.avatar, pageTitleRender={() => $t('APIPark')}
size: 'small', menuFooterRender={(props) => {
title: userInfo?.username||'unknown', if (props?.collapsed) return undefined
render: (props, dom) => { }}
return ( menuItemRender={(item, dom) => (
<Dropdown <div
menu={{ onClick={() => {
items // 同级目录点击无效
}} if (
> item.key &&
<div className='avatar-dom'>{dom} routerKeyMap.get(item.key) &&
</div> routerKeyMap.get(item.key).length > 0 &&
</Dropdown> routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1
); ) {
}, return
}} }
actionsRender={(props) => { if (item.key === pathname.split('/')[1]) {
if (props.isMobile) return []; return
if (typeof window === 'undefined') return []; }
return [
<LanguageSetting />, if (item.path) {
<Button className=" text-[#ffffffb3] hover:text-[#fff] border-none" type="default" ghost onClick={()=>{window.open('https://docs.apipark.com','_blank')}}> navigator(item.path)
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-help" width="14" height="14"/>{$t('文档')}</span> }
</Button> setPathname(item.path || '')
]; }}
}} >
headerTitleRender={() => ( {dom}
<div className="w-[192px] flex items-center"> </div>
<img )}
className="h-[20px] cursor-pointer " fixSiderbar={true}
src={Logo} layout="mix"
onClick={()=> navigator(mainPage)} splitMenus={true}
/> collapsed={false}
</div> collapsedButtonRender={false}
)} >
logo={Logo} <div
pageTitleRender={()=>$t('APIPark - 企业API数据开放平台')} className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${
menuFooterRender={(props) => { currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
if (props?.collapsed) return undefined; }`}
}} >
menuItemRender={(item, dom) => ( <Outlet />
<div </div>
onClick={() => { </ProLayout>
// 同级目录点击无效 </ConfigProvider>
if(item.key && routerKeyMap.get(item.key) && routerKeyMap.get(item.key).length > 0 && routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1){ </ProConfigProvider>
return </div>
} )
if(item.key === pathname.split('/')[1]){
return
}
if(item.path){
navigator(item.path)
}
setPathname(item.path || '');
}}
>
{dom}
</div>
)}
fixSiderbar={true}
layout='mix'
splitMenus={true}
collapsed={false}
collapsedButtonRender={false}
>
<div className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden' }`}>
<Outlet />
</div>
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
)
} }
export default BasicLayout export default BasicLayout
@@ -1,15 +1,11 @@
import { Breadcrumb } from "antd" import { Breadcrumb } from 'antd'
import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import {FC,useEffect} from "react"; import { FC, useEffect } from 'react'
const TopBreadcrumb: FC = () => { const TopBreadcrumb: FC = () => {
const { breadcrumb } = useBreadcrumb() const { breadcrumb } = useBreadcrumb()
useEffect(() => { useEffect(() => {}, [breadcrumb])
}, [breadcrumb]); return <Breadcrumb items={breadcrumb} />
return (
<Breadcrumb items={breadcrumb} />
)
} }
export default TopBreadcrumb export default TopBreadcrumb
@@ -1,34 +1,32 @@
import { FC } from 'react'; import { FC } from 'react'
import { Table } from 'antd'; import { Table } from 'antd'
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table'
import { $t } from '@common/locales'; import { $t } from '@common/locales'
interface DataType { interface DataType {
httpStatusCode: string; httpStatusCode: string
systemStatusCode: string; systemStatusCode: string
description: string; description: string
} }
const columns: ColumnsType<DataType> = [ const columns: ColumnsType<DataType> = [
{ {
title:$t('HTTP 状态码'), title: $t('HTTP 状态码'),
dataIndex: 'httpStatusCode', dataIndex: 'httpStatusCode',
key: 'httpStatusCode', key: 'httpStatusCode'
}, },
{ {
title:$t('系统状态码'), title: $t('系统状态码'),
dataIndex: 'systemStatusCode', dataIndex: 'systemStatusCode',
key: 'systemStatusCode', key: 'systemStatusCode'
}, },
{ {
title: $t('描述'), title: $t('描述'),
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
ellipsis:true ellipsis: true
}, }
]
];
const data: DataType[] = [ const data: DataType[] = [
// { // {
@@ -44,12 +42,12 @@ const data: DataType[] = [
{ {
httpStatusCode: '413', httpStatusCode: '413',
systemStatusCode: '10003', systemStatusCode: '10003',
description: '请求频率过高', description: '请求频率过高'
}, },
{ {
httpStatusCode: '403', httpStatusCode: '403',
systemStatusCode: '10004', systemStatusCode: '10004',
description: '请求来源非法,不在白名单中', description: '请求来源非法,不在白名单中'
}, },
// { // {
// httpStatusCode: '416', // httpStatusCode: '416',
@@ -59,7 +57,7 @@ const data: DataType[] = [
{ {
httpStatusCode: '504', httpStatusCode: '504',
systemStatusCode: '10006', systemStatusCode: '10006',
description: '网关超时', description: '网关超时'
}, },
// { // {
// httpStatusCode: '504', // httpStatusCode: '504',
@@ -69,7 +67,7 @@ const data: DataType[] = [
{ {
httpStatusCode: '404', httpStatusCode: '404',
systemStatusCode: '10007', systemStatusCode: '10007',
description: '接口不存在', description: '接口不存在'
}, },
// { // {
// httpStatusCode: '416', // httpStatusCode: '416',
@@ -84,42 +82,43 @@ const data: DataType[] = [
{ {
httpStatusCode: '400', httpStatusCode: '400',
systemStatusCode: '10010', systemStatusCode: '10010',
description: '无法识别请求内容,请检查请求体是否正确', description: '无法识别请求内容,请检查请求体是否正确'
}, },
{ {
httpStatusCode: '400', httpStatusCode: '400',
systemStatusCode: '10011', systemStatusCode: '10011',
description: '请求头部缺少 Content-Type 字段', description: '请求头部缺少 Content-Type 字段'
}, },
{ {
httpStatusCode: '400', httpStatusCode: '400',
systemStatusCode: '10011', systemStatusCode: '10011',
description: '请求头部 Content-Type 字段错误', description: '请求头部 Content-Type 字段错误'
}, },
{ {
httpStatusCode: '400', httpStatusCode: '400',
systemStatusCode: '10014', systemStatusCode: '10014',
description: '批量参数超出单次批量数量的最大限制', description: '批量参数超出单次批量数量的最大限制'
}, },
{ {
httpStatusCode: '400', httpStatusCode: '400',
systemStatusCode: '10016', systemStatusCode: '10016',
description: '参数缺少内容', description: '参数缺少内容'
}, },
{ {
httpStatusCode: '500', httpStatusCode: '500',
systemStatusCode: '10017', systemStatusCode: '10017',
description: '参数类型错误', description: '参数类型错误'
}, }
]; ]
const CodePage: FC = () => const CodePage: FC = () => (
<Table <Table
size="small" size="small"
columns={columns} columns={columns}
className='table-border border-b-0 rounded' className="table-border border-b-0 rounded"
dataSource={data?.map((item, index) => ({...item, key: index})) || []} dataSource={data?.map((item, index) => ({ ...item, key: index })) || []}
pagination={false} pagination={false}
/>; />
)
export default CodePage; export default CodePage
@@ -1,33 +1,32 @@
import { useState, FC } from 'react'
import { useState,FC } from 'react'; import { Tooltip, Button } from 'antd'
import { Tooltip, Button } from 'antd'; import useCopyToClipboard from '@common/hooks/copy'
import useCopyToClipboard from '@common/hooks/copy'; import { Icon } from '@iconify/react/dist/iconify.js'
import { Icon } from '@iconify/react/dist/iconify.js';
type AddressItem = { type AddressItem = {
expand?: boolean; expand?: boolean
[key: string]: unknown; [key: string]: unknown
} }
type CopyAddrListProps = { type CopyAddrListProps = {
addrItem: AddressItem; addrItem: AddressItem
onAddrItemChange?: (addrItem: AddressItem) => void; onAddrItemChange?: (addrItem: AddressItem) => void
keyName: string; keyName: string
} }
const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => { const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => {
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem); const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem)
const { copyToClipboard } = useCopyToClipboard(); const { copyToClipboard } = useCopyToClipboard()
const toggleExpand = () => { const toggleExpand = () => {
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }; const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }
setLocalAddrItem(updatedAddrItem); setLocalAddrItem(updatedAddrItem)
onAddrItemChange?.(updatedAddrItem); onAddrItemChange?.(updatedAddrItem)
}; }
const renderTooltipTitle = () => { const renderTooltipTitle = () => {
// 假设keyName对应的值是一个字符串数组 // 假设keyName对应的值是一个字符串数组
const addresses:string[] = localAddrItem[keyName] as string[] const addresses: string[] = localAddrItem[keyName] as string[]
return ( return (
<div> <div>
{addresses?.map((addr, index) => ( {addresses?.map((addr, index) => (
@@ -36,29 +35,41 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
</div> </div>
))} ))}
</div> </div>
); )
}; }
const renderAddresses = () => { const renderAddresses = () => {
if (!localAddrItem.expand) { if (!localAddrItem.expand) {
return ( return (
<span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle"> <span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle">
<Tooltip title={renderTooltipTitle}> <Tooltip title={renderTooltipTitle}>
<span className='flex items-center'> <span className="flex items-center">
<span className={`overflow-ellipsis inline-block overflow-hidden align-middle ${((localAddrItem[keyName] as string[]).length > 1) ? 'w-5/6' : 'w-full'}`}> <span
className={`overflow-ellipsis inline-block overflow-hidden align-middle ${(localAddrItem[keyName] as string[]).length > 1 ? 'w-5/6' : 'w-full'}`}
>
{(localAddrItem[keyName] as string[]).join(',')} {(localAddrItem[keyName] as string[]).join(',')}
</span> </span>
{(localAddrItem[keyName] as string[]).length === 1 && ( {(localAddrItem[keyName] as string[]).length === 1 && (
<Button type="primary" className="border-none ant-typography-copy text-theme hover:text-A_HOVER " ghost onClick={() => copyToClipboard((localAddrItem[keyName] as string))} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" /> <Button
type="primary"
className="border-none ant-typography-copy text-theme hover:text-A_HOVER "
ghost
onClick={() => copyToClipboard(localAddrItem[keyName] as string)}
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
size="small"
/>
)} )}
{(localAddrItem[keyName] as string[]).length !== 1 && ( {(localAddrItem[keyName] as string[]).length !== 1 && (
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="zhankai" style={{marginTop:'4px'}}></iconpark-icon>} onClick={toggleExpand} /> <Button
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
icon={<iconpark-icon name="zhankai" style={{ marginTop: '4px' }}></iconpark-icon>}
onClick={toggleExpand}
/>
)} )}
</span> </span>
</Tooltip> </Tooltip>
</span> </span>
); )
} else { } else {
return ( return (
<div className="flex flex-nowrap items-center justify-between"> <div className="flex flex-nowrap items-center justify-between">
@@ -66,21 +77,28 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
{(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => ( {(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => (
<div key={index} className="block w-full"> <div key={index} className="block w-full">
<span className="leading-6">{addr}</span> <span className="leading-6">{addr}</span>
<Button type="primary" className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER" ghost onClick={() => copyToClipboard(addr)} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" /> <Button
type="primary"
className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER"
ghost
onClick={() => copyToClipboard(addr)}
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
size="small"
/>
</div> </div>
))} ))}
</div> </div>
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="shouqi-2"></iconpark-icon>} onClick={toggleExpand} /> <Button
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
icon={<iconpark-icon name="shouqi-2"></iconpark-icon>}
onClick={toggleExpand}
/>
</div> </div>
); )
} }
}; }
return ( return <div>{renderAddresses()}</div>
<div> }
{renderAddresses()}
</div>
);
};
export default CopyAddrList; export default CopyAddrList
@@ -1,59 +1,84 @@
import { Button, Drawer, DrawerProps, Space } from 'antd'
import { Button, Drawer, DrawerProps, Space } from "antd"; import WithPermission from './WithPermission'
import WithPermission from "./WithPermission"; import { useEffect, useState } from 'react'
import { useEffect, useState } from "react"; import { $t } from '@common/locales'
import { $t } from '@common/locales';
export type DrawerWithFooterProps = DrawerProps & { export type DrawerWithFooterProps = DrawerProps & {
onSubmit?: () => Promise<boolean|string>|undefined onSubmit?: () => Promise<boolean | string> | undefined
submitAccess?: string submitAccess?: string
submitDisabled?:boolean submitDisabled?: boolean
onClose?:()=>void onClose?: () => void
showLastStep?:boolean showLastStep?: boolean
onLastStep?:()=>void onLastStep?: () => void
notAutoClose?:boolean notAutoClose?: boolean
showOkBtn?:boolean showOkBtn?: boolean
extraBtn?:React.ReactNode extraBtn?: React.ReactNode
okBtnTitle?:string okBtnTitle?: string
cancelBtnTitle?:string cancelBtnTitle?: string
} }
export function DrawerWithFooter(props:DrawerWithFooterProps){ export function DrawerWithFooter(props: DrawerWithFooterProps) {
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle= $t('提交'),cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props const {
const [submitLoading, setSubmitLoading] = useState<boolean>(false) children,
const handlerSubmit = ()=>{ title,
setSubmitLoading(true) placement = 'right',
onSubmit?.()?.then(()=>{!notAutoClose && onClose?.()}).finally(()=>{setSubmitLoading(false)}) onClose,
} onSubmit,
submitDisabled = false,
okBtnTitle = $t('提交'),
cancelBtnTitle,
open,
submitAccess,
showLastStep,
onLastStep,
notAutoClose,
showOkBtn = true,
extraBtn
} = props
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
const handlerSubmit = () => {
setSubmitLoading(true)
onSubmit?.()
?.then(() => {
!notAutoClose && onClose?.()
})
.finally(() => {
setSubmitLoading(false)
})
}
useEffect(()=>{!open && setSubmitLoading(false)},[open]) useEffect(() => {
return (<> !open && setSubmitLoading(false)
<Drawer }, [open])
{...props} return (
push={false} <>
title={title} <Drawer
placement={placement} {...props}
width="60%" push={false}
destroyOnClose={true} title={title}
maskClosable={false} placement={placement}
classNames={ width="60%"
{footer:'text-right'} destroyOnClose={true}
} maskClosable={false}
footer={ classNames={{ footer: 'text-right' }}
<Space className="flex flex-row-reverse" style={{}}> footer={
{showOkBtn && <WithPermission access={submitAccess}> <Space className="flex flex-row-reverse" style={{}}>
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}> {showOkBtn && (
{ okBtnTitle} <WithPermission access={submitAccess}>
</Button> <Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
</WithPermission>} {okBtnTitle}
{ showLastStep && <Button onClick={onLastStep ?? onClose}> { $t('上一步')}</Button>} </Button>
{ extraBtn } </WithPermission>
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消'):$t('关闭'))}</Button> )}
</Space> {showLastStep && <Button onClick={onLastStep ?? onClose}> {$t('上一步')}</Button>}
} {extraBtn}
onClose={onClose} <Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消') : $t('关闭'))}</Button>
open={open} </Space>
> }
{children} onClose={onClose}
</Drawer> open={open}
</>) >
} {children}
</Drawer>
</>
)
}
@@ -1,91 +1,93 @@
import { FC } from 'react'
import {FC } from 'react'; import { Input, Space } from 'antd'
import { Input, Space } from 'antd'; import { Icon } from '@iconify/react/dist/iconify.js'
import { Icon } from '@iconify/react/dist/iconify.js';
type KeyValueInput = { type KeyValueInput = {
key: string; key: string
value: string; value: string
}; }
type DynamicKeyValueInputProps = { type DynamicKeyValueInputProps = {
value?: KeyValueInput[]; value?: KeyValueInput[]
onChange?: (newValue: KeyValueInput[]) => void; onChange?: (newValue: KeyValueInput[]) => void
}; }
export function transferToList(rawData: unknown): Array<{ key: string; value: string }> {
export function transferToList (rawData:unknown):Array<{key:string, value:string}> { const res: Array<{ key: string; value: string }> = []
const res:Array<{key:string, value:string}> = [] if (!rawData) return res
if(!rawData) const keys: Array<string> = Object.keys(rawData)
return res
const keys:Array<string> = Object.keys(rawData)
if (keys?.length > 0) { if (keys?.length > 0) {
for (const key of keys) { for (const key of keys) {
res.push({ key: key, value: rawData[key] }) res.push({ key: key, value: rawData[key] })
}
return [...res, { key: '', value: '' }]
} }
return [...res, { key: '', value: '' }] return [{ key: '', value: '' }]
}
return [{ key: '', value: '' }]
} }
export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} { export function transferToMap(rawData: Array<{ key: string; value: string }>): { [key: string]: string } {
const res:{[key:string]:string} = {} const res: { [key: string]: string } = {}
for (const kv of rawData) { for (const kv of rawData) {
if (kv.key && kv.value) { res[kv.key] = kv.value } if (kv.key && kv.value) {
res[kv.key] = kv.value
}
} }
return res return res
} }
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({value = [{key:'',value:''}],onChange}) => { export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({ value = [{ key: '', value: '' }], onChange }) => {
// const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]); // const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]);
// Define a handler for when the inputs change // Define a handler for when the inputs change
const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => { const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => {
// Create a new array with the updated value // Create a new array with the updated value
const newKeyValuePairs = value ? [...value] : []; const newKeyValuePairs = value ? [...value] : []
if (newKeyValuePairs[index]) { if (newKeyValuePairs[index]) {
newKeyValuePairs[index][type] = newValue; newKeyValuePairs[index][type] = newValue
// If we're changing the last input and it's not empty, add a new pair // If we're changing the last input and it's not empty, add a new pair
if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) { if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) {
newKeyValuePairs.push({ key: '', value: '' }); newKeyValuePairs.push({ key: '', value: '' })
} }
// Call the onChange handler if it exists // Call the onChange handler if it exists
onChange?.(newKeyValuePairs); onChange?.(newKeyValuePairs)
} }
}; }
const addNewPair = () => { const addNewPair = () => {
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]; const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]
onChange?.(newKeyValuePairs); onChange?.(newKeyValuePairs)
}; }
const removePair = (index: number) => { const removePair = (index: number) => {
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []; const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []
onChange?.(newKeyValuePairs); onChange?.(newKeyValuePairs)
}; }
return ( return (
<> <>
{value && value?.map((pair, index) => ( {value &&
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline"> value?.map((pair, index) => (
<Input <Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
placeholder="Key" <Input
value={pair.key} placeholder="Key"
onChange={(e) => handleInputChange(index, 'key', e.target.value)} value={pair.key}
style={{ width: 162 }} /> onChange={(e) => handleInputChange(index, 'key', e.target.value)}
<Input style={{ width: 162 }}
placeholder="Value" />
value={pair.value} <Input
onChange={(e) => handleInputChange(index, 'value', e.target.value)} placeholder="Value"
style={{ width: 162 }} /> value={pair.value}
{index !== value.length - 1 && ( onChange={(e) => handleInputChange(index, 'value', e.target.value)}
<> style={{ width: 162 }}
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14"/> />
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14"/> {index !== value.length - 1 && (
</> <>
)} <Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14" />
</Space> <Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14" />
</>
)}
</Space>
))} ))}
</> </>
); )
}; }
@@ -1,116 +1,130 @@
import { EditableProTable } from "@ant-design/pro-components"; import { EditableProTable } from '@ant-design/pro-components'
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo } from 'react'
import { v4 as uuidv4} from 'uuid'; import { v4 as uuidv4 } from 'uuid'
import { PageProColumns } from "./PageList"; import { PageProColumns } from './PageList'
import TableBtnWithPermission from "./TableBtnWithPermission"; import TableBtnWithPermission from './TableBtnWithPermission'
import { $t } from "@common/locales"; import { $t } from '@common/locales'
import { useGlobalContext } from "@common/contexts/GlobalStateContext"; import { useGlobalContext } from '@common/contexts/GlobalStateContext'
interface EditableTableProps<T> { interface EditableTableProps<T> {
configFields: PageProColumns<T>[]; configFields: PageProColumns<T>[]
value?: T[]; // 外部传入的值 value?: T[] // 外部传入的值
className?: string; className?: string
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
// tableProps?: TableProps<T>; // tableProps?: TableProps<T>;
disabled?:boolean disabled?: boolean
extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id extendsId?: string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
} }
const EditableTable = <T extends { _id: string }>({ const EditableTable = <T extends { _id: string }>({
configFields, configFields,
value, // value 现在是外部传入的配置项数组 value, // value 现在是外部传入的配置项数组
onChange, // onChange 现在是当配置项数组变化时的回调函数 onChange, // onChange 现在是当配置项数组变化时的回调函数
// tableProps, // tableProps,
disabled, disabled,
className, className,
extendsId, extendsId
}: EditableTableProps<T>) => { }: EditableTableProps<T>) => {
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]); const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
const {state} = useGlobalContext() const { state } = useGlobalContext()
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
value?.map((item) => item._id) || ['1234']
);
useEffect(() => { useEffect(() => {
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]); setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }])
}, [value]); }, [value])
const getNotEmptyValue = (value:unknown)=>{ const getNotEmptyValue = (value: unknown) => {
return value return value
} }
const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields]) const translatedColumns = useMemo(
() => configFields.map((x) => ({ ...x, title: $t(x.title as string) })),
[state.language, configFields]
)
return ( return (
<EditableProTable<T> <EditableProTable<T>
className={className} className={className}
columns={translatedColumns} columns={translatedColumns}
rowKey="_id" rowKey="_id"
value={configurations as T[]} value={configurations as T[]}
size="small" size="small"
bordered={true} bordered={true}
recordCreatorProps={false} recordCreatorProps={false}
editable={ { editable={{
type: 'multiple', type: 'multiple',
editableKeys:disabled ? [] : configurations?.map(x=>x._id), editableKeys: disabled ? [] : configurations?.map((x) => x._id),
actionRender: (row, config) => { actionRender: (row, config) => {
return [ return [
<TableBtnWithPermission key="add" btnType="add" onClick={() => { <TableBtnWithPermission
const newId = uuidv4(); key="add"
setConfigurations((prev)=>{ btnType="add"
const tmpPreData = [...prev]; onClick={() => {
const newId = uuidv4() const newId = uuidv4()
const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1]; setConfigurations((prev) => {
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId }; const tmpPreData = [...prev]
const newId = uuidv4()
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值 const lastRecord: { [k: string]: unknown } = tmpPreData[tmpPreData.length - 1]
if(extendsId && extendsId.length > 0) { const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
extendsId.forEach(field => {
newRecord[field] = lastRecord[field];
});
}
tmpPreData.splice(Number(config.index) + 1, 0,newRecord);
onChange?.(getNotEmptyValue(tmpPreData));
return tmpPreData});
setEditableRowKeys((prev)=>([...prev,newId]))
}}
btnTitle="增加"/>,
(config.index !== configurations.length - 1 )&& <TableBtnWithPermission key="remove" btnType="remove" btnTitle="删除" // 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
onClick={() => { if (extendsId && extendsId.length > 0) {
setConfigurations((prev)=>{ extendsId.forEach((field) => {
const tmpPreData = [...prev]; newRecord[field] = lastRecord[field]
tmpPreData.splice(Number(config.index), 1); })
onChange?.(tmpPreData); }
return tmpPreData}); tmpPreData.splice(Number(config.index) + 1, 0, newRecord)
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id))) onChange?.(getNotEmptyValue(tmpPreData))
}}/>,, return tmpPreData
]; })
}, setEditableRowKeys((prev) => [...prev, newId])
onValuesChange: (record, recordList) => { }}
if(record._id === recordList[recordList.length - 1]._id){ btnTitle="增加"
const newId = uuidv4() />,
const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1];
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
if(extendsId && extendsId.length > 0) {
extendsId.forEach(field => {
newRecord[field] = lastRecord[field];
});
}
recordList = ([...recordList, newRecord as T]);
setEditableRowKeys((prev)=>[...prev, newId])
}
setConfigurations(recordList);
onChange?.(recordList);
},
onChange: setEditableRowKeys,
}}
/>
)
}
export default EditableTable; config.index !== configurations.length - 1 && (
<TableBtnWithPermission
key="remove"
btnType="remove"
btnTitle="删除"
onClick={() => {
setConfigurations((prev) => {
const tmpPreData = [...prev]
tmpPreData.splice(Number(config.index), 1)
onChange?.(tmpPreData)
return tmpPreData
})
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
}}
/>
),
,
]
},
onValuesChange: (record, recordList) => {
if (record._id === recordList[recordList.length - 1]._id) {
const newId = uuidv4()
const lastRecord: { [k: string]: unknown } = recordList[recordList.length - 1]
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
if (extendsId && extendsId.length > 0) {
extendsId.forEach((field) => {
newRecord[field] = lastRecord[field]
})
}
recordList = [...recordList, newRecord as T]
setEditableRowKeys((prev) => [...prev, newId])
}
setConfigurations(recordList)
onChange?.(recordList)
},
onChange: setEditableRowKeys
}}
/>
)
}
export default EditableTable
@@ -1,105 +1,119 @@
import { EditableFormInstance, EditableProTable } from "@ant-design/pro-components"; import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components'
import { useState, useEffect, useMemo, useRef, MutableRefObject } from "react"; import { useState, useEffect, useMemo, useRef, MutableRefObject } from 'react'
import { v4 as uuidv4} from 'uuid'; import { v4 as uuidv4 } from 'uuid'
import { PageProColumns } from "./PageList"; import { PageProColumns } from './PageList'
import TableBtnWithPermission from "./TableBtnWithPermission"; import TableBtnWithPermission from './TableBtnWithPermission'
import { $t } from "@common/locales"; import { $t } from '@common/locales'
import { useGlobalContext } from "@common/contexts/GlobalStateContext"; import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { Form } from "antd"; import { Form } from 'antd'
import { debounce } from "lodash-es"; import { debounce } from 'lodash-es'
interface EditableTableProps<T> { interface EditableTableProps<T> {
configFields: PageProColumns<T>[]; configFields: PageProColumns<T>[]
value?: T[]; // 外部传入的值 value?: T[] // 外部传入的值
className?: string; className?: string
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
// tableProps?: TableProps<T>; // tableProps?: TableProps<T>;
disabled?:boolean disabled?: boolean
getFromRef?:(form:MutableRefObject<EditableFormInstance<T> | undefined>)=>void getFromRef?: (form: MutableRefObject<EditableFormInstance<T> | undefined>) => void
} }
const EditableTableNotAutoGen = <T extends { _id: string }>({ const EditableTableNotAutoGen = <T extends { _id: string }>({
configFields, configFields,
value, // value 现在是外部传入的配置项数组 value, // value 现在是外部传入的配置项数组
onChange, // onChange 现在是当配置项数组变化时的回调函数 onChange, // onChange 现在是当配置项数组变化时的回调函数
// tableProps, // tableProps,
disabled, disabled,
className, className,
getFromRef getFromRef
}: EditableTableProps<T>) => { }: EditableTableProps<T>) => {
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]); const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
const {state} = useGlobalContext() const { state } = useGlobalContext()
const form =useRef<EditableFormInstance<T>>(); const form = useRef<EditableFormInstance<T>>()
const [tableForm] = Form.useForm(); const [tableForm] = Form.useForm()
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
value?.map((item) => item._id) || ['1234']
);
useEffect(()=>{ useEffect(() => {
getFromRef?.(form) getFromRef?.(form)
},[form]) }, [form])
useEffect(() => { useEffect(() => {
const newValue = value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}] const newValue = value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }]
setConfigurations(newValue); setConfigurations(newValue)
setTimeout(()=>validateForm(),1000) setTimeout(() => validateForm(), 1000)
}, [value]); }, [value])
const validateForm = async ()=>{ const validateForm = async () => {
await tableForm.validateFields(); await tableForm.validateFields()
} }
const translatedColumns = useMemo(()=>configFields.map((x)=>( const translatedColumns = useMemo(
{...x, () =>
title:$t(x.title as string), configFields.map((x) => ({
formItemProps:{ ...x,
...(x. formItemProps || {}), title: $t(x.title as string),
rules:[...(x.formItemProps?.rules || []).map((r:Record<string, string>)=>{ formItemProps: {
if(r.message){ ...(x.formItemProps || {}),
r.message = $t(r.message) rules: [
} ...(x.formItemProps?.rules || []).map((r: Record<string, string>) => {
return r if (r.message) {
})], r.message = $t(r.message)
}})),[state.language,configFields]) }
return r
const debouncedOnChange = useMemo(() => debounce((value) => { })
onChange?.(value); ]
}, 500), [onChange]); }
})),
[state.language, configFields]
)
return ( const debouncedOnChange = useMemo(
<EditableProTable<T> () =>
className={className} debounce((value) => {
columns={translatedColumns} onChange?.(value)
onChange={debouncedOnChange} }, 500),
controlled={true} [onChange]
rowKey="_id" )
value={configurations as T[]}
size="small"
editableFormRef={form}
bordered={true}
recordCreatorProps={false}
editable={ {
type: 'multiple',
form: tableForm,
// errorType:'default',
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
actionRender: (row, config) => {
return [
<TableBtnWithPermission key="delete" btnType="delete" btnTitle="删除"
onClick={() => {
setConfigurations((prev)=>{
const tmpPreData = [...prev];
tmpPreData.splice(Number(config.index), 1);
onChange?.(tmpPreData);
return tmpPreData});
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
}}/>,
];
},
onChange: setEditableRowKeys
}}
/>
)
}
export default EditableTableNotAutoGen; return (
<EditableProTable<T>
className={className}
columns={translatedColumns}
onChange={debouncedOnChange}
controlled={true}
rowKey="_id"
value={configurations as T[]}
size="small"
editableFormRef={form}
bordered={true}
recordCreatorProps={false}
editable={{
type: 'multiple',
form: tableForm,
// errorType:'default',
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
actionRender: (row, config) => {
return [
<TableBtnWithPermission
key="delete"
btnType="delete"
btnTitle="删除"
onClick={() => {
setConfigurations((prev) => {
const tmpPreData = [...prev]
tmpPreData.splice(Number(config.index), 1)
onChange?.(tmpPreData)
return tmpPreData
})
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
}}
/>
]
},
onChange: setEditableRowKeys
}}
/>
)
}
export default EditableTableNotAutoGen
@@ -1,165 +1,195 @@
import {useEffect, useMemo, useState} from 'react'; import { useEffect, useMemo, useState } from 'react'
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'; import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid'
import { ColumnsType } from 'antd/es/table'; import WithPermission from './WithPermission'
import WithPermission from './WithPermission'; import { $t } from '@common/locales'
import { $t } from '@common/locales'; import { COLUMNS_TITLE } from '@common/const/const'
import { COLUMNS_TITLE, VALIDATE_MESSAGE } from '@common/const/const'; import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'; import TableBtnWithPermission from './TableBtnWithPermission'
import TableBtnWithPermission from './TableBtnWithPermission';
export interface ConfigField<T> { export interface ConfigField<T> {
title: string; title: string
key: keyof T; key: keyof T
component: React.ReactNode; component: React.ReactNode
renderText?: (value: unknown, record: T) => string; renderText?: (value: unknown, record: T) => string
required?: boolean; required?: boolean
ellipsis?:boolean ellipsis?: boolean
unRender?:(form:FormInstance)=>boolean unRender?: (form: FormInstance) => boolean
} }
interface EditableTableWithModalProps<T> { interface EditableTableWithModalProps<T> {
configFields: ConfigField<T>[]; configFields: ConfigField<T>[]
value?: T[]; // 外部传入的值 value?: T[] // 外部传入的值
className?: string; className?: string
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
tableProps?: TableProps<T>; tableProps?: TableProps<T>
disabled?:boolean disabled?: boolean
} }
const EditableTableWithModal = <T extends { _id?: string }>({ const EditableTableWithModal = <T extends { _id?: string }>({
configFields, configFields,
value, // value 现在是外部传入的配置项数组 value, // value 现在是外部传入的配置项数组
onChange, // onChange 现在是当配置项数组变化时的回调函数 onChange, // onChange 现在是当配置项数组变化时的回调函数
tableProps, tableProps,
disabled, disabled,
className className
}: EditableTableWithModalProps<T>) => { }: EditableTableWithModalProps<T>) => {
const [form] = Form.useForm<FormInstance>(); const [form] = Form.useForm<FormInstance>()
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false)
const [configurations, setConfigurations] = useState<T[]>(value ||[]); const [configurations, setConfigurations] = useState<T[]>(value || [])
const [editingConfig, setEditingConfig] = useState<T | null>(null); const [editingConfig, setEditingConfig] = useState<T | null>(null)
const {state} = useGlobalContext() const { state } = useGlobalContext()
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>() const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
const showModal = (config?: T) => { const showModal = (config?: T) => {
if (config) { if (config) {
form.setFieldsValue(config as Record<string, unknown>); form.setFieldsValue(config as Record<string, unknown>)
setEditingConfig(config); setEditingConfig(config)
} else {
form.resetFields()
setEditingConfig(null)
}
setIsModalVisible(true)
}
const handleCancel = () => {
setIsModalVisible(false)
}
const handleDelete = (_id: string) => {
const newConfigurations = configurations.filter((config) => config._id !== _id)
setConfigurations(newConfigurations)
onChange?.(newConfigurations)
}
const handleOk = () => {
form
.validateFields()
.then((values) => {
let newConfigurations = [...configurations]
if (editingConfig && editingConfig._id) {
newConfigurations = newConfigurations?.map((config) =>
config._id === editingConfig._id ? { ...config, ...values } : config
)
} else { } else {
form.resetFields(); const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>
setEditingConfig(null); newConfigurations.push(newConfig as T)
} }
setIsModalVisible(true); setConfigurations(newConfigurations)
}; onChange?.(newConfigurations)
setIsModalVisible(false)
})
.catch((info) => {
console.log('Validate Failed:', info)
})
}
const handleCancel = () => { useEffect(() => {
setIsModalVisible(false); setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [])
}; }, [value])
const handleDelete = (_id: string) => { const columns = useMemo(
const newConfigurations = configurations.filter(config => config._id !== _id); () => [
setConfigurations(newConfigurations); ...configFields.map(({ title, key, renderText }) => ({
onChange?.(newConfigurations); title: $t(title),
}; dataIndex: key as string,
key: key as string,
const handleOk = () => { render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
form.validateFields() ellipsis: true
.then(values => { })),
let newConfigurations = [...configurations]; ...(disabled
if (editingConfig && editingConfig._id) { ? []
newConfigurations = newConfigurations?.map(config => : [
config._id === editingConfig._id ? { ...config, ...values } : config {
); title: COLUMNS_TITLE.operate,
} else { key: 'action',
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>; btnNums: 2,
newConfigurations.push(newConfig as T); render: (_: unknown, record: T) => (
}
setConfigurations(newConfigurations);
onChange?.(newConfigurations);
setIsModalVisible(false);
})
.catch(info => {
console.log('Validate Failed:', info);
});
};
useEffect(() => {
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
}, [value]);
const columns = useMemo(()=>[
...configFields.map(({ title, key, renderText }) => ({
title:$t(title),
dataIndex: key as string,
key: key as string,
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
ellipsis:true
})),
...(disabled ? []:[{
title: COLUMNS_TITLE.operate,
key: 'action',
btnNums:2,
render: (_: unknown, record: T) => (
<> <>
<div className="flex items-center"> <div className="flex items-center">
<TableBtnWithPermission key="add" disabled={disabled} btnType="edit" onClick={()=>{showModal(record)}} btnTitle='编辑'/> <TableBtnWithPermission
key="add"
disabled={disabled}
btnType="edit"
onClick={() => {
showModal(record)
}}
btnTitle="编辑"
/>
<Divider key="div1" type="vertical" /> <Divider key="div1" type="vertical" />
<TableBtnWithPermission key="delete" disabled={disabled} btnType="delete" onClick={()=>{handleDelete(record._id || '')}} btnTitle='删除'/> <TableBtnWithPermission
</div> key="delete"
disabled={disabled}
btnType="delete"
onClick={() => {
handleDelete(record._id || '')
}}
btnTitle="删除"
/>
</div>
</> </>
), )
}] ) }
],[state.language, disabled, configFields]) ])
],
[state.language, disabled, configFields]
)
const formItems = useMemo(() => {
const formItems = useMemo(()=>{ return configFields.map(({ title, key, component, required, unRender }) => {
return configFields.map(({ title,key, component, required,unRender }) => { return unRender && unRender(formsValue) ? null : (
return ( <Form.Item label={$t(title as string)} name={key as string} rules={[{ required }]}>
unRender && unRender(formsValue) ? null : {component}
<Form.Item </Form.Item>
label={$t(title as string)} )
name={key as string} })
rules={[{ required}]} }, [formsValue])
>
{component}
</Form.Item>
)
})
}
,[formsValue])
return (
<>
{!disabled && (
<Button className="" disabled={disabled} onClick={() => showModal()}>
{$t('添加配置')}
</Button>
)}
{configurations.length > 0 && (
<Table
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`}
{...tableProps}
dataSource={configurations}
size="small"
columns={columns}
rowKey="_id"
pagination={false}
/>
)}
<Modal
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
open={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
width={600}
maskClosable={false}
>
<WithPermission access="">
<Form
form={form}
name="editableTableWithModal"
layout="vertical"
scrollToFirstError
onFieldsChange={() => {
setFormsValue(form.getFieldsValue())
}}
// labelCol={{ span: 7 }}
// wrapperCol={{ span: 17}}
autoComplete="off"
>
{formItems}
</Form>
</WithPermission>
</Modal>
</>
)
}
export default EditableTableWithModal
return (
<>
{!disabled && <Button className="" disabled={disabled} onClick={() => showModal()}>{$t('添加配置')}</Button>}
{configurations.length > 0 &&
<Table
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`} {...tableProps} dataSource={configurations} size="small" columns={columns} rowKey="_id" pagination={false}/>}
<Modal
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
open={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
width={600}
maskClosable={false}
>
<WithPermission access=""><Form form={form} name="editableTableWithModal"
layout="vertical"
scrollToFirstError
onFieldsChange={(()=>{
setFormsValue(form.getFieldsValue())
})}
// labelCol={{ span: 7 }}
// wrapperCol={{ span: 17}}
autoComplete="off">
{formItems}
</Form></WithPermission>
</Modal>
</>
);
};
export default EditableTableWithModal;
@@ -1,23 +1,23 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from 'react'
function ErrorBoundary({ children }) { function ErrorBoundary({ children }) {
const [error, setError] = useState(null); const [error, setError] = useState(null)
useEffect(() => { useEffect(() => {
window.addEventListener("error", (event) => { window.addEventListener('error', (event) => {
setError(event.error); setError(event.error)
}); })
}, []); }, [])
if (error) { if (error) {
return ( return (
<div> <div>
<h1>An error occurred</h1> <h1>An error occurred</h1>
<pre>{error.message}</pre> <pre>{error.message}</pre>
</div> </div>
); )
}
return children;
} }
export default ErrorBoundary return children
}
export default ErrorBoundary
@@ -1,66 +1,108 @@
import { ArrowLeftOutlined } from '@ant-design/icons'
import { Button, Tag } from "antd" import WithPermission from '@common/components/aoplatform/WithPermission'
import {useNavigate} from "react-router-dom"; import { $t } from '@common/locales'
import WithPermission from "@common/components/aoplatform/WithPermission"; import { Button, Tag } from 'antd'
import { FC, ReactNode } from "react"; import { FC, ReactNode } from 'react'
import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons"; import { useNavigate } from 'react-router-dom'
import { $t } from "@common/locales";
class InsidePageProps { class InsidePageProps {
showBanner?:boolean = true showBanner?: boolean = true
pageTitle:string| React.ReactNode = '' pageTitle: string | React.ReactNode = ''
tagList?:Array<{label:string|ReactNode}> = [] tagList?: Array<{ label: string | ReactNode }> = []
children:React.ReactNode children: React.ReactNode
showBtn?:boolean = false showBtn?: boolean = false
btnTitle?:string = '' btnTitle?: string = ''
description?:string | React.ReactNode= '' description?: string | React.ReactNode = ''
onBtnClick?:()=>void onBtnClick?: () => void
backUrl?:string = '/' backUrl?: string = '/'
btnAccess?:string btnAccess?: string
showBorder?:boolean = true showBorder?: boolean = true
className?:string = '' className?: string = ''
contentClassName?:string='' contentClassName?: string = ''
headerClassName?:string='' headerClassName?: string = ''
/** 整个页面滚动 */ /** 整个页面滚动 */
scrollPage?:boolean = true scrollPage?: boolean = true
customBtn?:ReactNode customBtn?: ReactNode
} }
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true,customBtn})=>{ const InsidePage: FC<InsidePageProps> = ({
const navigate = useNavigate(); showBanner = true,
pageTitle,
tagList,
showBtn,
btnTitle,
btnAccess,
description,
children,
onBtnClick,
backUrl,
showBorder = true,
className = '',
contentClassName = '',
headerClassName = '',
scrollPage = true,
customBtn
}) => {
const navigate = useNavigate()
const goBack = () => { const goBack = () => {
navigate(backUrl || '/'); navigate(backUrl || '/')
}; }
return ( return (
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]"> <div className={`flex overflow-hidden flex-col flex-1 h-full ${className}`}>
<div className={`h-full flex flex-col flex-1 overflow-hidden ${className}`}> {showBanner && (
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}> <div
<div className="mb-[30px]"> className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]"> >
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button> {!pageTitle && !description && !backUrl && !customBtn ? (
</div>} <></>
<div className="flex justify-between mb-[20px] items-center "> ) : (
<div className="flex items-center gap-TAG_LEFT "> <div className="mb-[30px]">
<p className="text-theme text-[26px] ">{pageTitle}</p> {backUrl && (
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{ <div className="text-[18px] leading-[25px] mb-[12px]">
return ( <Tag key={tag.label as string} bordered={false} >{tag.label}</Tag>) <Button type="text" onClick={goBack}>
})} <ArrowLeftOutlined className="max-h-[14px]" />
</div> {$t('返回')}
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> { </Button>
onBtnClick&&onBtnClick()
}}>{btnTitle}</Button></WithPermission>}
{customBtn}
</div>
<p >
{description}
</p>
</div> </div>
</div>} )}
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>{children}</div> <div className="flex justify-between mb-[20px] items-center ">
<div className="flex items-center gap-TAG_LEFT">
<div className="text-theme text-[26px] ">{pageTitle}</div>
{tagList &&
tagList?.length > 0 &&
tagList?.map((tag) => {
return (
<Tag key={tag.label as string} bordered={false}>
{tag.label}
</Tag>
)
})}
</div>
{showBtn && (
<WithPermission access={btnAccess}>
<Button
type="primary"
onClick={() => {
onBtnClick && onBtnClick()
}}
>
{btnTitle}
</Button>
</WithPermission>
)}
{customBtn}
</div>
<div>{description}</div>
</div>
)}
</div> </div>
) )}
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>
{children}
</div>
</div>
)
} }
export default InsidePage export default InsidePage
@@ -1,72 +1,92 @@
import { Dropdown, Row, Col, Button } from 'antd'; import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import i18n from '@common/locales'; import i18n from '@common/locales'
import { memo, useEffect, useMemo } from 'react'; import { Icon } from '@iconify/react/dist/iconify.js'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'; import { Button, Dropdown } from 'antd'
import { Icon } from '@iconify/react/dist/iconify.js'; import { memo, useEffect, useMemo } from 'react'
const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => { const LanguageItems = [
const { dispatch,state} = useGlobalContext() {
const items = [ key: 'en-US',
{ label: (
key: 'en-US', <Button key="en" type="text" className="flex items-center p-0 bg-transparent border-none">
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent "> English
English </Button>
</Button>, ),
title:'English' title: 'English'
}, },
{ {
key: 'ja-JP', key: 'ja-JP',
label: <Button key="jp" type="text" className="border-none p-0 flex items-center bg-transparent "> label: (
<Button key="jp" type="text" className="flex items-center p-0 bg-transparent border-none">
</Button>,
title: '日本語', </Button>
}, ),
{ title: '日本語'
key: 'zh-TW', },
label: <Button key="tw" type="text" className="border-none p-0 flex items-center bg-transparent "> {
key: 'zh-TW',
</Button>, label: (
title: '繁體中文', <Button key="tw" type="text" className="flex items-center p-0 bg-transparent border-none">
},
{ </Button>
key: 'zh-CN', ),
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent "> title: '繁體中文'
},
</Button>, {
title: '简体中文', key: 'zh-CN',
}, label: (
]; <Button key="cn" type="text" className="flex items-center p-0 bg-transparent border-none">
</Button>
),
title: '简体中文'
}
]
const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
const { dispatch, state } = useGlobalContext()
const langLabel = useMemo(()=>items.find((item) => item?.key === state.language)?.title,[state.language]) const langLabel = useMemo(() => LanguageItems.find((item) => item?.key === state.language)?.title, [state.language])
useEffect(()=>{ useEffect(() => {
const savedLang = sessionStorage.getItem('i18nextLng') const savedLang = i18n.language || sessionStorage.getItem('i18nextLng')
const browserLang = navigator.language || navigator.userLanguage if (savedLang && state.language !== savedLang) {
if(savedLang){ dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang })
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang }); } else if (!savedLang) {
}else{ const browserLang = navigator.language
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang }); const supportedLang = LanguageItems.find((item) => item.key === browserLang) ? browserLang : 'zh-CN'
if (state.language === supportedLang) return
dispatch({ type: 'UPDATE_LANGUAGE', language: supportedLang })
i18n.changeLanguage(supportedLang)
} }
},[ }, [])
])
return ( return (
<Dropdown <Dropdown
trigger={['hover']} trigger={['hover']}
menu={{ menu={{
items, items: LanguageItems,
style:{minWidth:'80px'}, style: { minWidth: '80px' },
onClick: (e) => { onClick: (e) => {
const { key } = e; const { key } = e
dispatch({ type: 'UPDATE_LANGUAGE', language: key }); dispatch({ type: 'UPDATE_LANGUAGE', language: key })
i18n.changeLanguage(key); i18n.changeLanguage(key)
sessionStorage.setItem('i18nextLng', key)
} }
}} }}
> >
<Button className={`border-none ${mode==='dark' ? "text-[#333] hover:text-[#333333b3]" : "text-[#ffffffb3] hover:text-[#fff] "}`} type="default" ghost > <Button
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-language" width="14" height="14"/>{langLabel}</span> className={`border-none ${
</Button> mode === 'dark' ? 'text-[#333] hover:text-[#333333b3]' : 'text-[#ffffffb3] hover:text-[#fff] '
}`}
type="default"
ghost
>
<span className="flex items-center gap-[8px]">
{' '}
<Icon icon="ic:baseline-language" width="14" height="14" />
{langLabel}
</span>
</Button>
</Dropdown> </Dropdown>
); )
}; }
export default memo(LanguageSetting); export default memo(LanguageSetting)
@@ -1,275 +1,199 @@
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from 'antd'
import { GetProp, TransferProps, TreeDataNode, theme, Transfer, Tree, Spin } from "antd"; import { DataNode } from 'antd/es/tree'
import { DataNode, TreeProps } from "antd/es/tree"; import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; import { ApartmentOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons'
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons"; import { ColumnsType } from 'antd/es/table'
import { cloneDeep, debounce } from "lodash-es"; import { $t } from '@common/locales'
import { ColumnsType } from "antd/es/table"; import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { $t } from "@common/locales";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
type TransferItem = GetProp<TransferProps, 'dataSource'>[number];
export type TransferTableProps<T> = { export type TransferTableProps<T> = {
request?:(k?:string)=>Promise<{data:T[],success:boolean}> request?: (k?: string) => Promise<{ data: T[]; success: boolean }>
columns: ColumnsType<T> columns: ColumnsType<T>
primaryKey:string primaryKey: string
onSelect:(selectedData:T[])=>void onSelect: (selectedData: string[]) => void
tableType?:'member'|'api' tableType?: 'member' | 'api'
disabledData:string[] disabledData: string[]
searchPlaceholder?:string searchPlaceholder?: string
} }
export type TransferTableHandle<T> = { export type TransferTableHandle<T> = {
selectedData: () => T[]; selectedRowKeys: () => React.Key[]
selectedRowKeys: () => React.Key[];
} }
interface TreeTransferProps { interface TreeTransferProps {
dataSource: TreeDataNode[]; dataSource: TreeDataNode[]
targetKeys: TransferProps['targetKeys']; targetKeys: TransferProps['targetKeys']
onChange: TransferProps['onChange']; onChange: TransferProps['onChange']
} }
// Customize Table Transfer
const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) =>
selectedKeys.includes(eventKey);
const generateTree = ( const generateTree = (
treeNodes: TreeDataNode[] = [], treeNodes: TreeDataNode[] = [],
checkedKeys: TreeTransferProps['targetKeys'] = [], checkedKeys: TreeTransferProps['targetKeys'] = [],
filterUnchecked: boolean = false, filterUnchecked: boolean = false,
disabledData:string[], disabledData: string[],
filteredItems?:Set<string> filteredItems?: Set<string>
): TreeDataNode[] => { ): TreeDataNode[] => {
const checkedKeysSet = new Set(checkedKeys); const checkedKeysSet = new Set(checkedKeys)
return treeNodes return treeNodes
.map(({ children, ...props }) => { .map(({ children, ...props }) => {
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems); const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems)
const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1) const isDisabled =
? true !filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1
: (filterUnchecked ? false : checkedKeysSet.has(props.id as string)); ? true
const hasEnabledChild = childNodes.some(node => !node.disabled); : filterUnchecked
? false
: checkedKeysSet.has(props.id as string)
const hasEnabledChild = childNodes.some((node) => !node.disabled)
return { return {
...props, ...props,
title: <span className="w-full truncate ml-[4px] block">{props.name}</span>, title: <span className="w-full truncate ml-[4px] block">{props.name}</span>,
key: props.id, key: props.id,
disabled: isDisabled && !hasEnabledChild, disabled: isDisabled && !hasEnabledChild,
children: childNodes, children: childNodes
};
})
.filter(node => {
let res:boolean= true
if(filterUnchecked){
res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) )
} }
})
if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){ .filter((node) => {
let res: boolean = true
if (filterUnchecked) {
res =
(!disabledData || disabledData.indexOf(node.key as string) === -1) &&
(checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0))
}
if (
filterUnchecked &&
filteredItems &&
filteredItems.size &&
!filteredItems.has(node.key as string) &&
!(node.children && node.children.length > 0)
) {
return false return false
} }
return res return res
} })
) }
};
const TransferTree = (props)=>{ const MemberTransfer = forwardRef<
const { direction, token, tableHeight, dataSource, targetKeys, onItemSelect, onItemSelectAll,checkedKey,selectedKeys, filteredItems ,disabledData} = props; TransferTableHandle<{ [k: string]: unknown }>,
const [expandedKeys, setExpandedKeys] = useState<string[]>([]); TransferTableProps<{ [k: string]: unknown }>
>(<T extends { [k: string]: unknown }>(props: TransferTableProps<T>, ref: Ref<TransferTableHandle<T>>) => {
const { request, columns, primaryKey, onSelect, tableType, disabledData = [], searchPlaceholder } = props
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([])
const [dataSource, setDataSource] = useState<DataNode[]>([])
const parentRef = useRef<HTMLDivElement>(null)
const [loading, setLoading] = useState<boolean>(false)
const { state } = useGlobalContext()
const [expandedKeys, setExpandedKeys] = useState<string[]>([])
const [searchWord, setSearchWord] = useState<string>('')
useEffect(() => {
setTargetKeys(disabledData)
}, [disabledData])
const getExpandedKeys = (newData:TreeDataNode[], expandedSet:Set<string> = new Set())=>{ useImperativeHandle(ref, () => ({
newData.forEach((item)=>{ selectedRowKeys: () => targetKeys
if(item.children && item.children.length > 0){ }))
expandedSet.add(item.key)
getExpandedKeys(item.children,expandedSet) const translatedDataSource = useMemo(() => {
const loop = (data: DataNode[]): DataNode[] =>
data?.map((item) => {
const strTitle: string = item.name === '所有成员' ? ($t(item.name) as string) : (item.name as string)
const index = strTitle.indexOf(searchWord)
const beforeStr = strTitle.substring(0, index)
const afterStr = strTitle.slice(index + searchWord.length)
const title =
index > -1 ? (
<span className="w-[calc(100%-16px)] truncate" title={strTitle}>
{beforeStr}
<span className="text-theme">{searchWord}</span>
{afterStr}
</span>
) : (
<span className="w-[calc(100%-16px)] truncate" title={`${strTitle}`}>
{strTitle}
</span>
)
if (item.children) {
return {
...item,
title,
disableCheckbox: disabledData.indexOf(item.key as string) !== -1,
icon: <ApartmentOutlined />,
children: loop(item.children as T[])
}
}
return {
...item,
title,
icon: <UserOutlined />,
isLeaf: true,
disableCheckbox: disabledData.indexOf(item.key as string) !== -1
}
})
return loop(dataSource)
}, [dataSource, state.language, searchWord])
const getInitExpandKeys = (data: T[], expandKeys: string[] = []) => {
data.forEach((item) => {
if (item.children?.length) {
expandKeys.push(item.key as string)
getInitExpandKeys(item.children, expandKeys)
} }
}) })
return expandedSet return expandKeys
} }
const treeData:TreeDataNode[] = useMemo(()=>{ const getDataSource = () => {
const filteredSet = filteredItems && filteredItems.length > 0 ? new Set(filteredItems.map((x)=>x.id)) : new Set() setLoading(true)
const res = dataSource && dataSource.length > 0 ? generateTree(dataSource, targetKeys,direction === 'right',disabledData,filteredSet) : [] request &&
setExpandedKeys(Array.from(getExpandedKeys(res))) request()
return res .then((res) => {
},[ const { data, success } = res
dataSource, targetKeys,direction ,disabledData,filteredItems setDataSource(success ? data : [])
]) setExpandedKeys(getInitExpandKeys(success ? data : []))
})
.finally(() => {
setLoading(false)
})
}
const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { useEffect(() => {
setExpandedKeys(expandedKeysValue as string[]); getDataSource()
}; }, [])
return ( return (
<div ref={parentRef}>
<div style={{ padding: token.paddingXS }}> <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="">
<Tree <Input
className="icon-tree" className="mb-[10px]"
blockNode placeholder={searchPlaceholder}
checkable onChange={(e) => setSearchWord(e.target.value)}
showIcon value={searchWord}
checkedKeys={direction === 'left' ? Array.from(new Set([...checkedKey,...disabledData])) : selectedKeys } />
defaultExpandAll <>
expandedKeys={expandedKeys} {translatedDataSource && translatedDataSource.length > 0 ? (
onExpand={onExpand} <Tree
height={tableHeight} checkable
icon={(props)=> { return (props.type === 'member' ? <UserOutlined /> :<ApartmentOutlined /> )} } expandedKeys={expandedKeys}
treeData={treeData} checkedKeys={targetKeys}
onCheck={(_checkedKeys, e:{checked: boolean, checkedNodes, node, event, halfCheckedKeys}) => { selectable={false}
if(e.checked){ onCheck={(e) => {
onItemSelectAll( _checkedKeys, e.checked); setTargetKeys(e)
}else{ onSelect((e as string[])?.filter((x) => disabledData.indexOf(x as string) === -1) || [])
const checkedKeyArrFromTree = e.checkedNodes.map(node => node.key) }}
onItemSelectAll((checkedKey as string[]).filter(key => checkedKeyArrFromTree.indexOf(key) === -1),e.checked) onExpand={setExpandedKeys}
} treeData={translatedDataSource}
}} blockNode
onSelect={(_, { node: { key } }) => { showIcon
onItemSelect(key as string, !isChecked(checkedKey, key)); />
}} ) : (
/> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</>
</Spin>
</div> </div>
) )
}
const MemberTransfer= forwardRef<TransferTableHandle<{[k:string]:unknown}>, TransferTableProps<{[k:string]:unknown}>>(
<T extends {[k:string]:unknown}>(props: TransferTableProps<T>, ref:Ref<TransferTableHandle<T>>) => {
const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props
const [tableHeight, setTableHeight] = useState(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3);
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([]);
const [dataSource, setDataSource] = useState<DataNode[] >([])
const parentRef = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState<boolean>(false)
const {state} = useGlobalContext()
useEffect(()=>{
setTargetKeys(disabledData)
},[disabledData])
useImperativeHandle(ref, () =>({
selectedData: () => dataSource,
selectedRowKeys: () => targetKeys,}))
const onChange: TreeTransferProps['onChange'] = (keys) => {
onSelect?.(new Set(keys))
setTargetKeys(Array.from(new Set(keys)));
};
const { token } = theme.useToken();
const transferDataSource: TransferItem[] = useMemo(()=>{
function flatten(list: TreeDataNode[] = [], res:TransferItem[]) {
list.forEach((item) => {
res.push({...item, title:item.title === '所有成员' ? $t((item as unknown as {title:string}).title):item.title }as TransferItem);
flatten(item.children,res);
});
}
const res:TransferItem[] =[]
flatten(dataSource,res);
return res
},[
dataSource, state.language
])
const translatedDataSource = useMemo(()=>dataSource.map((item)=>({
...item,
name:item.name === '所有成员' ? $t((item as unknown as {name:string}).name):item.name,
})),[dataSource, state.language])
let memo: Record<string, boolean> = {};
const handlerFilterOption = (inputValue: string, item: any, parentResult: boolean = false, childrenSet: Set<string> = new Set()): boolean => {
const cacheKey = `${inputValue}_${item.key}`;
if (memo[cacheKey]) {
return memo[cacheKey];
}
childrenSet.add(item.key);
let result = item.title.includes(inputValue) || parentResult
if (item.children) {
for (const child of item.children) {
if (handlerFilterOption(inputValue, child, result,childrenSet)) {
result = true;
}
}
}
if (result) {
memo[cacheKey] = result;
childrenSet.forEach((key) => {
memo[`${inputValue}_${key}`] = result;
});
}
return result;
};
const getDataSource = ()=>{
setLoading(true)
request && request().then((res)=>{
const {data,success} = res
setDataSource(success? data : [])
}).finally(()=>{setLoading(false)})
}
useEffect(() => {
getDataSource()
const handleResize = () => {
setTableHeight(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3)
};
const debouncedHandleResize = debounce(handleResize, 200);
// 监听窗口大小变化
window.addEventListener('resize', debouncedHandleResize);
handleResize();
return () => {
window.removeEventListener('resize', debouncedHandleResize);
};
}, []);
return (
<div ref={parentRef}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
<Transfer
showSearch
onSearch={(dir)=>{
memo = {};
}}
listStyle={{width:'408px'}}
disabledData={disabledData}
filterOption={(inputValue: string, item: any) => handlerFilterOption(inputValue, item)}
targetKeys={targetKeys}
dataSource={transferDataSource}
className="tree-transfer"
render={(item) => item.title!}
showSelectAll={false}
onChange={onChange}
titles={['','']}
>
{({ direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems}) => {
const treeProps = {
dataSource:translatedDataSource, direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems,token,tableHeight,targetKeys,disabledData
}
if (direction === 'left') {
const checkedKey = [...selectedKeys, ...targetKeys as string[]];
return (
<TransferTree {...treeProps} checkedKey={checkedKey} />
);
}
if(direction === 'right'){
const checkedKey = [...selectedKeys,...targetKeys as string[]];
return (
<TransferTree {...treeProps} checkedKey={checkedKey} />
);
}
}}
</Transfer>
</Spin>
</div>
);
}) })
export default MemberTransfer; export default MemberTransfer
@@ -0,0 +1,22 @@
import React, { useEffect, useState } from 'react'
import { Result, Skeleton } from 'antd'
const NotFound: React.FC = () => {
const [showPage, setShowPage] = useState<boolean>(false)
useEffect(() => {
setTimeout(() => setShowPage(true), 1000)
}, [])
return (
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
{showPage ? (
<Result className="w-full" status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
) : (
<Skeleton active />
)}
</div>
)
}
export default NotFound
@@ -1,243 +1,370 @@
import { SearchOutlined } from '@ant-design/icons'
import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd'; import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import { DragSortTable, ProTable } from '@ant-design/pro-components'
import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components'; import WithPermission from '@common/components/aoplatform/WithPermission'
import { import { PERMISSION_DEFINITION } from '@common/const/permissions'
DragSortTable, import { withMinimumDelay } from '@common/utils/ux'
ProTable, import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd'
} from '@ant-design/pro-components'; import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
import './PageList.module.css'
import {SearchOutlined} from "@ant-design/icons";
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import WithPermission from '@common/components/aoplatform/WithPermission'; import {
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'; ChangeEvent,
import { useGlobalContext } from '../../contexts/GlobalStateContext'; RefAttributes,
import { PERMISSION_DEFINITION } from '@common/const/permissions'; forwardRef,
import { withMinimumDelay } from '@common/utils/ux'; useEffect,
useImperativeHandle,
useMemo,
useRef,
useState
} from 'react'
import { useGlobalContext } from '../../contexts/GlobalStateContext'
import './PageList.module.css'
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T , ValueType> & {btnNums? : number} export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> { interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
id?:string id?: string
columns: PageProColumns<T,'text'>[] columns: PageProColumns<T, 'text'>[]
request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}> request?: (
dropMenu?:MenuProps params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
searchPlaceholder?:string sorter: unknown,
showPagination?:boolean filter: unknown
primaryKey?:string ) => Promise<{ data: T[]; success: boolean }>
addNewBtnTitle?:string dropMenu?: MenuProps
addNewBtnAccess?:string searchPlaceholder?: string
tableClickAccess?:string showPagination?: boolean
onAddNewBtnClick?:()=>void primaryKey?: string
beforeSearchNode?:React.ReactNode[] addNewBtnTitle?: string
onSearchWordChange?:(e:ChangeEvent<HTMLInputElement>) => void addNewBtnAccess?: string
afterNewBtn?:React.ReactNode[] tableClickAccess?: string
dragSortKey?:string onAddNewBtnClick?: () => void
onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void> beforeSearchNode?: React.ReactNode[]
tableTitle?:string onSearchWordChange?: (e: ChangeEvent<HTMLInputElement>) => void
dataSource?:T[] afterNewBtn?: React.ReactNode[]
onRowClick?:(record:T)=>void dragSortKey?: string
showColSetting?:boolean onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
minVirtualHeight?:number tableTitle?: string
besidesTableHeight?:number dataSource?: T[]
noTop?:boolean onRowClick?: (record: T) => void
tableClass?:string showColSetting?: boolean
tableTitleClass?:string minVirtualHeight?: number
addNewBtnWrapperClass?:string besidesTableHeight?: number
delayLoading?:boolean noTop?: boolean
noScroll?:boolean tableClass?: string
tableTitleClass?: string
addNewBtnWrapperClass?: string
delayLoading?: boolean
noScroll?: boolean
/* 前端分页的表格,需要传入该字段以支持后端搜索 */ /* 前端分页的表格,需要传入该字段以支持后端搜索 */
manualReloadTable?:()=>void manualReloadTable?: () => void
} }
const PageList = <T extends Record<string, unknown>>(
props: React.PropsWithChildren<PageListProps<T>>,
const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChildren<PageListProps<T>>,ref: React.Ref<ActionType>) => { ref: React.Ref<ActionType>
const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass = '',tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props ) => {
const parentRef = useRef<HTMLDivElement>(null); const {
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight); id,
const [tableWidth, setTableWidth] = useState<number|undefined>(undefined); columns,
const actionRef = useRef<ActionType>(); request,
const [allowTableClick,setAllowTableClick] = useState<boolean>(false) dropMenu,
const {accessData,checkPermission,accessInit,state} = useGlobalContext() searchPlaceholder,
showPagination = true,
primaryKey = 'id',
addNewBtnTitle,
addNewBtnAccess,
tableClickAccess,
tableClass,
onAddNewBtnClick,
beforeSearchNode,
onSearchWordChange,
manualReloadTable,
afterNewBtn,
dragSortKey,
onDragSortEnd,
tableTitle,
rowSelection,
onChange,
dataSource,
onRowClick,
showColSetting = false,
minVirtualHeight,
noTop,
addNewBtnWrapperClass = '',
tableTitleClass,
delayLoading = true,
besidesTableHeight,
noScroll
} = props
const parentRef = useRef<HTMLDivElement>(null)
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight)
const [tableWidth, setTableWidth] = useState<number | undefined>(undefined)
const actionRef = useRef<ActionType>()
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
const [minTableWidth, setMinTableWidth] = useState<number>(0) const [minTableWidth, setMinTableWidth] = useState<number>(0)
// 使用useImperativeHandle来自定义暴露给父组件的实例值 useImperativeHandle(ref, () => actionRef.current!)
useImperativeHandle(ref, () => actionRef.current!);
useEffect(()=>{ useEffect(() => {
actionRef?.current?.reload?.() actionRef?.current?.reload?.()
},[state.language]) }, [state.language])
const lastAccess = useMemo(()=>{
if(!tableClickAccess) return true
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
},[allowTableClick, accessData,accessInit])
useEffect(()=>{ const lastAccess = useMemo(() => {
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true) if (!tableClickAccess) return true
},[accessData]) return checkPermission(tableClickAccess as keyof (typeof PERMISSION_DEFINITION)[0])
}, [allowTableClick, accessData, accessInit])
const resizeObserverRef = useRef<ResizeObserver |null >(null);
useEffect(() => {
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
}, [accessData])
const resizeObserverRef = useRef<ResizeObserver | null>(null)
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
if (parentRef.current && !noScroll) { if (parentRef.current && !noScroll) {
const res = parentRef.current.getBoundingClientRect(); const res = parentRef.current.getBoundingClientRect()
const height = res.height - ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0) + 1); // 减去顶部按钮、底部分页、表头高度 const height =
setTableWidth(minTableWidth - 5> res.width ? minTableWidth : undefined); res.height -
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight)); ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) + (besidesTableHeight ?? 0) + 1) // 减去顶部按钮、底部分页、表头高度
setTableWidth(minTableWidth - 5 > res.width ? minTableWidth : undefined)
height &&
setTableHeight(
minVirtualHeight === undefined ? height : height > minVirtualHeight ? height : minVirtualHeight
)
} }
}; }
const debouncedHandleResize = debounce(handleResize, 200); const debouncedHandleResize = debounce(handleResize, 200)
if (!resizeObserverRef.current && !noScroll) { if (!resizeObserverRef.current && !noScroll) {
// 创建一个 ResizeObserver 来监听高度变化,只创建一次 // 创建一个 ResizeObserver 来监听高度变化,只创建一次
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize); resizeObserverRef.current = new ResizeObserver(debouncedHandleResize)
// 开始监听 // 开始监听
if (parentRef.current && !minVirtualHeight) { if (parentRef.current && !minVirtualHeight) {
resizeObserverRef.current.observe(parentRef.current); resizeObserverRef.current.observe(parentRef.current)
} }
} }
// 在 minTableWidth 变化时手动触发 handleResize // 在 minTableWidth 变化时手动触发 handleResize
handleResize(); handleResize()
// 清理函数 // 清理函数
return () => { return () => {
if (resizeObserverRef.current) { if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect(); resizeObserverRef.current.disconnect()
resizeObserverRef.current = null; resizeObserverRef.current = null
} }
}; }
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项 }, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]) // 将相关依赖项作为 useEffect 的依赖项
const newColumns = useMemo(() => {
let width: number = 0
const res = columns?.map((x, index) => {
const newColumns = useMemo(()=>{ const sorter = localStorage.getItem(`${id}_sorter`)
let width:number = 0 const filters = localStorage.getItem(`${id}_filters`)
const res = columns?.map( x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
(x, index)=>{ if (sorter && x.sorter) {
const sorter = localStorage.getItem(`${id}_sorter`) const sorterObj = JSON.parse(sorter)
const filters = localStorage.getItem(`${id}_filters`) const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email') x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
if(sorter && x.sorter){ // x.showSorterTooltip = {target:'sorter-icon'}
const sorterObj = JSON.parse(sorter) }
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex if (filters && x.filters) {
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined const filtersObj = JSON.parse(filters)
// x.showSorterTooltip = {target:'sorter-icon'} const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
} x.defaultFilteredValue = filtersObj?.[xName as string]
if(filters && x.filters){ }
const filtersObj = JSON.parse(filters) if ((index === columns.length - 1 || x.key === 'option') && x.btnNums) {
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
x.defaultFilteredValue = filtersObj?.[xName as string] x.width = Math.max(optionWidth, 54)
} }
if((index === columns.length -1 || x.key === 'option') && x.btnNums){ width += Number(x.width ?? (x.filters || x.sorter ? 120 : 100))
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21 return x
x.width = Math.max(optionWidth, 54) })
} setMinTableWidth(width)
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
return x})
setMinTableWidth(width)
return res return res
},[columns]) }, [columns])
const headerTitle = ()=>{ const headerTitle = () => {
return ( return (
<>{ <>
tableTitle ? <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span> : ( {tableTitle ? (
addNewBtnTitle ? <WithPermission access={addNewBtnAccess} ><Button type="primary" className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`} onClick={onAddNewBtnClick}>{addNewBtnTitle}</Button></WithPermission> : undefined <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span>
) ) : addNewBtnTitle ? (
<WithPermission access={addNewBtnAccess}>
} <Button
{afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined} type="primary"
</> className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`}
) onClick={onAddNewBtnClick}
>
{addNewBtnTitle}
</Button>
</WithPermission>
) : undefined}
{afterNewBtn ? (afterNewBtn as React.ReactNode[]) : undefined}
</>
)
} }
const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => { const getTableActions = () => {
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined); return [
}; ...(beforeSearchNode ? [beforeSearchNode] : []),
...(searchPlaceholder
? [
<Input
key="search-input"
className="my-btnbase ml-btnbase"
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
onPressEnter={() => {
if (manualReloadTable) {
manualReloadTable()
return
}
if (actionRef.current) {
actionRef.current.reset?.()
actionRef.current.reload?.()
}
}}
allowClear
placeholder={searchPlaceholder}
prefix={
<SearchOutlined
className="cursor-pointer"
onClick={() => {
if (actionRef.current) {
actionRef.current.reset?.()
actionRef.current.reload?.()
}
}}
/>
}
/>
]
: [])
]
}
const requestWithDelay = (
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
sort: unknown,
filter: unknown
) => {
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false ? 0 : undefined)
}
return ( return (
<div ref={parentRef} className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag':''} ${tableClass ?? ''}`}style={{ height: '100%' }}> <div
{dragSortKey? <DragSortTable<T> ref={parentRef}
className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag' : ''} ${tableClass ?? ''}`}
style={{ height: '100%' }}
>
{dragSortKey ? (
<DragSortTable<T>
actionRef={actionRef} actionRef={actionRef}
columns={newColumns} columns={newColumns}
rowKey={primaryKey} rowKey={primaryKey}
search={false} search={false}
pagination={false} pagination={
showPagination
? {
showSizeChanger: true,
showQuickJumper: true,
size: 'default'
}
: false
}
request={request} request={request}
dragSortKey={dragSortKey} dragSortKey={dragSortKey}
onDragSortEnd={onDragSortEnd} onDragSortEnd={onDragSortEnd}
scroll={noScroll ? undefined :{ y: tableHeight }} scroll={noScroll ? undefined : { y: tableHeight }}
options={{ options={{
reload: false, reload: false,
density: false, density: false,
setting: false, setting: false
}} }}
headerTitle={ toolbar={{
headerTitle() actions: getTableActions()
} }}
/> : <ProTable<T> headerTitle={headerTitle()}
/>
) : (
<ProTable<T>
actionRef={actionRef} actionRef={actionRef}
columns={newColumns} columns={newColumns}
virtual virtual
scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }} scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
size="middle" size="middle"
rowSelection={rowSelection} rowSelection={rowSelection}
tableAlertRender={false} tableAlertRender={false}
tableAlertOptionRender={false} tableAlertOptionRender={false}
request={request ? requestWithDelay : undefined} request={request ? requestWithDelay : undefined}
toolBarRender={() => [ toolBarRender={() => [
dropMenu ? (<Dropdown dropMenu ? (
key="menu" <Dropdown key="menu" menu={dropMenu}>
menu={dropMenu} <Button></Button>
> </Dropdown>
<Button> ) : null
</Button>
</Dropdown>):null,
]} ]}
toolbar={{ toolbar={{
actions:[...[beforeSearchNode],...[searchPlaceholder?<Input className="my-btnbase ml-btnbase" onChange={ onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{actionRef.current?.reload?.()}}/>}/>:null]], actions: getTableActions()
}} }}
options={{ options={{
reload: false, reload: false,
density: false, density: false,
setting: showColSetting ? { setting: showColSetting
draggable:false, ? {
showListItemOption:false draggable: false,
} :false, showListItemOption: false
}
: false
}} }}
showSorterTooltip={false} showSorterTooltip={false}
columnsState={{persistenceType:'localStorage',persistenceKey:id}} columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
pagination={showPagination ? { pagination={
showSizeChanger: true, showPagination
showQuickJumper: true, ? {
size:'default' showSizeChanger: true,
}:false} showQuickJumper: true,
size: 'default'
}
: false
}
onChange={(
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<T> | SorterResult<T>[],
extra: TableCurrentDataSource<T>
) => {
localStorage.setItem(`${id}_filters`, JSON.stringify(filters))
!Array.isArray(sorter) &&
localStorage.setItem(
`${id}_sorter`,
JSON.stringify({ columnKey: sorter?.columnKey, order: sorter?.order })
)
onChange?.(pagination, filters, sorter, extra)
}}
rowKey={primaryKey} rowKey={primaryKey}
onChange={(pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>, sorter: SorterResult<T> | SorterResult<T>[],extra:TableCurrentDataSource<T>) =>{
localStorage.setItem(`${id}_filters`,JSON.stringify(filters))
!Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order}))
onChange?.(pagination,filters,sorter,extra)}}
dataSource={dataSource} dataSource={dataSource}
search={false} search={false}
headerTitle={ headerTitle={headerTitle()}
headerTitle() onRow={
onRowClick && allowTableClick
? (record) => ({
onClick: () => {
onRowClick(record)
}
})
: undefined
} }
onRow={onRowClick && allowTableClick ? (record) => ({ rowClassName={() => (onRowClick && allowTableClick ? 'cursor-pointer' : '')}
onClick: () => { />
onRowClick(record); )}
}
}):undefined}
rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''}
/>}
</div> </div>
); )
}; }
export default forwardRef(PageList) as <T extends Record<string,unknown>>(props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }) => ReturnType<typeof PageList>; export default forwardRef(PageList) as <T extends Record<string, unknown>>(
props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }
) => ReturnType<typeof PageList>
@@ -0,0 +1,100 @@
import { App, Form, Input, Row, Table } from 'antd'
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
import { useFetch } from '@common/hooks/http.ts'
import { BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { $t } from '@common/locales'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { PolicyPublishModalHandle, PolicyPublishModalProps } from '@common/const/type'
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle, PolicyPublishModalProps>((props, ref) => {
const { message } = App.useApp()
const { data } = props
const [form] = Form.useForm()
const { fetchData } = useFetch()
const { state } = useGlobalContext()
const publish: () => Promise<boolean | string | Record<string, unknown>> = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then((value) => {
const body = { ...value, source: data.source }
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish', {
method: 'POST',
eoBody: body,
eoTransformKeys: ['versionName']
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(response)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
})
}
useImperativeHandle(ref, () => ({
publish
}))
useEffect(() => {
form.setFieldsValue(data)
}, [data])
const translatedPolicyColumns = useMemo(
() =>
PolicyPublishColumns.map((x) => ({
...x,
title: typeof x.title === 'string' ? $t(x.title) : x.title
})),
[state.language]
)
return (
<>
<WithPermission access="">
<Form
className=" mx-auto"
form={form}
labelAlign="left"
layout="vertical"
scrollToFirstError
name="publishApprovalModalContent"
// labelCol={{span: 3}}
// wrapperCol={{span: 21}}
autoComplete="off"
>
<Form.Item label={$t('发布名称')} name="versionName" rules={[{ required: true, whitespace: true }]}>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item label={$t('描述')} name="desc">
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
<span>{$t('策略列表')}</span>
</Row>
<Row className="mb-mbase ">
<Table
columns={translatedPolicyColumns}
bordered={true}
rowKey="name"
size="small"
dataSource={data.strategies || []}
pagination={false}
/>
{!data?.isPublish && data?.unpublishMsg && <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
</Row>
</Form>
</WithPermission>
</>
)
})
@@ -1,231 +1,402 @@
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd"; import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd'
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react"; import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx"; import {
import {useFetch} from "@common/hooks/http.ts"; PublishApprovalInfoType,
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR} from "@common/const/const.tsx"; PublishApprovalModalHandle,
import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; PublishApprovalModalProps,
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx"; PublishVersionTableListItem
import { $t } from "@common/locales"; } from '@common/const/approval/type.tsx'
import { ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const"; import { useFetch } from '@common/hooks/http.ts'
import { useGlobalContext } from "@common/contexts/GlobalStateContext"; import {
import { LoadingOutlined } from "@ant-design/icons"; BasicResponse,
import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline"; FORM_ERROR_TIPS,
PLACEHOLDER,
RESPONSE_TIPS,
STATUS_CODE,
STATUS_COLOR
} from '@common/const/const.tsx'
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '@core/const/system/const.tsx'
import { $t } from '@common/locales'
import {
ApprovalPolicyColumns,
ApprovalRouteColumns,
ApprovalStatusColorClass,
ApprovalUpstreamColumns,
ChangeTypeEnum
} from '@common/const/approval/const'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { LoadingOutlined } from '@ant-design/icons'
import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline'
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle, PublishApprovalModalProps>(
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => { (props, ref) => {
const { message } = App.useApp() const { message } = App.useApp()
const { type,data,insidePage = false, serviceType = 'rest', serviceId, teamId} = props const { type, data, insidePage = false, serviceType = 'rest', serviceId, teamId } = props
const [form] = Form.useForm(); const [form] = Form.useForm()
const {fetchData} = useFetch() const { fetchData } = useFetch()
const {state} = useGlobalContext() const { state } = useGlobalContext()
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{ const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
if(type === 'view'){ if (type === 'view') {
return Promise.resolve(true)
}
return form
.validateFields()
.then((value) => {
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
form.setFields([
{
name: 'opinion',
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
}
])
form.scrollToField('opinion')
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
}
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`, {
method: 'PUT',
eoBody: { comments: value.opinion },
eoParams: { id: data!.id, project: serviceId },
eoTransformKeys: ['versionRemark']
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
return Promise.resolve(true) return Promise.resolve(true)
} } else {
return form.validateFields().then((value)=>{ message.error(msg || $t(RESPONSE_TIPS.error))
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){ return Promise.reject(msg || $t(RESPONSE_TIPS.error))
form.setFields([{ }
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)] })
}]) .catch((errorInfo) => Promise.reject(errorInfo))
form.scrollToField('opinion')
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
}
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
return Promise.resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> Promise.reject(errorInfo))
}).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)})
}
const publish:(notSave?:boolean)=>Promise<boolean | string | Record<string, unknown>> = (notSave)=>{
return new Promise((resolve, reject)=>{
form.validateFields().then((value)=>{
const body = {...value, ...(type === 'publish'&&{release:data.id})}
fetchData<BasicResponse<null>>(
notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(response)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
const online:()=>Promise<boolean | string> = ()=>{
return new Promise((resolve, reject)=>{
form.validateFields().then(()=>{
fetchData<BasicResponse<null>>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
useImperativeHandle(ref, ()=>({
save,
publish,
online
}) })
.catch((err) => {
form.scrollToField(err.errorFields[0].name[0])
return Promise.reject(err)
})
}
const publish: (notSave?: boolean) => Promise<boolean | string | Record<string, unknown>> = (notSave) => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then((value) => {
const body = { ...value, ...(type === 'publish' && { release: data.id }) }
fetchData<BasicResponse<null>>(notSave ? 'service/publish/apply' : 'service/publish/release/do', {
method: 'POST',
eoBody: body,
eoParams: { service: serviceId, team: teamId },
eoTransformKeys: ['versionRemark']
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(response)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
})
}
const online: () => Promise<boolean | string> = () => {
return new Promise((resolve, reject) => {
form
.validateFields()
.then(() => {
fetchData<BasicResponse<null>>('service/publish/execute', {
method: 'PUT',
eoParams: { project: serviceId, id: (data as PublishVersionTableListItem).flowId },
eoTransformKeys: ['versionRemark']
})
.then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
})
}
useImperativeHandle(ref, () => ({
save,
publish,
online
}))
useEffect(() => {
form.setFieldsValue({ opinion: '', ...data })
}, [])
const translatedUpstreamColumns = useMemo(
() =>
ApprovalUpstreamColumns.map((x) => ({
...x,
...(x.dataIndex === 'type'
? {
valueEnum: {
static: {
text: $t('静态上游')
}
}
}
: {}),
...(x.dataIndex === 'change'
? {
render: (_, entity) => (
<Tooltip
placement="top"
title={
entity.change === 'error'
? $t('该 API 缺失(0)(1)(2)请先补充', [
entity.proxyStatus == 1 && $t('转发信息,'),
entity.docStatus == 1 && $t('文档信息,'),
entity.upstreamStatus == 1 && $t('上游信息,')
])
: ''
}
>
<span
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
>
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
{entity.change === 'error'
? $t('该 API 缺失(0)(1)(2)请先补充', [
entity.proxyStatus == 1 && $t('转发信息,'),
entity.docStatus == 1 && $t('文档信息,'),
entity.upstreamStatus == 1 && $t('上游信息,')
])
: ''}
</span>
</Tooltip>
)
}
: {}),
title: typeof x.title === 'string' ? $t(x.title) : x.title
})),
[state.language]
) )
useEffect(()=>{ const translatedRouteColumns = useMemo(
form.setFieldsValue({ opinion:'',...data}) () =>
},[]) ApprovalRouteColumns.filter((x) =>
serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods'
const translatedUpstreamColumns = useMemo(()=>ApprovalUpstreamColumns.map((x)=>({ ).map((x) => ({
...x, ...x,
...(x.dataIndex === 'type' ? {valueEnum:{ ...(x.dataIndex === 'change'
'static':{ ? {
text:$t('静态上游') render: (_, entity) => (
} <Tooltip
}}:{}), placement="top"
...(x.dataIndex === 'change' ? { title={
render:(_,entity)=>( entity.change === 'error'
<Tooltip placement="top" title={entity.change === 'error' ? $t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}> ? $t('该 API 缺失(0)(1)(2)请先补充', [
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')} entity.proxyStatus == 1 && $t('转发信息,'),
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}</span> entity.docStatus == 1 && $t('文档信息,'),
</Tooltip>) entity.upstreamStatus == 1 && $t('上游信息,')
}:{}), ])
title: typeof x.title === 'string' ? $t(x.title) : x.title, : ''
})),[state.language])
const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.filter(x=> serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods').map((x)=>({
...x,
...(x.dataIndex === 'change' ? {
render:(_,entity)=>(
<Tooltip placement="top" title={entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}
</span>
</Tooltip>)
}:{}
),
title: typeof x.title === 'string' ? $t(x.title) : x.title,
})),[state.language, serviceType])
const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{
if(x.dataIndex === 'status'){
return {...x,title:$t(x.title),
render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{
switch(entity.status){
case 'done':
return <span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
case 'error':
return <Tooltip title={entity.error || $t('上线失败')}><span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>{$t('失败')} {entity.error}</span></Tooltip>
default:
return <LoadingOutlined className="text-theme" spin />
} }
}} >
} <span
}),[state.language]) className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
>
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
{entity.change === 'error'
? $t('该 API 缺失(0)(1)(2)请先补充', [
entity.proxyStatus == 1 && $t('转发信息,'),
entity.docStatus == 1 && $t('文档信息,'),
entity.upstreamStatus == 1 && $t('上游信息,')
])
: ''}
</span>
</Tooltip>
)
}
: {}),
title: typeof x.title === 'string' ? $t(x.title) : x.title
})),
[state.language, serviceType]
)
const translatedPublishColumns = useMemo(
() =>
SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x) => {
if (x.dataIndex === 'status') {
return {
...x,
title: $t(x.title),
render: (_: unknown, entity: SystemInsidePublishOnlineItems) => {
switch (entity.status) {
case 'done':
return (
<span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
)
case 'error':
return (
<Tooltip title={entity.error || $t('上线失败')}>
<span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>
{$t('失败')} {entity.error}
</span>
</Tooltip>
)
default:
return <LoadingOutlined className="text-theme" spin />
}
}
}
}
}),
[state.language]
)
const translatedPolicyColumns = useMemo(
() =>
ApprovalPolicyColumns.map((x) => {
return {
...x,
title: typeof x.title === 'string' ? $t(x.title) : x.title,
...(x.dataIndex === 'status'
? {
render: (_, entity) => (
<span
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
>
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
</span>
)
}
: {})
}
}),
[state.language]
)
return ( return (
<> <>
{!insidePage && <> {!insidePage && (
<>
<Row className="my-mbase"> <Row className="my-mbase">
<Col className="text-left" span={4}><span >{$t('申请系统')}</span></Col> <Col className="text-left" span={4}>
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col> <span>{$t('申请系统')}</span>
</Col>
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
</Row> </Row>
<Row className="my-mbase"> <Row className="my-mbase">
<Col className="text-left" span={4}><span >{$t('所属团队')}</span></Col> <Col className="text-left" span={4}>
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col> <span>{$t('所属团队')}</span>
</Col>
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
</Row> </Row>
<Row className="my-mbase"> <Row className="my-mbase">
<Col className="text-left" span={4}><span >{$t('申请人')}</span></Col> <Col className="text-left" span={4}>
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col> <span>{$t('申请人')}</span>
</Col>
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
</Row> </Row>
<Row className="my-mbase"> <Row className="my-mbase">
<Col className="text-left" span={4}><span >{$t('申请时间')}</span></Col> <Col className="text-left" span={4}>
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col> <span>{$t('申请时间')}</span>
</Col>
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
</Row> </Row>
</> } </>
<WithPermission access=""><Form )}
className=" mx-auto" <WithPermission access="">
form={form} <Form
labelAlign='left' className=" mx-auto"
layout='vertical' form={form}
scrollToFirstError labelAlign="left"
name="publishApprovalModalContent" layout="vertical"
// labelCol={{span: 3}} scrollToFirstError
// wrapperCol={{span: 21}} name="publishApprovalModalContent"
autoComplete="off" // labelCol={{span: 3}}
disabled={type === 'view'} // wrapperCol={{span: 21}}
> autoComplete="off"
disabled={type === 'view'}
>
{insidePage && (
<>
<Form.Item label={$t('版本号')} name="version" rules={[{ required: true, whitespace: true }]}>
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
{ <Form.Item label={$t('版本说明')} name="versionRemark">
insidePage && <Input.TextArea
<> className="w-INPUT_NORMAL"
<Form.Item disabled={type !== 'add' && type !== 'publish'}
label={$t("版本号")} placeholder={$t(PLACEHOLDER.input)}
name="version" />
rules={[{required: true,whitespace:true }]} </Form.Item>
> </>
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} /> )}
</Form.Item> <Row className="mt-mbase pb-[8px] h-[32px] font-bold">
<span>{$t('路由列表')}</span>
<Form.Item </Row>
label={$t("版本说明")} <Row className="mb-mbase ">
name="versionRemark" <Table
> columns={translatedRouteColumns}
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} /> bordered={true}
</Form.Item> rowKey="id"
</> size="small"
} dataSource={data.diffs?.routers || []}
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('路由列表')}</span></Row> pagination={false}
<Row className="mb-mbase "> />
<Table </Row>
columns={translatedRouteColumns} {serviceType === 'rest' && (
bordered={true} <>
rowKey="id" <Row className="mt-mbase pb-[8px] h-[32px] font-bold">
size="small" <span>{$t('上游列表')}</span>
dataSource={data.diffs?.routers || []} </Row>
pagination={false} <Row className="mb-mbase ">
/></Row> <Table
{ bordered={true}
serviceType === 'rest' && <> columns={translatedUpstreamColumns}
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('上游列表')}</span></Row> size="small"
<Row className="mb-mbase "> rowKey="id"
<Table dataSource={data.diffs?.upstreams || []}
bordered={true} pagination={false}
columns={translatedUpstreamColumns} />
size="small" </Row>
rowKey="id" </>
dataSource={data.diffs?.upstreams || []} )}
pagination={false} <Row className="mt-mbase pb-[8px] h-[32px] font-bold">
/></Row> <span>{$t('策略列表')}</span>
</> </Row>
} <Row className="mb-mbase ">
<Form.Item <Table
bordered={true}
columns={translatedPolicyColumns}
size="small"
rowKey="id"
dataSource={data.diffs?.strategies || []}
pagination={false}
/>
</Row>
{/* <Form.Item
label={$t("备注")} label={$t("备注")}
name="remark" name="remark"
> >
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} /> <Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
</Form.Item> </Form.Item> */}
{/* {/*
{type !== 'add' && type !== 'publish' && <Form.Item {type !== 'add' && type !== 'publish' && <Form.Item
label={$t("审核意见" label={$t("审核意见"
name="opinion" name="opinion"
@@ -238,20 +409,29 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
}, },
]);}}/> ]);}}/>
</Form.Item>} */} </Form.Item>} */}
{['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <> {['error', 'done'].indexOf(data.status) !== -1 &&
<Row className="text-left h-[32px] mb-8px]" span={3}><span>{$t('上线情况')}</span></Row> data.clusterPublishStatus &&
<Row span={24} className="mb-mbase"> data.clusterPublishStatus.length > 0 && (
<Table <>
bordered={true} <Row className="text-left h-[32px] mb-8px]" span={3}>
columns={[...translatedPublishColumns]} <span>{$t('上线情况')}</span>
size="small" </Row>
rowKey="id" <Row span={24} className="mb-mbase">
dataSource={data.clusterPublishStatus || []} <Table
pagination={false} bordered={true}
/> columns={[...translatedPublishColumns]}
</Row></>} size="small"
</Form> rowKey="id"
</WithPermission> dataSource={data.clusterPublishStatus || []}
</>) pagination={false}
}) />
</Row>
</>
)}
</Form>
</WithPermission>
</>
)
}
)
@@ -1,64 +1,64 @@
import { FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react'
import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react';
interface ScrollableSectionProps { interface ScrollableSectionProps {
children: React.ReactNode; children: React.ReactNode
} }
const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => { const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => {
const scrollAreaRef = useRef<HTMLDivElement>(null); const scrollAreaRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
if (scrollAreaRef.current) { if (scrollAreaRef.current) {
const scrollTop = scrollAreaRef.current.scrollTop; const scrollTop = scrollAreaRef.current.scrollTop
const scrollHeight = scrollAreaRef.current.scrollHeight; const scrollHeight = scrollAreaRef.current.scrollHeight
const clientHeight = scrollAreaRef.current.clientHeight; const clientHeight = scrollAreaRef.current.clientHeight
// 如果滚动到顶部,.content-before 应该显示阴影 // 如果滚动到顶部,.content-before 应该显示阴影
const showTopShadow = scrollTop > 0; const showTopShadow = scrollTop > 0
// 如果滚动到底部,.content-after 应该显示阴影 // 如果滚动到底部,.content-after 应该显示阴影
const showBottomShadow = scrollHeight - scrollTop < clientHeight; const showBottomShadow = scrollHeight - scrollTop < clientHeight
// 这里我们不直接更新状态,而是通过ref来设置样式 // 这里我们不直接更新状态,而是通过ref来设置样式
if (showTopShadow && !showBottomShadow) { if (showTopShadow && !showBottomShadow) {
setElementShadow('.content-before', true); setElementShadow('.content-before', true)
setElementShadow('.content-after', false); setElementShadow('.content-after', false)
} else if (!showTopShadow && showBottomShadow) { } else if (!showTopShadow && showBottomShadow) {
setElementShadow('.content-before', false); setElementShadow('.content-before', false)
setElementShadow('.content-after', true); setElementShadow('.content-after', true)
} else { } else {
setElementShadow('.content-before', false); setElementShadow('.content-before', false)
setElementShadow('.content-after', false); setElementShadow('.content-after', false)
} }
} }
}; }
scrollAreaRef.current?.addEventListener('scroll', handleScroll); scrollAreaRef.current?.addEventListener('scroll', handleScroll)
return () => { return () => {
scrollAreaRef.current?.removeEventListener('scroll', handleScroll); scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
}; }
}, []); }, [])
const setElementShadow = (elementSelector: string, showShadow: boolean) => { const setElementShadow = (elementSelector: string, showShadow: boolean) => {
const element = document.querySelector(elementSelector); const element = document.querySelector(elementSelector)
if (element) { if (element) {
element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none'; element.style.boxShadow = showShadow
? elementSelector === '.content-before'
? '0 2px 2px #0000000d'
: '0 -2px 2px -2px var(--border-color)'
: 'none'
} }
} }
const childrenWithRef = Children.toArray(children).map((child) => { const childrenWithRef = Children.toArray(children).map((child) => {
if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) { if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) {
// 将 ref 附加到具有 'scroll-area' 类名的子元素 // 将 ref 附加到具有 'scroll-area' 类名的子元素
return cloneElement(child, { ref: scrollAreaRef }); return cloneElement(child, { ref: scrollAreaRef })
} }
return child; return child
}); })
return ( return <> {childrenWithRef}</>
<> {childrenWithRef} }
</>
);
};
export default ScrollableSection; export default ScrollableSection
@@ -1,115 +1,129 @@
import {App, Col, Form, Input, Row} from "antd"; import { App, Col, Form, Input, Row } from 'antd'
import { forwardRef, useEffect, useImperativeHandle} from "react"; import { forwardRef, useEffect, useImperativeHandle } from 'react'
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx"; import { SubscribeApprovalInfoType } from '@common/const/approval/type.tsx'
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; import { BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import {useFetch} from "@common/hooks/http.ts"; import { useFetch } from '@common/hooks/http.ts'
import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { SubscribeApprovalList } from "@common/const/approval/const"; import { SubscribeApprovalList } from '@common/const/approval/const'
import { $t } from "@common/locales"; import { $t } from '@common/locales'
type SubscribeApprovalModalProps = { type SubscribeApprovalModalProps = {
type:'approval'|'view' type: 'approval' | 'view'
data?:SubscribeApprovalInfoType data?: SubscribeApprovalInfoType
inSystem?:boolean inSystem?: boolean
serviceId:string serviceId: string
teamId:string teamId: string
} }
export type SubscribeApprovalModalHandle = { export type SubscribeApprovalModalHandle = {
save:(operate:'pass'|'refuse') =>Promise<boolean|string> save: (operate: 'pass' | 'refuse') => Promise<boolean | string>
} }
type FieldType = { type FieldType = {
reason?:string; reason?: string
opinion?:string; opinion?: string
}; }
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => { export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle, SubscribeApprovalModalProps>(
(props, ref) => {
const { message } = App.useApp() const { message } = App.useApp()
const {data, type,inSystem=false, teamId, serviceId} = props const { data, type, inSystem = false, teamId, serviceId } = props
const [form] = Form.useForm(); const [form] = Form.useForm()
const {fetchData} = useFetch() const { fetchData } = useFetch()
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{ const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject) => {
if(type === 'view'){ if (type === 'view') {
resolve(true) resolve(true)
return return
} }
form.validateFields().then((value)=>{ form
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){ .validateFields()
form.setFields([{ .then((value) => {
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)] if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
}]) form.setFields([
form.scrollToField('opinion') {
reject($t(RESPONSE_TIPS.refuseOpinion)) name: 'opinion',
return errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
} }
fetchData<BasicResponse<null>>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).then(response=>{ ])
const {code,msg} = response form.scrollToField('opinion')
if(code === STATUS_CODE.SUCCESS){ reject($t(RESPONSE_TIPS.refuseOpinion))
message.success(msg || $t(RESPONSE_TIPS.success)) return
resolve(true) }
}else{ fetchData<BasicResponse<null>>(`${inSystem ? 'service/' : ''}approval/subscribe`, {
message.error(msg || $t(RESPONSE_TIPS.error)) method: 'POST',
reject(msg || $t(RESPONSE_TIPS.error)) eoBody: { opinion: value.opinion, operate },
} eoParams: inSystem ? { apply: data!.id, team: teamId } : { id: data!.id, team: teamId }
}).catch((errorInfo)=> reject(errorInfo)) })
}).catch((errorInfo)=> reject(errorInfo)) .then((response) => {
}) const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
.catch((errorInfo) => reject(errorInfo))
})
.catch((errorInfo) => reject(errorInfo))
})
} }
useImperativeHandle(ref, ()=>({ useImperativeHandle(ref, () => ({
save save
}) }))
)
useEffect(()=>{ useEffect(() => {
form.setFieldsValue({opinion:'',...data}) form.setFieldsValue({ opinion: '', ...data })
},[]) }, [])
return ( return (
<div className="my-btnybase">{ <div className="my-btnybase">
SubscribeApprovalList?.map((x)=>( {SubscribeApprovalList?.map((x) => (
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto"> <Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
<Col className="text-left" span={6}>{$t(x.title)}</Col> <Col className="text-left" span={6}>
<Col >{(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'}</Col> {$t(x.title)}
</Row> </Col>
)) <Col>
} {(data as { [k: string]: unknown })?.[x.key]?.name || (data as { [k: string]: unknown })?.[x.key] || '-'}
</Col>
</Row>
))}
<WithPermission access=""> <WithPermission access="">
<Form <Form
labelAlign='left' labelAlign="left"
layout='vertical' layout="vertical"
form={form} form={form}
className="mx-auto " className="mx-auto "
name="subscribeApprovalModalContent" name="subscribeApprovalModalContent"
// labelCol={{ span: 6}} // labelCol={{ span: 6}}
// wrapperCol={{ span: 18}} // wrapperCol={{ span: 18}}
autoComplete="off" autoComplete="off"
disabled={type === 'view'} disabled={type === 'view'}
> >
<Form.Item<FieldType> label={$t('申请原因')} name="reason">
<Form.Item<FieldType> <Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
label={$t("申请原因")} </Form.Item>
name="reason" <Form.Item<FieldType> label={$t('审核意见')} name="opinion" extra={$t(FORM_ERROR_TIPS.refuseOpinion)}>
> <Input.TextArea
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " /> className="w-INPUT_NORMAL"
</Form.Item> placeholder={$t(PLACEHOLDER.input)}
<Form.Item<FieldType> onChange={() => {
label={$t("审核意见")} form.setFields([
name="opinion" {
extra={$t(FORM_ERROR_TIPS.refuseOpinion)} name: 'opinion',
> errors: [] // 设置为空数组来移除错误信息
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([ }
{ ])
name: 'opinion', }}
errors: [], // 设置为空数组来移除错误信息 />
}, </Form.Item>
])}} /> </Form>
</Form.Item> </WithPermission>
</Form> </div>
</WithPermission>
</div>
) )
}) }
)
@@ -1,71 +1,121 @@
import { PERMISSION_DEFINITION } from '@common/const/permissions'
import { Button, Tooltip } from "antd" import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { useState, useMemo, useEffect, useCallback } from "react" import { $t } from '@common/locales'
import { useGlobalContext } from "@common/contexts/GlobalStateContext" import { Icon } from '@iconify/react/dist/iconify.js'
import { useNavigate } from "react-router-dom" import { Button, Tooltip } from 'antd'
import { PERMISSION_DEFINITION } from "@common/const/permissions" import { useCallback, useEffect, useMemo, useState } from 'react'
import { Icon } from "@iconify/react/dist/iconify.js" import { useNavigate } from 'react-router-dom'
import { $t } from "@common/locales"
type TableBtnWithPermissionProps = { type TableBtnWithPermissionProps = {
btnTitle:string btnTitle: string
access?:keyof typeof PERMISSION_DEFINITION[0], access?: keyof (typeof PERMISSION_DEFINITION)[0]
tooltip?:string, tooltip?: string
disabled?:boolean, disabled?: boolean
navigateTo?:string, navigateTo?: string
onClick?:(args?:unknown)=>void onClick?: (args?: unknown) => void
className?:string className?: string
btnType:string btnType: string
} }
const TableIconName={ const TableIconName = {
'add':'ic:baseline-add', add: 'ic:baseline-add',
'edit':'ic:baseline-edit', edit: 'ic:baseline-edit',
'delete':'ic:baseline-delete', delete: 'ic:baseline-delete',
'remove':'ic:baseline-minus', remove: 'ic:baseline-minus',
'copy':'ic:baseline-file-copy', copy: 'ic:baseline-file-copy',
'view':'ic:baseline-remove-red-eye', view: 'ic:baseline-remove-red-eye',
'publish':'ic:baseline-publish', publish: 'ic:baseline-publish',
'approval':'ic:baseline-approval', offline: 'ic:baseline-file-download-off',
'stop':'ic:baseline-stop-circle', approval: 'ic:baseline-approval',
'online':'ic:baseline-check-circle', stop: 'ic:baseline-stop-circle',
'cancel':'ic:baseline-cancel-schedule-send', online: 'ic:baseline-check-circle',
'refresh':'ic:baseline-refresh' cancel: 'ic:baseline-cancel-schedule-send',
refresh: 'ic:baseline-refresh',
logs: 'hugeicons:google-doc',
disable: 'ic:baseline-pause-circle',
enable: 'ic:baseline-play-circle'
} }
// 表格操作栏按钮,受权限控制 // 表格操作栏按钮,受权限控制
const TableBtnWithPermission = ({btnTitle, access, tooltip, disabled, navigateTo, onClick,className,btnType}:TableBtnWithPermissionProps) => { const TableBtnWithPermission = ({
btnTitle,
const [btnAccess, setBtnAccess] = useState<boolean>(false) access,
const {accessData,checkPermission,accessInit} = useGlobalContext() tooltip,
const navigate = useNavigate() disabled,
const lastAccess = useMemo(()=>{ navigateTo,
if(!accessInit) return false onClick,
if(!access) return true className,
return checkPermission(access) btnType
},[access, accessData,checkPermission,accessInit]) }: TableBtnWithPermissionProps) => {
const [btnAccess, setBtnAccess] = useState<boolean>(false)
const [btnStatus, setBtnStatus] = useState<boolean>(false)
const [closeToolTip, setCloseToolTip] = useState<boolean>(false)
const { accessData, checkPermission, accessInit } = useGlobalContext()
const navigate = useNavigate()
const lastAccess = useMemo(() => {
if (!accessInit) return false
if (!access) return true
return checkPermission(access)
}, [access, accessData, checkPermission, accessInit])
useEffect(()=>{ useEffect(() => {
access ? setBtnAccess(lastAccess) : setBtnAccess(true) access ? setBtnAccess(lastAccess) : setBtnAccess(true)
},[access, lastAccess]) }, [access, lastAccess])
const handleClick = useCallback(
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => { (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation() e.stopPropagation()
navigateTo ? navigate(navigateTo) : onClick?.() setTimeout(() => {
}, [navigateTo, navigate, onClick]) setBtnStatus(false)
setCloseToolTip(true)
return (<>{ })
!btnAccess || (disabled&&tooltip) ?
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。',[$t(btnTitle).toLowerCase()])}>
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18"/>} >{}</Button>
</Tooltip>
:
<Tooltip placement="top" title={$t(btnTitle)}>
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18"/>} onClick={handleClick}>{}</Button>
</Tooltip>
}</> navigateTo ? navigate(navigateTo) : onClick?.()
); },
[navigateTo, navigate, onClick]
)
const changeTooltipStatus = (open: boolean) => {
setBtnStatus(open)
if (closeToolTip) {
setBtnStatus(false)
setCloseToolTip(false)
} }
}
export default TableBtnWithPermission return (
<>
{!btnAccess || (disabled && tooltip) ? (
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
<Button
type="text"
disabled={true}
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
key={btnType}
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
>
{}
</Button>
</Tooltip>
) : (
<Tooltip
placement="top"
title={$t(btnTitle)}
trigger="hover"
open={btnStatus}
onOpenChange={changeTooltipStatus}
>
<Button
type="text"
disabled={disabled}
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
key={btnType}
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
onClick={handleClick}
>
{}
</Button>
</Tooltip>
)}
</>
)
}
export default TableBtnWithPermission
@@ -1,37 +1,38 @@
import { Tag, TagProps } from 'antd'
import { useState, useMemo, useEffect } from 'react'
import { PERMISSION_DEFINITION } from '@common/const/permissions'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { Tag, TagProps } from "antd"; export interface TagWithPermission extends TagProps {
import { useState, useMemo, useEffect } from "react"; access?: string
import { PERMISSION_DEFINITION } from "@common/const/permissions";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
export interface TagWithPermission extends TagProps{
access?:string
} }
export default function TagWithPermission(props:TagWithPermission){ export default function TagWithPermission(props: TagWithPermission) {
const {access,onClose} = props const { access, onClose } = props
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true) const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
const {accessData,checkPermission,accessInit} = useGlobalContext() const { accessData, checkPermission, accessInit } = useGlobalContext()
const lastAccess = useMemo(()=>{ const lastAccess = useMemo(() => {
if(!access) return true if (!access) return true
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]) return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
},[access, accessData,checkPermission,accessInit]) }, [access, accessData, checkPermission, accessInit])
useEffect(()=>{ useEffect(() => {
access ? setEditAccess(lastAccess) : setEditAccess(true) access ? setEditAccess(lastAccess) : setEditAccess(true)
},[lastAccess]) }, [lastAccess])
const handleTagClose = (e: React.MouseEvent<HTMLElement>)=>{
e.preventDefault();
if(!editAccess) return
onClose?.(e)
}
return <Tag const handleTagClose = (e: React.MouseEvent<HTMLElement>) => {
closeIcon e.preventDefault()
{...props} if (!editAccess) return
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`} onClose?.(e)
onClose={handleTagClose}> }
{props.children}
</Tag>
} return (
<Tag
closeIcon
{...props}
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
onClose={handleTagClose}
>
{props.children}
</Tag>
)
}
@@ -1,34 +1,33 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState } from 'react';
const ThemeSwitcher = () => { const ThemeSwitcher = () => {
const [darkMode, setDarkMode] = useState(true); const [darkMode, setDarkMode] = useState(true)
useEffect(() => { useEffect(() => {
let isDarkMode = localStorage.getItem('dark-mode'); const isDarkMode = localStorage.getItem('dark-mode')
if(isDarkMode !== undefined && isDarkMode !== null){ if (isDarkMode !== undefined && isDarkMode !== null) {
setDarkMode(isDarkMode === 'true') setDarkMode(isDarkMode === 'true')
}else{ } else {
localStorage.setItem('dark-mode', (darkMode).toString()); localStorage.setItem('dark-mode', darkMode.toString())
} }
}, []); }, [])
useEffect(()=>{ useEffect(() => {
document.documentElement.classList.toggle('dark', darkMode); document.documentElement.classList.toggle('dark', darkMode)
},[darkMode]) }, [darkMode])
const toggleDarkMode = () => { const toggleDarkMode = () => {
setDarkMode(!darkMode); setDarkMode(!darkMode)
localStorage.setItem('dark-mode', (!darkMode).toString()); localStorage.setItem('dark-mode', (!darkMode).toString())
document.documentElement.classList.toggle('dark', !darkMode); document.documentElement.classList.toggle('dark', !darkMode)
}; }
return ( return (
// <button onClick={toggleDarkMode}> // <button onClick={toggleDarkMode}>
// {darkMode ? '切换到白天模式' : '切换到黑夜模式'} // {darkMode ? '切换到白天模式' : '切换到黑夜模式'}
// </button> // </button>
<></> <></>
); )
}; }
export default ThemeSwitcher; export default ThemeSwitcher
@@ -1,121 +1,148 @@
import { useEffect, useState } from 'react'
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'
import dayjs, { Dayjs } from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import '../../index.css'
import { $t } from '@common/locales'
import { useState } from 'react'; type RangePickerProps = GetProps<typeof DatePicker.RangePicker>
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'; export type RangeValue = [Dayjs | null, Dayjs | null] | null
import dayjs, { Dayjs } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import "../../index.css"
import { $t } from '@common/locales';
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>; dayjs.extend(customParseFormat)
export type RangeValue = [Dayjs | null, Dayjs | null] | null;
dayjs.extend(customParseFormat);
export type TimeRange = { export type TimeRange = {
start:number|null start: number | null
end:number|null end: number | null
} }
export type TimeRangeButton = ''| 'hour' | 'day' | 'threeDays' | 'sevenDays'; export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
type TimeRangeSelectorProps = { type TimeRangeSelectorProps = {
initialTimeButton?:TimeRangeButton, initialTimeButton?: TimeRangeButton
initialDatePickerValue?:RangeValue initialDatePickerValue?: RangeValue
onTimeRangeChange?:(timeRange:TimeRange) =>void onTimeRangeChange?: (timeRange: TimeRange) => void
hideTitle?:boolean hideTitle?: boolean
onTimeButtonChange:(time:TimeRangeButton) =>void onTimeButtonChange: (time: TimeRangeButton) => void
labelSize?:'small'|'default' labelSize?: 'small' | 'default'
bindRef?: any
hideBtns?: TimeRangeButton[]
defaultTimeButton?: TimeRangeButton
}
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
const {
initialTimeButton,
initialDatePickerValue,
onTimeRangeChange,
hideTitle,
onTimeButtonChange,
labelSize = 'default',
bindRef,
hideBtns = [],
defaultTimeButton = 'hour'
} = props
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
useEffect(() => {
if (bindRef) {
bindRef({ reset })
} }
const TimeRangeSelector = (props:TimeRangeSelectorProps) => { }, [bindRef])
const {initialTimeButton,initialDatePickerValue,onTimeRangeChange,hideTitle,onTimeButtonChange,labelSize='default'} = props
const [timeButton, setTimeButton] = useState(initialTimeButton || '');
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null,null]);
// 根据选择的时间范围计算开始和结束时间 // 根据选择的时间范围计算开始和结束时间
const calculateTimeRange = (curBtn:'hour'|'day'|'threeDays'|'sevenDays') => { const calculateTimeRange = (curBtn: TimeRangeButton) => {
const currentSecond = new Date().getTime() // 当前毫秒数时间戳 const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳
const currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳 let startMin = currentSecond - 60 * 60
let startMin = currentMin - 60 * 60 * 1000
switch (curBtn) { switch (curBtn) {
case 'hour': { case 'hour': {
startMin = currentMin - 60 * 60 * 1000 startMin = currentSecond - 60 * 60
break break
} }
case 'day': { case 'day': {
startMin = currentMin - 24 * 60 * 60 * 1000 startMin = currentSecond - 24 * 60 * 60
break break
} }
case 'threeDays': { case 'threeDays': {
startMin = startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 2 * 24 * 60 * 60
new Date(new Date().setHours(0, 0, 0, 0)).getTime() - break
2 * 24 * 60 * 60 * 1000 }
break case 'sevenDays': {
} startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 6 * 24 * 60 * 60
case 'sevenDays': { break
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
6 * 24 * 60 * 60 * 1000
break
}
} }
if (onTimeRangeChange) {
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 });
} }
}; if (onTimeRangeChange) {
onTimeRangeChange({ start: startMin, end: currentSecond })
}
}
// 处理单选按钮的变化 // 处理单选按钮的变化
const handleRadioChange = (e:RadioChangeEvent) => { const handleRadioChange = (e: RadioChangeEvent) => {
setTimeButton(e.target.value); setTimeButton(e.target.value)
onTimeButtonChange?.(e.target.value) onTimeButtonChange?.(e.target.value)
setDatePickerValue(null) setDatePickerValue(null)
calculateTimeRange(e.target.value); calculateTimeRange(e.target.value)
}; }
const reset = () => {
setTimeButton(defaultTimeButton)
calculateTimeRange(defaultTimeButton)
setDatePickerValue(null)
}
// 处理日期选择器的变化 // 处理日期选择器的变化
const handleDatePickerChange = (dates: RangeValue) => { const handleDatePickerChange = (dates: RangeValue) => {
setTimeButton(dates ? '' : 'hour') setTimeButton(dates ? '' : defaultTimeButton)
onTimeButtonChange?.(dates ? '' : 'hour') onTimeButtonChange?.(dates ? '' : defaultTimeButton)
setDatePickerValue(dates); setDatePickerValue(dates)
if (dates && Array.isArray(dates) && dates.length === 2) { if (dates && Array.isArray(dates) && dates.length === 2) {
const [startDate, endDate] = dates; const [startDate, endDate] = dates
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00 const start = startDate!.startOf('day').unix() // 开始日期的00:00:00
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59 const end = endDate!.endOf('day').unix() // 结束日期的23:59:59
if (onTimeRangeChange) { if (onTimeRangeChange) {
onTimeRangeChange({ start, end }); onTimeRangeChange({ start, end })
} }
} }
}; if (!dates) {
calculateTimeRange(defaultTimeButton)
}
}
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
// Can not select days before today and today
const disabledDate: RangePickerProps['disabledDate'] = (current) => { return current && current.valueOf() > dayjs().startOf('day').valueOf()
// Can not select days before today and today }
return current && current.valueOf() > dayjs().startOf('day').valueOf();
};
return ( return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase"> <div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>} {!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid"> <Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button> {hideBtns?.length && hideBtns.includes('hour') ? null : (
<Radio.Button value="day">{$t('近24小时')}</Radio.Button> <Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button> )}
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button> {hideBtns?.length && hideBtns.includes('day') ? null : (
</Radio.Group> <Radio.Button value="day">{$t('近24小时')}</Radio.Button>
)}
{hideBtns?.length && hideBtns.includes('threeDays') ? null : (
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
)}
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : (
<Radio.Button className="rounded-e-none" value="sevenDays">
{$t('近7天')}
</Radio.Button>
)}
</Radio.Group>
<DatePicker.RangePicker <DatePicker.RangePicker
value={datePickerValue} value={datePickerValue}
className="rounded-s-none ml-[-1px]" className="rounded-s-none ml-[-1px]"
disabledDate={disabledDate} disabledDate={disabledDate}
onChange={handleDatePickerChange} onChange={handleDatePickerChange}
onOpenChange={(open)=>{ onOpenChange={(open) => {
if(!open && datePickerValue && datePickerValue.length > 2){ if (!open && datePickerValue && datePickerValue.length > 2) {
setTimeButton('') setTimeButton('')
onTimeButtonChange?.('') onTimeButtonChange?.('')
} }
}} }}
/> />
</div> </div>
); )
}; }
export default TimeRangeSelector; export default TimeRangeSelector
@@ -1,45 +1,94 @@
import { CheckOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons'
import {CheckOutlined, LoadingOutlined, MoreOutlined} from "@ant-design/icons"; import { Dropdown, Input, InputRef, MenuProps } from 'antd'
import {Dropdown, Input, InputRef, MenuProps} from "antd"; import { ReactNode, useEffect, useRef, useState } from 'react'
import { ReactNode, useEffect, useRef, useState} from "react";
export type TreeWithMoreProp = { export type TreeWithMoreProp = {
children:ReactNode, children: ReactNode
dropdownMenu:MenuProps['items'] dropdownMenu: MenuProps['items']
editable?:boolean editable?: boolean
editingId?:string editingId?: string
afterEdit?:(val:string)=>Promise<string|boolean> afterEdit?: (val: string) => Promise<string | boolean>
editKey?:string editKey?: string
entity?:{id:string,[k:string]:unknown | string} entity?: { id: string; [k: string]: unknown | string }
onBlur?:()=>void onBlur?: () => void
stopClick?:boolean stopClick?: boolean
} }
const TreeWithMore = ({children,dropdownMenu,editable,editingId,entity,editKey='name',afterEdit,onBlur,stopClick=true}:TreeWithMoreProp)=>{ const TreeWithMore = ({
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string) children,
const [submitting, setSubmitting] = useState<boolean>(false) dropdownMenu,
const inputRef = useRef<InputRef>(null) editable,
editingId,
entity,
editKey = 'name',
afterEdit,
onBlur,
stopClick = true
}: TreeWithMoreProp) => {
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
const [submitting, setSubmitting] = useState<boolean>(false)
const inputRef = useRef<InputRef>(null)
const handleSubmit = (val:string)=>{ const handleSubmit = (val: string) => {
if(submitting) return if (submitting) return
setSubmitting(true) setSubmitting(true)
afterEdit && afterEdit(val).finally(()=>setSubmitting(false)) afterEdit && afterEdit(val).finally(() => setSubmitting(false))
} }
useEffect(()=>{inputRef.current?.focus()},[inputRef]) useEffect(() => {
inputRef.current?.focus()
}, [inputRef])
return (<> return (
{ <>
editable && editingId && entity?.id && editingId === entity.id ? <Input ref={inputRef} value={editValue} onChange={(e)=>{setEditValue(e.target.value)}} onBlur={()=>{onBlur?.()}} onClick={(e)=>stopClick&&e?.stopPropagation()} onPressEnter={()=>{handleSubmit(editValue)}} suffix={submitting ? <LoadingOutlined />:<CheckOutlined onClick={()=>{handleSubmit(editValue)}}/>} />: {editable && editingId && entity?.id && editingId === entity.id ? (
<Dropdown menu={{items:dropdownMenu}} trigger={['contextMenu']} > <Input
<div className='tree-title-hover' >{children} ref={inputRef}
<span onClick={(e)=>{ stopClick && e.stopPropagation();}}> value={editValue}
<Dropdown menu={{items:dropdownMenu}} trigger={['click']} > onChange={(e) => {
<MoreOutlined className="tree-title-more" onClick={(e)=>{ stopClick && e.stopPropagation(); }} /> setEditValue(e.target.value)
</Dropdown> }}
</span> onBlur={() => {
</div> onBlur?.()
}}
onClick={(e) => stopClick && e?.stopPropagation()}
onPressEnter={() => {
handleSubmit(editValue)
}}
suffix={
submitting ? (
<LoadingOutlined />
) : (
<CheckOutlined
onClick={() => {
handleSubmit(editValue)
}}
/>
)
}
/>
) : (
<Dropdown menu={{ items: dropdownMenu }} trigger={['contextMenu']}>
<div className="tree-title-hover">
{children}
<span
onClick={(e) => {
stopClick && e.stopPropagation()
}}
>
<Dropdown menu={{ items: dropdownMenu }} trigger={['click']}>
<MoreOutlined
className="tree-title-more"
onClick={(e) => {
stopClick && e.stopPropagation()
}}
/>
</Dropdown>
</span>
</div>
</Dropdown> </Dropdown>
}</>) )}
</>
)
} }
export default TreeWithMore export default TreeWithMore
@@ -1,143 +1,190 @@
import { $t } from "@common/locales" import { $t } from '@common/locales'
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/ /* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/
export const TranslateWord = ()=>{ export const TranslateWord = () => {
return ( return (
<> <>
{$t('文件日志')} {$t('文件日志')}
{$t('HTTP日志')} {$t('HTTP日志')}
{$t('Kafka日志')} {$t('Kafka日志')}
{$t('NSQ日志')} {$t('NSQ日志')}
{$t('Syslog日志')} {$t('Syslog日志')}
{$t('未分配')} {$t('未分配')}
{$t('超级管理员')} {$t('超级管理员')}
{$t('团队管理员')} {$t('团队管理员')}
{$t('运维管理员')} {$t('运维管理员')}
{$t('普通成员')} {$t('普通成员')}
{$t('只读成员')} {$t('只读成员')}
{$t('服务管理员')} {$t('服务管理员')}
{$t('服务开发者')} {$t('服务开发者')}
{$t('消费者开发者')} {$t('消费者开发者')}
{$t('消费者管理员')} {$t('消费者管理员')}
{$t('驱动名称')} {$t('驱动名称')}
{$t('请求失败数')} {$t('请求失败数')}
{$t('转发失败数')} {$t('转发失败数')}
{$t('作用范围')} {$t('作用范围')}
{$t('添加条目')} {$t('添加条目')}
{$t('添加地址')} {$t('添加地址')}
{$t('文件名称')} {$t('文件名称')}
{$t('存放目录')} {$t('存放目录')}
{$t('日志分割周期')} {$t('日志分割周期')}
{$t('过期时间')} {$t('过期时间')}
{$t('单位:天')} {$t('单位:天')}
{$t('输出格式')} {$t('输出格式')}
{$t('格式化配置')} {$t('格式化配置')}
{$t('服务器地址')} {$t('服务器地址')}
{$t('Access日志')} {$t('Access日志')}
{$t('NSQD地址列表')} {$t('NSQD地址列表')}
{$t('鉴权Secret')} {$t('鉴权Secret')}
{$t('网络协议')} {$t('网络协议')}
{$t('日志等级')} {$t('日志等级')}
{$t('单行')} {$t('单行')}
{$t('小时')} {$t('小时')}
{$t('天')} {$t('天')}
{$t('未发布')} {$t('未发布')}
{$t('待发布')} {$t('待发布')}
{$t('单位:s,最小值:1')} {$t('单位:s,最小值:1')}
{$t('上传文件')} {$t('上传文件')}
{$t('替换文件')} {$t('替换文件')}
{$t('是否放行')} {$t('是否放行')}
{$t('监控')} {$t('监控')}
{$t('必填')} {$t('必填')}
{$t('字符非法,仅支持英文')} {$t('字符非法,仅支持英文')}
{$t('上传 OpenAPI 文档 (.json/.yaml)')} {$t('上传 OpenAPI 文档 (.json/.yaml)')}
{$t('替换 OpenAPI 文档 (.json/.yaml)')} {$t('替换 OpenAPI 文档 (.json/.yaml)')}
{$t('打开 OpenAPI YAML 编辑器')} {$t('打开 OpenAPI YAML 编辑器')}
{$t('无需审核:允许任何消费者调用该服务')} {$t('无需审核:允许任何消费者调用该服务')}
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')} {$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
{$t('永久')} {$t('永久')}
{$t('否')} {$t('否')}
{$t('是')} {$t('是')}
{$t('无需审核')} {$t('无需审核')}
{$t('需要审核')} {$t('需要审核')}
{$t('创建时间')} {$t('创建时间')}
{$t('协议')} {$t('协议')}
{$t('方法')} {$t('方法')}
{$t('地址(IP 端口或域名)')} {$t('地址(IP 端口或域名)')}
{$t('权重(0-999')} {$t('权重(0-999')}
{$t('带权轮询')} {$t('带权轮询')}
{$t('发布版本')} {$t('发布版本')}
{$t('发布申请记录')} {$t('发布申请记录')}
{$t('创建版本时间')} {$t('创建版本时间')}
{$t('版本状态')} {$t('版本状态')}
{$t('创建人')} {$t('创建人')}
{$t('审核时间')} {$t('审核时间')}
{$t('申请方-消费者')} {$t('申请方-消费者')}
{$t('审核状态')} {$t('审核状态')}
{$t('申请人')} {$t('申请人')}
{$t('审核人')} {$t('审核人')}
{$t('审核时间')} {$t('审核时间')}
{$t('来源')} {$t('来源')}
{$t('订阅时间')} {$t('订阅时间')}
{$t('请输入')} {$t('请输入')}
{$t('请选择')} {$t('请选择')}
{$t('创建者')} {$t('创建者')}
{$t('服务数量')} {$t('服务数量')}
{$t('负责人')} {$t('负责人')}
{$t('姓名')} {$t('姓名')}
{$t('团队角色')} {$t('团队角色')}
{$t('添加日期')} {$t('添加日期')}
{$t('请求成功数')} {$t('请求成功数')}
{$t('转发成功数')} {$t('转发成功数')}
{$t('API 名称')} {$t('API 名称')}
{$t('失败状态码数')} {$t('失败状态码数')}
{$t('所属服务')} {$t('所属服务')}
{$t('平均响应时间(ms)')} {$t('平均响应时间(ms)')}
{$t('最大响应时间(ms)')} {$t('最大响应时间(ms)')}
{$t('最小响应时间(ms)')} {$t('最小响应时间(ms)')}
{$t('平均请求流量(KB)')} {$t('平均请求流量(KB)')}
{$t('最大请求流量(KB)')} {$t('最大请求流量(KB)')}
{$t('最小请求流量(KB)')} {$t('最小请求流量(KB)')}
{$t('所有成员')} {$t('所有成员')}
{$t('状态')} {$t('状态')}
{$t('角色名称')} {$t('角色名称')}
{$t('绑定域名')} {$t('绑定域名')}
{$t('过期日期')} {$t('过期日期')}
{$t('支持字母开头、英文数字中横线下划线组合')} {$t('支持字母开头、英文数字中横线下划线组合')}
{$t('英文数字下划线任意一种,首字母必须为英文')} {$t('英文数字下划线任意一种,首字母必须为英文')}
{$t('字符非法,仅支持英文')} {$t('字符非法,仅支持英文')}
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')} {$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
{$t('选择拒绝时,审核意见为必填')} {$t('选择拒绝时,审核意见为必填')}
{$t('操作成功')} {$t('操作成功')}
{$t('操作失败')} {$t('操作失败')}
{$t('正在操作')} {$t('正在操作')}
{$t('正在加载数据')} {$t('正在加载数据')}
{$t('获取数据失败')} {$t('获取数据失败')}
{$t('登录成功')} {$t('登录成功')}
{$t('退出成功,将跳转至登录页')} {$t('退出成功,将跳转至登录页')}
{$t('未填写审核意见')} {$t('未填写审核意见')}
{$t('复制成功')} {$t('复制成功')}
{$t('复制失败,请手动复制')} {$t('复制失败,请手动复制')}
{$t('服务所属团队')} {$t('服务所属团队')}
{$t('在线')} {$t('在线')}
{$t('已拒绝')} {$t('已拒绝')}
{$t('中止')} {$t('中止')}
{$t('发布异常')} {$t('发布异常')}
{$t('发布中')} {$t('发布中')}
{$t('申请方所属团队')} {$t('申请方所属团队')}
{$t('发布状态')} {$t('发布状态')}
{$t(' 次')} {$t(' 次')}
{$t('每分钟')} {$t('每分钟')}
{$t('每5分钟')} {$t('每5分钟')}
{$t('每小时')} {$t('每小时')}
{$t('每天')} {$t('每天')}
{$t('每周')} {$t('每周')}
{$t('上线结果')} {$t('上线结果')}
{$t('订阅服务数量')} {$t('订阅服务数量')}
{$t('鉴权数量')} {$t('鉴权数量')}
{$t('列表')} {$t('列表')}
{$t('块')} {$t('块')}
{$t('HTTP 请求头')}
</> {$t('全等匹配')}
) {$t('前缀匹配')}
} {$t('后缀匹配')}
{$t('子串匹配')}
{$t('非等匹配')}
{$t('空值匹配')}
{$t('存在匹配')}
{$t('不存在匹配')}
{$t('区分大小写的正则匹配')}
{$t('不区分大小写的正则匹配')}
{$t('任意匹配')}
{$t('驳回')}
{$t('已订阅')}
{$t('取消申请')}
{$t('透传客户端请求 Host')}
{$t('使用上游服务 Host')}
{$t('重写 Host')}
{$t('动态服务发现')}
{$t('地址')}
{$t('新增')}
{$t('申请方消费者')}
{$t('策略名称')}
{$t('优先级')}
{$t('筛选条件')}
{$t('处理数')}
{$t('数据格式')}
{$t('关键字')}
{$t('正则表达式')}
{$t('手机号')}
{$t('身份证号')}
{$t('银行卡号')}
{$t('金额')}
{$t('日期')}
{$t('局部显示')}
{$t('局部遮蔽')}
{$t('截取')}
{$t('替换')}
{$t('乱序')}
{$t('随机字符串')}
{$t('自定义字符串')}
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
{$t('待更新')}
{$t('待删除')}
{$t('内容')}
{$t('调用地址')}
{$t('消费者 IP')}
{$t('鉴权名称')}
</>
)
}

Some files were not shown because too many files have changed in this diff Show More