Compare commits

...

109 Commits

Author SHA1 Message Date
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
Liujian 0a1b08157d add default router when create new rest service 2024-12-16 14:31:52 +08:00
ningyv d36c66371f Merge pull request #147 from APIParkLab/fix/fixbug-cx
fix: consumer permission
2024-12-13 20:02:45 +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
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
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
刘健 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
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
杨梦洁 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
刘健 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
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
刘健 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
刘健 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
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
杨梦洁 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
杨梦洁 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
杨梦洁 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
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
150 changed files with 5565 additions and 958 deletions
+6 -6
View File
@@ -27,7 +27,7 @@ type imlAPIController struct {
}
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 {
return nil, err
}
@@ -52,7 +52,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
"provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"config": input.AiModel.Config,
},
}
@@ -73,7 +73,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
Retry: input.Retry,
Plugins: plugins,
},
Upstream: info.Provider.Id,
Upstream: input.AiModel.Provider,
Disable: false,
})
@@ -86,7 +86,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) {
info, err := i.serviceModule.Get(ctx, serviceId)
_, err := i.serviceModule.Get(ctx, serviceId)
if err != nil {
return nil, err
}
@@ -106,11 +106,11 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
"provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"config": input.AiModel.Config,
},
}
upstream = &info.Provider.Id
upstream = &input.AiModel.Provider
}
if input.AiPrompt != nil {
+2 -2
View File
@@ -45,8 +45,8 @@ func (p *imlCluster) Check(ctx *gin.Context, input *cluster_dto.CheckCluster) ([
// return id, nil
//}
//
//func (p *imlCluster) Search(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
// return p.module.Search(ctx, keyword)
//func (p *imlCluster) SearchByDriver(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
// return p.module.SearchByDriver(ctx, keyword)
//}
//
//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:""`
}
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) {
trend, timeInterval, err := i.module.MessageTrend(ctx, input)
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)
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 {
+6 -1
View File
@@ -1,12 +1,13 @@
package router
import (
"io"
api_doc "github.com/APIParkLab/APIPark/module/api-doc"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/APIParkLab/APIPark/module/router"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
"github.com/gin-gonic/gin"
"io"
)
var _ IRouterController = (*imlAPIController)(nil)
@@ -15,6 +16,10 @@ type imlAPIController struct {
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) {
return i.module.Detail(ctx, serviceId, apiId)
}
+3 -1
View File
@@ -1,9 +1,10 @@
package router
import (
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"reflect"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
@@ -24,6 +25,7 @@ type IRouterController interface {
Delete(ctx *gin.Context, serviceId string, apiId string) error
// Prefix 获取API前缀
Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
Simple(ctx *gin.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error)
}
type IAPIDocController interface {
+147 -28
View File
@@ -7,6 +7,16 @@ import (
"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"
@@ -25,7 +35,6 @@ import (
"github.com/APIParkLab/APIPark/module/service"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/APIParkLab/APIPark/module/upstream"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/eolinker/go-common/store"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@@ -42,29 +51,102 @@ type imlServiceController struct {
docModule service.IServiceDocModule `autowired:""`
aiAPIModule ai_api.IAPIModule `autowired:""`
routerModule router.IRouterModule `autowired:""`
apiDocModule api_doc.IAPIDocModule `autowired:""`
providerModule ai.IProviderModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""`
settingModule system.ISettingModule `autowired:""`
transaction store.ITransaction `autowired:""`
}
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,
},
},
var (
loader = openapi3.NewLoader()
)
func (i *imlServiceController) swagger(ctx *gin.Context, id string) (*openapi3.T, error) {
doc, err := i.apiDocModule.GetDoc(ctx, id)
if err != nil {
return nil, err
}
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) {
@@ -228,21 +310,12 @@ func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string,
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) {
now := time.Now()
defer func() {
log.Infof("get service %s cost %d ms", id, time.Since(now).Milliseconds())
}()
return i.module.Get(ctx, id)
}
func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) {
@@ -253,7 +326,33 @@ func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *se
if input.Kind == "ai" {
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) {
@@ -336,3 +435,23 @@ func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto.
func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error {
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,
},
},
}
}
+4 -5
View File
@@ -24,12 +24,11 @@ type IServiceController interface {
Delete(ctx *gin.Context, id string) error
ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, 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)
//editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error)
//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)
Swagger(ctx *gin.Context)
ExportSwagger(ctx *gin.Context)
}
type IAppController interface {
+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{})
})
}
+22 -1
View File
@@ -265,7 +265,8 @@ func (i *imlInitController) OnInit() {
return fmt.Errorf("create default team error: %v", err)
}
// 创建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",
Prefix: "/rest-demo",
Description: "Auto created By APIPark",
@@ -277,6 +278,26 @@ func (i *imlInitController) OnInit() {
if err != nil {
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服务
err = i.createAIService(ctx, info.Id, &service_dto.CreateService{
Name: "AI Demo Service",
@@ -146,8 +146,11 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
...x,
title: typeof x.title === 'string' ? $t(x.title) : x.title,
...(x.dataIndex === 'status' ? {
render:(_,entity)=>(
entity.status === 0 ? $t('正常') : $t('无效'))
render:(_,entity)=> (
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
</span>
)
}:{})
}
}),[state.language])
@@ -37,6 +37,8 @@ const TableIconName = {
const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: 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(() => {
@@ -52,16 +54,27 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
setTimeout(() => {
setBtnStatus(false)
setCloseToolTip(true)
})
navigateTo ? navigate(navigateTo) : onClick?.()
}, [navigateTo, navigate, onClick])
const changeTooltipStatus = (open: boolean) => {
setBtnStatus(open)
if (closeToolTip) {
setBtnStatus(false)
setCloseToolTip(false)
}
}
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)}>
<Tooltip placement="top" title={$t(btnTitle)} trigger='hover' open={btnStatus} onOpenChange={changeTooltipStatus}>
<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>
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
import dayjs, { Dayjs } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
@@ -12,69 +12,80 @@ export type RangeValue = [Dayjs | null, Dayjs | null] | null;
dayjs.extend(customParseFormat);
export type TimeRange = {
start:number|null
end:number|null
start: number | null
end: number | null
}
export type TimeRangeButton = ''| 'hour' | 'day' | 'threeDays' | 'sevenDays';
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays';
type TimeRangeSelectorProps = {
initialTimeButton?:TimeRangeButton,
initialDatePickerValue?:RangeValue
onTimeRangeChange?:(timeRange:TimeRange) =>void
hideTitle?:boolean
onTimeButtonChange:(time:TimeRangeButton) =>void
labelSize?:'small'|'default'
}
const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
const {initialTimeButton,initialDatePickerValue,onTimeRangeChange,hideTitle,onTimeButtonChange,labelSize='default'} = props
initialTimeButton?: TimeRangeButton,
initialDatePickerValue?: RangeValue
onTimeRangeChange?: (timeRange: TimeRange) => void
hideTitle?: boolean
onTimeButtonChange: (time: TimeRangeButton) => void
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]);
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null]);
useEffect(() => {
if (bindRef) {
bindRef({ reset });
}
}, [bindRef])
// 根据选择的时间范围计算开始和结束时间
const calculateTimeRange = (curBtn:'hour'|'day'|'threeDays'|'sevenDays') => {
const currentSecond = new Date().getTime() // 当前毫秒数时间戳
const currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳
let startMin = currentMin - 60 * 60 * 1000
const calculateTimeRange = (curBtn: TimeRangeButton) => {
const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
let startMin = currentSecond - 60 * 60
switch (curBtn) {
case 'hour': {
startMin = currentMin - 60 * 60 * 1000
break
}
case 'day': {
startMin = currentMin - 24 * 60 * 60 * 1000
break
}
case 'threeDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
2 * 24 * 60 * 60 * 1000
break
}
case 'sevenDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
6 * 24 * 60 * 60 * 1000
break
}
case 'hour': {
startMin = currentSecond - 60 * 60
break
}
case 'day': {
startMin = currentSecond - 24 * 60 * 60
break
}
case 'threeDays': {
startMin =
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
2 * 24 * 60 * 60
break
}
case 'sevenDays': {
startMin =
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
6 * 24 * 60 * 60
break
}
}
if (onTimeRangeChange) {
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 });
onTimeRangeChange({ start: startMin, end: currentSecond });
}
};
// 处理单选按钮的变化
const handleRadioChange = (e:RadioChangeEvent) => {
const handleRadioChange = (e: RadioChangeEvent) => {
setTimeButton(e.target.value);
onTimeButtonChange?.(e.target.value)
setDatePickerValue(null)
calculateTimeRange(e.target.value);
};
const reset = () => {
setTimeButton(defaultTimeButton)
calculateTimeRange(defaultTimeButton)
setDatePickerValue(null)
}
// 处理日期选择器的变化
const handleDatePickerChange = (dates: RangeValue) => {
setTimeButton(dates ? '' : 'hour')
onTimeButtonChange?.(dates ? '' : 'hour')
setTimeButton(dates ? '' : defaultTimeButton)
onTimeButtonChange?.(dates ? '' : defaultTimeButton)
setDatePickerValue(dates);
if (dates && Array.isArray(dates) && dates.length === 2) {
const [startDate, endDate] = dates;
@@ -84,34 +95,37 @@ const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
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) => {
// Can not select days before today and today
return current && current.valueOf() > dayjs().startOf('day').valueOf();
};
};
return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>
</Radio.Group>
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
{hideBtns?.length && hideBtns.includes('hour') ? null : <Radio.Button value="hour">{$t('近1小时')}</Radio.Button>}
{hideBtns?.length && hideBtns.includes('day') ? null : <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
value={datePickerValue}
className="rounded-s-none ml-[-1px]"
className="rounded-s-none ml-[-1px]"
disabledDate={disabledDate}
onChange={handleDatePickerChange}
onOpenChange={(open)=>{
if(!open && datePickerValue && datePickerValue.length > 2){
setTimeButton('')
onTimeButtonChange?.('')
}
onOpenChange={(open) => {
if (!open && datePickerValue && datePickerValue.length > 2) {
setTimeButton('')
onTimeButtonChange?.('')
}
}}
/>
</div>
@@ -181,6 +181,10 @@ export const TranslateWord = ()=>{
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
{$t('待更新')}
{$t('待删除')}
{$t('内容')}
{$t('调用地址')}
{$t('消费者 IP')}
{$t('鉴权名称')}
</>
)
}
@@ -13,6 +13,7 @@ export interface CodeboxApiRef {
formatCode: () => void
}
export type codeBoxLanguagesType = 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
interface CodeboxProps {
options?: MonacoEditor.IStandaloneEditorConstructionOptions
value?: string
@@ -22,7 +23,7 @@ interface CodeboxProps {
height?: string | null
readOnly?: boolean
apiRef?: RefObject<CodeboxApiRef>
language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
language?: codeBoxLanguagesType
extraContent?:React.ReactNode
sx?:Record<string,unknown>
editorTheme?:'vs' | 'vs-dark' | 'hc-black'
@@ -295,7 +295,7 @@ export interface IconParkIconElement extends HTMLElement {
| 'apispace'
| 'auto-generate-api'
| 'compare-api'
| 'multi-protocal'
| 'multi-protocol'
| 'read-good'
| 'richdoc'
| 'mockapi'
@@ -486,17 +486,17 @@ export const PERMISSION_DEFINITION = [
},
"team.application.subscription.add": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.subscription.subscribe"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.subscribe"] }]
}
},
"team.application.subscription.edit": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.subscription.manager_subscribed_services"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.manager_subscribed_services"] }]
}
},
"team.application.subscription.delete": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.team.consumer.subscription.manager_subscribed_services"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.team.consumer.subscription.manager_subscribed_services"] }]
}
},
"team.application.application.view": {
@@ -506,47 +506,47 @@ export const PERMISSION_DEFINITION = [
},
"team.application.application.add": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
}
},
"team.application.application.edit": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
}
},
"team.application.application.delete": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
}
},
"team.consumer.authorization.view": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","system.workspace.application.view_all","team.consumer.authorization.view"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","system.workspace.application.view_all","team.consumer.authorization.view"] }]
}
},
"team.application.authorization.add": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
}
},
"team.application.authorization.edit": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
}
},
"team.application.authorization.delete": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
}
},
"team.application.authorization.cancelSubApply": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
}
},
"team.application.authorization.cancelSub": {
"granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }]
"anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
}
},
"team.team.team.view": {
@@ -1,3 +1,4 @@
import { codeBoxLanguagesType } from "@common/components/postcat/api/Codebox";
export const MatchRules = [
{ value: 'inner', label: '数据格式' },
@@ -54,4 +55,34 @@ export const StrategyStatusEnum = {
"offline":'text-status_fail',
"delete":'text-status_offline',
}
export const contentTypeToLanguageMap: Record<string, codeBoxLanguagesType> = {
// JSON
"application/json": "json",
// XML
"application/xml": "xml",
"text/xml": "xml",
// HTML
"text/html": "html",
// Plain text
"text/plain": "plaintext",
// JavaScript
"application/javascript": "javascript",
"text/javascript": "javascript",
// CSS
"text/css": "css",
// YAML
"application/x-yaml": "yaml",
"text/yaml": "yaml",
// Others (fallback)
"*/*": "plaintext", // 任意类型默认处理为普通文本
};
@@ -45,13 +45,13 @@ export type GlobalAction =
{
"name": "工作空间",
"key": "workspace",
"path": "/guide/page",
"path": "/guide",
"icon": "ic:baseline-space-dashboard",
"children": [
{
"name": "首页",
"key": "guide",
"path": "/guide/page",
"path": "/guide",
"icon": "ic:baseline-home",
"access": "all"
},
@@ -179,7 +179,7 @@ export type GlobalAction =
"name": "全局策略",
"key": "globalpolicy",
"path": "/globalpolicy",
"icon": "uil:comment-shield",
"icon": "icon-park-solid:exchange-three",
"access": "system.settings.data_source.view"
},
{
@@ -1,6 +1,7 @@
import { RESPONSE_TIPS } from '@common/const/const';
import { message } from 'antd';
import { $t } from "@common/locales"
import { useEffect, useState } from 'react';
const useCopyToClipboard = () => {
@@ -749,5 +749,19 @@
"K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms",
"K1da86266": "Invalid",
"K3a34d49b": "Pending Update",
"Kd2850420": "Pending Deletion"
"Kd2850420": "Pending Deletion",
"K9ada4366": "Operation successful, the page will refresh shortly",
"K9b332ab1": "Request prefix",
"K3d78d483": "HTTP headers",
"K17dc3a62": "Data logs",
"Ke429194e": "Processing logs",
"K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search",
"Kb147fabc": "Create",
"K40ca4f2": "Update",
"K3e7aa0ad": "Content",
"K2f5fdf5e": "Call Address",
"K1bc5e0a3": "Consumer IP",
"K6f39ea21": "Authentication Name",
"K8c34c02f": "Before Masking",
"K8e3d388d": "After Masking"
}
@@ -771,6 +771,19 @@
"K67f4e9bb": "外部プラットフォームと統合する際に、API市場のドキュメント情報を取得するためのドメイン名",
"K1da86266": "無効",
"K3a34d49b": "更新待ち",
"Kd2850420": "削除待ち"
"Kd2850420": "削除待ち",
"K9ada4366": "操作成功、ページを更新します",
"K9b332ab1": "リクエストプレフィックス",
"K3d78d483": "HTTPヘッダー",
"K17dc3a62": "データログ",
"Ke429194e": "ログの処理",
"K84ffb1dd": "呼び出しアドレス、コンシューマーIP、条件を入力して検索",
"Kb147fabc": "新規作成",
"K40ca4f2": "更新",
"K3e7aa0ad": "内容",
"K2f5fdf5e": "呼び出しアドレス",
"K1bc5e0a3": "コンシューマー IP",
"K6f39ea21": "認証名",
"K8c34c02f": "マスキング前",
"K8e3d388d": "マスキング後"
}
@@ -702,5 +702,19 @@
"K31faa2a1": "优先级",
"Kbdec9fa": "筛选条件",
"Kbcbb7391": "处理数",
"Kad207008": "编辑"
"Kad207008": "编辑",
"K9ada4366": "操作成功,即将刷新页面",
"K9b332ab1": "请求前缀",
"K3d78d483": "HTTP 头部",
"K17dc3a62": "数据日志",
"Ke429194e": "处理日志",
"K84ffb1dd": "输入调用地址、消费者IP和消费者条件查找",
"Kb147fabc": "新建",
"K40ca4f2": "更新",
"K3e7aa0ad": "内容",
"K2f5fdf5e": "调用地址",
"K1bc5e0a3": "消费者 IP",
"K6f39ea21": "鉴权名称",
"K8c34c02f": "脱敏前",
"K8e3d388d": "脱敏后"
}
@@ -771,5 +771,19 @@
"K67f4e9bb": "與外部平台集成時,用於獲取 API 市場文檔信息的域名",
"K1da86266": "無效",
"K3a34d49b": "待更新",
"Kd2850420": "待刪除"
"Kd2850420": "待刪除",
"K9ada4366": "操作成功,即將刷新頁面",
"K9b332ab1": "請求前綴",
"K3d78d483": "HTTP 標頭",
"K17dc3a62": "數據日誌",
"Ke429194e": "處理日誌",
"K84ffb1dd": "輸入調用地址、消費者 IP 和消費者條件進行查找",
"Kb147fabc": "新建",
"K40ca4f2": "更新",
"K3e7aa0ad": "內容",
"K2f5fdf5e": "調用地址",
"K1bc5e0a3": "消費者 IP",
"K6f39ea21": "鑑權名稱",
"K8c34c02f": "脫敏前",
"K8e3d388d": "脫敏後"
}
@@ -1,115 +1,157 @@
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { COLUMNS_TITLE } from "@common/const/const";
import { EntityItem } from "@common/const/type";
export type PartitionConfigFieldType = {
name?: string;
id?: string;
description?: string;
prefix?:string
url?:string
managerAddress?:string
canDelete?:boolean
name?: string;
id?: string;
description?: string;
prefix?: string
url?: string
managerAddress?: string
canDelete?: boolean
};
export type PartitionCertTableListItem = {
id:string;
name: string;
domains:string[];
notAfter:string;
notBefore:string;
updater:EntityItem;
updateTime:string;
id: string;
name: string;
domains: string[];
notAfter: string;
notBefore: string;
updater: EntityItem;
updateTime: string;
};
export type PartitionCertConfigFieldType = {
id?:string
key:string
pem:string
id?: string
key: string
pem: string
};
export type PartitionCertConfigProps = {
type:'add'|'edit'
entity?:PartitionCertConfigFieldType
type: 'add' | 'edit'
entity?: PartitionCertConfigFieldType
}
export type PartitionCertConfigHandle = {
save:()=>Promise<boolean|string>
save: () => Promise<boolean | string>
}
export type PartitionClusterFieldType = {
name?: string;
id?: string;
description?: string;
address?:string;
protocol?:'http'|'https'
name?: string;
id?: string;
description?: string;
address?: string;
protocol?: 'http' | 'https'
};
export type ClusterConfigProps = {
mode:'config' | 'retry' | 'result' | 'edit',
clusterId?:string
initFormValue?:{[k:string]:string|number}
mode: 'config' | 'retry' | 'result' | 'edit',
clusterId?: string
initFormValue?: { [k: string]: string | number }
}
export type ClusterConfigHandle = {
save:()=>Promise<boolean|string>
check:()=>Promise<boolean>
save: () => Promise<boolean | string>
check: () => Promise<boolean>
}
export type PartitionClusterTableListItem = {
id:string;
name: string;
status:0|1;
description:string;
id: string;
name: string;
status: 0 | 1;
description: string;
};
export type PartitionClusterNodeTableListItem = {
id:string;
name: string;
managerAddress:string[];
serviceAddress:string[];
peerAddress:string[];
status:0|1;
id: string;
name: string;
managerAddress: string[];
serviceAddress: string[];
peerAddress: string[];
status: 0 | 1;
};
export type PartitionClusterNodeModalTableListItem = {
id: string,
name: string,
managerAddress: [],
serviceAddress: [],
peerAddress: string,
status: string
id: string,
name: string,
managerAddress: [],
serviceAddress: [],
peerAddress: string,
status: string
}
export type NodeModalFieldType = {
address:string
address: string
}
export type NodeModalHandle = {
save:()=>void
save: () => void
}
export type NodeModalPropsType = {
changeStatus:(status:ClusterPageShowStatus)=>void
getClusterInfo:()=>void
status:ClusterPageShowStatus
changeStatus: (status: ClusterPageShowStatus) => void
getClusterInfo: () => void
status: ClusterPageShowStatus
}
export type ClusterPageShowStatus = 'view'|'preview'|'edit'
export type ClusterPageShowStatus = 'view' | 'preview' | 'edit'
export type PartitionTableListItem = {
id:string;
name: string;
clusterNum:number;
updater:EntityItem;
updateTime:string;
id: string;
name: string;
clusterNum: number;
updater: EntityItem;
updateTime: string;
};
export type SimplePartition = EntityItem & { clusters: (EntityItem & {description:string})[] }
export type SimplePartition = EntityItem & { clusters: (EntityItem & { description: string })[] }
export type PartitionDashboardConfigFieldType = {
driver:string
config:{
org:string
token:string
addr:string
driver: string
config: {
org: string
token: string
addr: string
}
}
export type PartitionDataLogHeaderListFieldType = {
key: string
value: string
}
export type PartitionDataLogConfigFieldType = {
headers: PartitionDataLogHeaderListFieldType[]
url: string
}
export const PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS: PageProColumns<PartitionDataLogConfigFieldType & { _id: string }>[] = [
{
title: ('标签'),
dataIndex: 'key',
formItemProps: {
className: 'p-0 bg-transparent border-none',
rootClassName: 'test',
rules: [
{
required: true,
whitespace: true
},
],
},
ellipsis: true
},
{
title: ('内容'),
dataIndex: 'value',
formItemProps: {
className: 'p-0 bg-transparent border-none'
}
}
},
{
title: COLUMNS_TITLE.operate,
valueType: 'option',
btnNums: 2,
render: () => null
},
];
@@ -239,7 +239,7 @@ export const AiServiceSubscriberConfig = forwardRef<AiServiceSubscriberConfigHan
getAiServiceList()
}, [serviceId]);
return (<WithPermission access="team.service.subscription.add">
return (<WithPermission access="">
<Form
layout='vertical'
labelAlign='left'
@@ -106,9 +106,9 @@ const AiSettingList = ()=>{
</a>
<div>
<CancelBtn/>
<WithPermission access="system.devops.ai_provider.edit" showDisabled={false}>
<OkBtn/>
</WithPermission>
{
checkAccess('system.devops.ai_provider.edit', accessData) ? <OkBtn/> : null
}
</div>
</div>
);
@@ -3,7 +3,7 @@ import { useEffect } from "react";
import { PartitionDashboardConfigFieldType } from "../../const/partitions/types";
import { DASHBOARD_SETTING_DRIVER_OPTION_LIST } from "../../const/partitions/const";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { BasicResponse, PLACEHOLDER, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const";
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const";
import { useFetch } from "@common/hooks/http";
import { $t } from "@common/locales";
@@ -29,10 +29,10 @@ export type DashboardSettingEditProps = {
fetchData<BasicResponse<{info: PartitionDashboardConfigFieldType}>>('monitor/config',{method: 'POST',body:JSON.stringify(value),eoParams:{}}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功,即将刷新页面')
message.success(msg || $t('操作成功,即将刷新页面'))
refreshData?.()
}else{
message.error(msg || '操作失败')
message.error(msg || $t('操作失败'))
}
})
})
@@ -0,0 +1,90 @@
import EditableTable from "@common/components/aoplatform/EditableTable"
import WithPermission from "@common/components/aoplatform/WithPermission"
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const"
import { useFetch } from "@common/hooks/http"
import { $t } from "@common/locales"
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDataLogConfigFieldType, PartitionDataLogHeaderListFieldType } from "@core/const/partitions/types"
import { Button, Form, Input, message } from "antd"
import { useEffect } from "react"
export type DashboardPageShowStatus = 'view' | 'edit'
export type DashboardSettingEditProps = {
changeStatus: (status: DashboardPageShowStatus) => void
refreshData: () => void
data?: PartitionDataLogConfigFieldType
}
const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
const { changeStatus, refreshData, data } = props
const [form] = Form.useForm();
const { fetchData } = useFetch()
const onFinish = () => {
form.validateFields().then((value) => {
const formData = {
config: {
url: value.url,
headers: value.headers.filter((item: PartitionDataLogHeaderListFieldType) => item.key).map((item: PartitionDataLogHeaderListFieldType) => ({key:item.key, value:item.value || ''}))
}
}
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', { method: 'POST', body: JSON.stringify(formData), eoParams: {} }).then(response => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t('操作成功,即将刷新页面'))
refreshData?.()
} else {
message.error(msg || $t('操作失败'))
}
})
})
}
useEffect(() => { form.setFieldsValue(data) }, [data])
useEffect(() => {
return (form.setFieldsValue({}))
}, []);
return (
<>
<div className="overflow-auto h-full">
<WithPermission access={''} >
<Form
form={form}
className="mx-auto flex flex-col justify-between h-full"
layout="vertical"
onFinish={onFinish}
autoComplete="off"
>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("请求前缀")}
name="url"
rules={[{ required: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("HTTP 头部")}
name="headers"
>
<EditableTable<PartitionDataLogConfigFieldType & { _id: string }>
configFields={PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS}
/>
</Form.Item>
<div className="flex gap-btnbase">
<WithPermission access='system.devops.data_source.edit'>
<Button type="primary" htmlType="submit">
{$t('保存')}
</Button>
</WithPermission>
<Button type="default" onClick={() => changeStatus('view')}>
{$t('取消')}
</Button>
</div>
</Form>
</WithPermission>
</div>
</>
);
}
export default DataLogSettingEdit;
@@ -57,7 +57,7 @@ const CertConfigModal = forwardRef<PartitionCertConfigHandle,PartitionCertConfig
}
}, []);
return (<WithPermission access={type === 'edit' ? 'system.devops.ssl_certificate.edit':'system.devops.ssl_certificate.add'}>
return (<WithPermission access=''>
<Form
layout='vertical'
labelAlign='left'
@@ -187,7 +187,7 @@ const PartitionInsideCert:FC = ()=>{
switch (type){
case 'add':
title=$t('添加证书')
content= <CertConfigModal ref={addRef} type="add"/>
content= <WithPermission access='system.devops.ssl_certificate.add'><CertConfigModal ref={addRef} type="add"/></WithPermission>
break;
case 'edit':{
title=$t('修改证书')
@@ -195,7 +195,7 @@ const PartitionInsideCert:FC = ()=>{
const {code,data,msg} = await fetchData<BasicResponse<{cert:{key:string, pem:string}}>>('certificate',{method:'GET',eoParams:{id:entity!.id}})
message.destroy()
if(code === STATUS_CODE.SUCCESS){
content= <CertConfigModal ref={editRef} type="edit" entity={{...data.cert,id:entity!.id}}/>
content= <WithPermission access={'system.devops.ssl_certificate.edit'}><CertConfigModal ref={editRef} type="edit" entity={{...data.cert,id:entity!.id}}/></WithPermission>
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
return
@@ -1,93 +1,175 @@
import { FC, useEffect, useState} from "react";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {App, Button, Card, Col, Row, Spin, Tag} from "antd";
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { FC, useEffect, useState } from "react";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import { App, Button, Card, Col, Row, Spin, Tag } from "antd";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
import { useFetch } from "@common/hooks/http.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { LoadingOutlined } from "@ant-design/icons";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import { $t } from "@common/locales/index.ts";
import DashboardSettingEdit, { DashboardPageShowStatus } from "./DashboardSettingEdit.tsx";
import { PartitionDashboardConfigFieldType } from "@core/const/partitions/types.ts";
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDashboardConfigFieldType, PartitionDataLogConfigFieldType } from "@core/const/partitions/types.ts";
import PageList from "@common/components/aoplatform/PageList.tsx";
import DataLogSettingEdit from "./DataLogSettingEdit.tsx";
const PartitionInsideDashboardSetting:FC = ()=> {
const {setBreadcrumb} = useBreadcrumb()
const {message} = App.useApp()
const {fetchData} = useFetch()
const [data, setData] = useState<PartitionDashboardConfigFieldType>()
const [loading, setLoading] = useState<boolean>(false)
const [showStatus, setShowStatus] = useState<DashboardPageShowStatus>('view')
const PartitionInsideDashboardSetting: FC = () => {
const { setBreadcrumb } = useBreadcrumb()
const { message } = App.useApp()
const { fetchData } = useFetch()
const [data, setData] = useState<PartitionDashboardConfigFieldType>()
const [dataLogData, setDataLogData] = useState<PartitionDataLogConfigFieldType>()
const [loading, setLoading] = useState<boolean>(false)
const [dataLogLoading, setDataLogLoading] = useState<boolean>(false)
const [showGraphStatus, setShowGraphStatus] = useState<DashboardPageShowStatus>('view')
const [showDataLogStatus, setShowDataLogStatus] = useState<DashboardPageShowStatus>('view')
const getDashboardSettingInfo = () => {
setLoading(true)
return fetchData<BasicResponse<{ nodes:PartitionDashboardConfigFieldType[] }>>('monitor/config', {method: 'GET',eoTransformKeys:[]}).then(response => {
const {code, data, msg} = response
if (code === STATUS_CODE.SUCCESS) {
data?.info?.driver && setData(data.info)
setShowStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return {data: [], success: false}
}).finally(()=>{
setLoading(false)
const getDashboardSettingInfo = () => {
setLoading(true)
return fetchData<BasicResponse<{ nodes: PartitionDashboardConfigFieldType[] }>>('monitor/config', { method: 'GET', eoTransformKeys: [] }).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
data?.info?.driver && setData(data.info)
setShowGraphStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
setLoading(false)
})
}
const getDataLogSettingInfo = () => {
setDataLogLoading(true)
return fetchData<BasicResponse<{ nodes: PartitionDataLogConfigFieldType[] }>>('log/loki', { method: 'GET', eoTransformKeys: [] }).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
data?.info && setDataLogData({
url: data.info?.config?.url || '',
headers: data.info?.config?.headers || []
})
}
setShowDataLogStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
setDataLogLoading(false)
})
}
useEffect(() => {
setBreadcrumb([
{ title: $t('数据源') }
])
getDashboardSettingInfo()
getDataLogSettingInfo()
}, []);
useEffect(() => {
setBreadcrumb([
{title: $t('数据源')}
])
getDashboardSettingInfo()
}, []);
const setDashboardSettingBtn = ()=>{
return (<>
{showStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => setShowStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission> }</>
)
}
return (
<>
<InsidePage
pageTitle={$t('数据源')}
description={$t("设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。")}
showBorder={false}
scrollPage={true}
>
<div className="flex flex-col h-full overflow-auto pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X">
<Spin wrapperClassName=" h-full flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={loading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!data || !data?.driver) && showStatus === 'view' ? 'hidden': ''}`,
}}
className="overflow-hidden w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('统计图表')}</span>
{!loading && !data?.driver && <Tag color='#f50'>{ $t('未配置')}
</Tag>}</div>}
extra={setDashboardSettingBtn()}>
{showStatus === 'view'&& data && data.driver && DashboardConfigPreview(data) }
{showStatus !== 'view' && <DashboardSettingEdit data={data} changeStatus={setShowStatus} refreshData={getDashboardSettingInfo} />}
</Card>
</div>
</Spin>
</div>
</InsidePage>
</>
const setDashboardSettingBtn = () => {
return (<>
{showGraphStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => setShowGraphStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission>}</>
)
}
const setDataLogSettingBtn = () => {
return (<>
{showDataLogStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => setShowDataLogStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission>}</>
)
}
return (
<>
<InsidePage
pageTitle={$t('数据源')}
description={$t("设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。")}
showBorder={false}
scrollPage={false}
>
<div className="flex flex-col overflow-auto pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X">
<Spin wrapperClassName="flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!data || !data?.driver) && showGraphStatus === 'view' ? 'hidden' : ''}`,
}}
className="overflow-hidden w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('统计图表')}</span>
{!loading && !data?.driver && <Tag color='#f50'>{$t('未配置')}
</Tag>}</div>}
extra={setDashboardSettingBtn()}>
{showGraphStatus === 'view' && data && data.driver && DashboardConfigPreview(data)}
{showGraphStatus !== 'view' && <DashboardSettingEdit data={data} changeStatus={setShowGraphStatus} refreshData={getDashboardSettingInfo} />}
</Card>
</div>
</Spin>
<Spin wrapperClassName="flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={dataLogLoading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!dataLogData) && showDataLogStatus === 'view' ? 'hidden' : ''}`,
}}
className="overflow-hidden mt-[30px] w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('数据日志')}</span>
{!dataLogLoading && !dataLogData && <Tag color='#f50'>{$t('未配置')}
</Tag>}</div>}
extra={setDataLogSettingBtn()}>
{showDataLogStatus === 'view' && dataLogData && DataLogConfigPreview(dataLogData)}
{showDataLogStatus !== 'view' && <DataLogSettingEdit data={dataLogData} changeStatus={setShowDataLogStatus} refreshData={getDataLogSettingInfo} />}
</Card>
</div>
</Spin>
</div>
</InsidePage>
</>
)
}
export function DashboardConfigPreview (x:PartitionDashboardConfigFieldType){
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('数据源')}</Col><Col>{x?.driver}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('地址(IP:端口)')}</Col><Col>{x?.config?.addr}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('组织(Organization')}</Col><Col>{x?.config?.org}</Col></Row>
</div>}
export function DashboardConfigPreview(x: PartitionDashboardConfigFieldType) {
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('数据源')}</Col><Col>{x?.driver}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('地址(IP:端口)')}</Col><Col>{x?.config?.addr}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('组织(Organization')}</Col><Col>{x?.config?.org}</Col></Row>
</div>
}
export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
const columns = PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS.map(x => {
return {
...x,
title: (<span title={$t(x.title as string)}>{$t(x.title as string)}</span>)
}
})
const getTableList = () => {
return new Promise((resolve, reject) => {
resolve({
data: x?.headers || [],
success: true
})
})
}
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}</Col><Col>{x?.url}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('HTTP 头部')}</Col><Col className="w-full">
<div className="w-full h-full p-[20px]">
<PageList
id="global_role"
tableClass="role_table mb-btnrbase"
primaryKey="'key'"
columns={[...columns]}
request={() => getTableList()}
showPagination={false}
noScroll={true}
/>
</div>
</Col></Row>
</div>
}
export default PartitionInsideDashboardSetting
@@ -66,7 +66,7 @@ const DataMasking = (props: any) => {
}
return {
...x,
title: <span title={$t(x.title as string)}>{$t(x.title as string)}</span>
title: (<span title={$t(x.title as string)}>{$t(x.title as string)}</span>)
}
})
return res
@@ -79,7 +79,7 @@ const DataMasking = (props: any) => {
{
title: '',
key: 'option',
btnNums: rowOperation.length -1,
btnNums: rowOperation.length,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
@@ -233,7 +233,6 @@ const DataMasking = (props: any) => {
* @param entity
*/
const openLogsModal = (entity: any) => {
console.log('日志', entity);
setStrategy(entity.id)
setModalVisible(true)
}
@@ -379,10 +378,11 @@ const DataMasking = (props: any) => {
<Modal
title={$t('处理日志')}
visible={modalVisible}
destroyOnClose={true}
onCancel={handleCloseModal}
footer={null}
wrapClassName="modal-without-footer"
width={1000}
width={1100}
maskClosable={true}
>
<div className="pb-btnybase flex flex-nowrap flex-col h-full w-full items-center justify-between">
@@ -15,7 +15,7 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
{
title: ('优先级'),
dataIndex: 'priority',
width: 140,
width: 90,
ellipsis: true,
sorter: (a: any, b: any) => {
return (a.priority as number) - (b.priority as number)
@@ -26,7 +26,8 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
dataIndex: 'publishStatus',
filters: true,
onFilter: false ,
width: 140,
ellipsis: true,
width: 110,
valueEnum: new Map(
Object.keys(StrategyStatusEnum).map(key=>
[key,
@@ -38,6 +39,8 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
dataIndex: 'isStop',
filters: true,
onFilter: false ,
ellipsis: true,
width: 90,
valueEnum: {
false: { text: <span className="text-status_success">{$t('启用')}</span> },
true: { text: <span className="text-status_fail">{$t('禁用')}</span> }
@@ -51,7 +54,8 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
{
title: ('处理数'),
dataIndex: 'processedTotal',
ellipsis: true
ellipsis: true,
width: 80,
},
{
title: ('更新者'),
@@ -81,10 +85,10 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
width: 200
},
{
title: ('消费者IP'),
title: ('消费者 IP'),
dataIndex: 'remote_ip',
ellipsis: true,
width: 150
width: 100
},
{
title: ('消费者'),
@@ -94,14 +98,14 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
},
{
title: ('鉴权名称'),
dataIndex: 'authorization',
dataIndex: ['authorization', 'name'],
ellipsis: true,
width: 100
},
{
title: ('时间'),
dataIndex: 'record_time',
width: 150,
width: 110,
ellipsis: true
},
]
@@ -1,13 +1,12 @@
import { Codebox } from "@common/components/postcat/api/Codebox";
import { Codebox, codeBoxLanguagesType } from "@common/components/postcat/api/Codebox";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { RouterParams } from "@common/const/type";
import { $t } from "@common/locales";
import { App, Button, message, Switch, Modal, Spin } from 'antd'
import { message, Spin } from 'antd'
import { useFetch } from "@common/hooks/http";
import { useEffect, useState } from "react";
import { LoadingOutlined } from "@ant-design/icons";
import { useParams } from "react-router-dom";
import { contentTypeToLanguageMap } from "@common/const/policy/consts";
type LogItems = {
id: string;
origin: string;
@@ -18,7 +17,15 @@ const DataMaskingCompare = () => {
const { fetchData } = useFetch()
const [loading, setLoading] = useState(false)
const [originValue, setOriginValue] = useState('')
const [targetValue, settTargetValue] = useState('')
const [targetValue, setTargetValue] = useState('')
const [language, setLanguage] = useState<codeBoxLanguagesType>('json')
const getMonacoEditorLanguage = (contentType: string): codeBoxLanguagesType => {
// 提取主类型,忽略参数(如 "; charset=utf-8"
const mainType = contentType.split(";")[0].trim().toLowerCase();
// 根据映射表获取语言,默认返回 "plaintext"
return contentTypeToLanguageMap[mainType] || "json";
};
const getLogData = () => {
setLoading(true)
return fetchData<BasicResponse<{ log: LogItems }>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/log`,
@@ -33,8 +40,10 @@ const DataMaskingCompare = () => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const { log } = data
setOriginValue(log.origin || '')
settTargetValue(log.target || '')
const docLanguage = getMonacoEditorLanguage(log.content_type)
setLanguage(docLanguage)
setOriginValue(docLanguage === 'json' ? JSON.stringify(JSON.parse(log.origin || ''), null, 2) : log.origin || '')
setTargetValue(docLanguage === 'json' ? JSON.stringify(JSON.parse(log.target || ''), null, 2) : log.target || '')
setLoading(false)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -42,17 +51,6 @@ const DataMaskingCompare = () => {
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
const aa = `{
"code": {
"gg": "gg",
"gg1": "gg",
"gg2": "gg",
"gg3": "gg",
"gg4": "gg"
}
}`
setOriginValue(JSON.stringify(JSON.parse(aa), null, 2))
settTargetValue(JSON.stringify(JSON.parse(aa), null, 2))
setLoading(false)
})
}
@@ -64,24 +62,26 @@ const DataMaskingCompare = () => {
<div className="flex h-full overflow-hidden">
<div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-gray-200 mb-2 flex items-center justify-center">
{$t('脱敏前')}
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
language={language}
height="100%"
width="100%"
value={originValue}
sx={{ whiteSpace: 'nowrap' }}
readOnly
/>
</div>
</div>
<div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-green-100 mb-2 flex items-center justify-center">
{$t('脱敏后')}
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
language={language}
width="100%"
height="100%"
value={targetValue}
@@ -1,46 +1,62 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { DataMaskLogItem } from "@common/const/policy/type";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { $t } from "@common/locales";
import { App, Button, message, DatePicker, Modal } from 'antd'
import { Button, message } from 'antd'
import { DATA_MASKING_TABLE_LOG_COLUMNS, DATA_MASKING_TABLE_COLUMNS } from './DataMaskingColumn';
import { DATA_MASKING_TABLE_LOG_COLUMNS } from './DataMaskingColumn';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { ActionType } from '@ant-design/pro-components';
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const';
import { useParams } from 'react-router-dom';
import { RouterParams } from '@common/const/type';
import { useFetch } from '@common/hooks/http';
import WithPermission from '@common/components/aoplatform/WithPermission';
import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector';
import { SearchBody } from '@dashboard/const/type';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
const { RangePicker } = DatePicker;
const DataMaskingLogModal = (props: any) => {
const { strategy } = props;
const { state, accessData } = useGlobalContext()
const { serviceId, teamId } = useParams<RouterParams>()
const [datePickerValue, setDatePickerValue] = useState<any>();
const [queryData, setQueryData] = useState<SearchBody>({})
const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
const [queryData, setQueryData] = useState<SearchBody>({
start: currentSecond - 24 * 60 * 60,
end: currentSecond
})
/**
*
*/
const { fetchData } = useFetch()
/**
* ref
*/
* ref
*/
const pageListRef = useRef<ActionType>(null);
/**
*
*/
const [searchWord, setSearchWord] = useState<string>('')
/**
*
*/
let resetTimeRange = () => {}
/**
*
*/
const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>('day');
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
};
/**
*
*/
const operation: PageProColumns<any>[] = [
{
title: '操作',
title: '',
key: 'option',
btnNums: 1,
fixed: 'right',
@@ -54,7 +70,7 @@ const DataMaskingLogModal = (props: any) => {
url += `/${teamId}`
}
return [
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="view" btnType="view" onClick={() => { window.open(url,'_blank') }} btnTitle="查看" />
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="view" btnType="view" onClick={() => { window.open(url, '_blank') }} btnTitle="查看" />
]
}
}
@@ -68,11 +84,11 @@ const DataMaskingLogModal = (props: any) => {
const columns = useMemo(() => {
const res = DATA_MASKING_TABLE_LOG_COLUMNS.map(x => {
if (x.dataIndex === 'url') {
x.render = (text: any, record: any) => <><span className='text-green-500'>{record.method}</span>&nbsp;<span>{text}</span></>
x.render = (text: any, record: any) => <><div className='w-full'><span className='text-green-500'>{record.method}</span>&nbsp;<span className='w-[calc(100%-25px)] text-ellipsis overflow-hidden whitespace-nowrap inline-block align-top'>{text}</span></div></>
}
return {
...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
title: (<span title={$t(x.title as string)}>{$t(x.title as string)}</span>)
}
})
return res
@@ -88,7 +104,7 @@ const DataMaskingLogModal = (props: any) => {
current: number;
}) => {
return fetchData<BasicResponse<{ logs: DataMaskLogItem[], total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/logs`,
{
method: 'GET',
eoParams: {
@@ -106,204 +122,9 @@ const DataMaskingLogModal = (props: any) => {
).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const mockData: any = [
{
id: '12334',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff1',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff2',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff3',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff4',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff5',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff6',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff7',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff8',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff9',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff11',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff22',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:11',
}
]
// 保存数据
return {
data: mockData,
data: data.logs || [],
total: data.total,
success: true
}
@@ -320,24 +141,13 @@ const DataMaskingLogModal = (props: any) => {
const handleTimeRangeChange = (timeRange: TimeRange) => {
setQueryData(pre => ({ ...pre, ...timeRange } as SearchBody))
manualReloadTable()
};
const handleDatePickerChange = (dates: any) => {
if (dates && Array.isArray(dates) && dates.length === 2) {
const [startDate, endDate] = dates;
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59
handleTimeRangeChange({ start, end });
} else {
handleTimeRangeChange({ start: null, end: null})
}
}
const resetQuery = () => {
setDatePickerValue(null)
handleTimeRangeChange({ start: null, end: null})
resetTimeRange()
};
return (
<>
<div className="w-full h-full p-[20px]">
@@ -348,11 +158,17 @@ const DataMaskingLogModal = (props: any) => {
columns={[...columns, ...operation]}
afterNewBtn={
[<div className="flex items-center flex-wrap p-[10px] px-btnbase content-before bg-MAIN_BG ">
<RangePicker
onChange={handleDatePickerChange}
value={datePickerValue} />
<div className="flex [&>.reset-btn]:!h-auto flex-nowrap items-center ml-[10px]">
<Button className="reset-btn" onClick={resetQuery}>{$t('重置')}</Button>
<TimeRangeSelector
labelSize="small"
bindRef={bindRef}
hideBtns={['hour']}
defaultTimeButton="day"
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
onTimeRangeChange={handleTimeRangeChange} />
<div className="flex flex-nowrap items-center pt-btnybase">
<Button onClick={resetQuery}>{$t('重置')}</Button>
</div>
</div>]
}
@@ -239,7 +239,7 @@ export const SystemSubscriberConfig = forwardRef<SystemSubscriberConfigHandle,Sy
getSystemList()
}, [serviceId]);
return (<WithPermission access="team.service.subscription.add">
return (<WithPermission access="">
<Form
layout='vertical'
labelAlign='left'
+2 -1
View File
@@ -77,7 +77,8 @@ export default defineConfig({
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
}
}
},
open: true
},
logLevel:'info'
})
@@ -137,7 +137,7 @@ const ServiceHubDetail = () => {
key: 'api-integrate',
label: $t('集成'),
children: <div className={`p-btnbase ${serviceBasicInfo?.serviceKind?.toLocaleLowerCase() === 'ai' ? 'ai-service-api-preview' : ''}`}><Integrate service={service!} /></div>,
icon: <BgColorsOutlined />
icon: <Icon icon="icon-park-solid:whole-site-accelerator" width="15" height="15" />,
}
]
@@ -5,6 +5,7 @@ import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useParams } from "react-router-dom"
import { useState, useEffect } from "react"
import { RouterParams } from "@common/const/type"
import useCopyToClipboard from "@common/hooks/copy.ts";
import { useFetch } from "@common/hooks/http"
const Integrate = ({ service }: { service: ServiceDetailType }) => {
@@ -12,6 +13,7 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => {
const [url, setUrl] = useState('');
const { serviceId} = useParams<RouterParams>()
const {fetchData} = useFetch()
const { copyToClipboard } = useCopyToClipboard();
useEffect(()=>{
setUrl(`${service?.basic?.sitePrefix || window.location?.origin}/api/v1/service/swagger/${serviceId}` )
@@ -28,8 +30,7 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => {
*
*/
const copyURL = async (): Promise<void> => {
await navigator.clipboard.writeText(url)
message.success($t(RESPONSE_TIPS.copySuccess))
copyToClipboard(url)
}
/**
*
@@ -55,8 +56,8 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => {
<p className={stepClass}>{$t('步骤二:导入 API 文档数据')}</p>
<div className='my-[10px]'>{$t('可通过以下 URL 或 下载 Json 文件,导入 API 文档数据到 Agent 平台中。')}</div>
<div className="flex w-full items-center gap-[30px]">
<Space.Compact className=" flex-1 ">
<Input disabled value={url} />
<Space.Compact className="w-[700px]">
<Input className="truncate" disabled title={url} value={url} />
<Button type="primary" onClick={copyURL}>{$t('复制 URL')}</Button>
</Space.Compact>
<span className="text-[14px] font-bold">OR</span>
@@ -11,7 +11,7 @@ import { RouterParams } from "@core/components/aoplatform/RenderRoutes";
import { SimpleTeamItem } from "@common/const/type";
import { useTenantManagementContext } from "../../../contexts/TenantManagementContext";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
import { GlobalProvider, useGlobalContext } from "@common/contexts/GlobalStateContext";
import { $t } from "@common/locales";
import WithPermission from "@common/components/aoplatform/WithPermission";
import InsidePage from "@common/components/aoplatform/InsidePage";
@@ -149,7 +149,7 @@ export default function ServiceHubManagement() {
switch (type){
case 'add':
title=$t('添加消费者')
content=<ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} />
content=<GlobalProvider><ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} /></GlobalProvider>
break;
// case 'edit':{
// title='配置 Open Api'
+2
View File
@@ -69,8 +69,10 @@ func (a *ApplicationClient) toApinto(app *gateway.ApplicationRelease) interface{
Expire: info.Expire,
Pattern: driver.ToPattern(info.Config),
HideCredential: info.HideCredential,
Labels: info.Label,
},
},
Labels: info.Label,
})
}
labels := make(map[string]string)
+6 -2
View File
@@ -3,7 +3,7 @@ package apinto
import (
"context"
"strings"
"github.com/APIParkLab/APIPark/gateway"
admin_client "github.com/eolinker/eosc/process-admin/client"
)
@@ -14,6 +14,10 @@ type ClientDriver struct {
client admin_client.Client
}
func (c *ClientDriver) Strategy() gateway.IStrategyClient {
return NewStrategyClient(c.client)
}
func (c *ClientDriver) Close(ctx context.Context) error {
if c.client != nil {
return c.client.Close()
@@ -74,7 +78,7 @@ func NewClientDriver(cfg *gateway.ClientConfig) (*ClientDriver, error) {
}
func genWorkerID(id string, profession string) string {
suffix := "@" + profession
if strings.HasSuffix(id, suffix) {
return id
-1
View File
@@ -20,7 +20,6 @@ func NewDynamicClient(client admin_client.Client, resource string) (*DynamicClie
cfg, has := gateway.GetDynamicResourceDriver(resource)
if !has {
return nil, errors.New("resource not found")
}
return &DynamicClient{client: client, profession: cfg.Profession, driver: cfg.Driver}, nil
+10 -8
View File
@@ -7,15 +7,17 @@ type Application struct {
}
type Authorization struct {
Type string `json:"type"`
Position string `json:"position"`
TokenName string `json:"token_name"`
Config interface{} `json:"config"`
Users []*AuthUser `json:"users"`
Type string `json:"type"`
Position string `json:"position"`
TokenName string `json:"token_name"`
Config interface{} `json:"config"`
Users []*AuthUser `json:"users"`
Labels map[string]string `json:"labels"`
}
type AuthUser struct {
Expire int64 `json:"expire"`
Pattern interface{} `json:"pattern"`
HideCredential bool `json:"hide_credential"`
Expire int64 `json:"expire"`
Pattern interface{} `json:"pattern"`
HideCredential bool `json:"hide_credential"`
Labels map[string]string `json:"labels"`
}
+7
View File
@@ -63,6 +63,13 @@
rely: eolinker.com:apinto:plugin_app
config:
cache: redis@output
- id: eolinker.com:apinto:strategy-plugin-data_mask
name: strategy_data_mask
status: global
rely: eolinker.com:apinto:plugin_app
config:
cache: redis@output
-
id: eolinker.com:apinto:ai_prompt
name: ai_prompt
+11 -16
View File
@@ -59,27 +59,11 @@ func (p *ProjectClient) online(ctx context.Context, project *gateway.ProjectRele
"project": project.Id,
}
if project.Upstream != nil {
//upstreams, err := matchLabels[entity.Service](ctx, p.client, gateway.ProfessionService, matches)
//if err != nil {
// if !errors.Is(err, proto.Nil) {
// return err
// }
//}
//upstreamMap := utils.SliceToMap(upstreams, func(t *entity.Service) string {
// return t.ID
//})
upstreamId := genWorkerID(project.Upstream.ID, gateway.ProfessionService)
err := p.client.Set(ctx, upstreamId, entity.ToService(project.Upstream, project.Version, matches))
if err != nil {
return err
}
//for id := range upstreamMap {
// err = p.client.Del(ctx, id)
// if err != nil {
// return err
// }
//}
}
routers, err := matchLabels[entity.Router](ctx, p.client, gateway.ProfessionRouter, matches)
@@ -106,6 +90,17 @@ func (p *ProjectClient) online(ctx context.Context, project *gateway.ProjectRele
}
delete(routerMap, id)
}
// 发布策略
for _, s := range project.Strategies {
if s.Config.IsDelete {
p.client.Del(ctx, genWorkerID(s.Config.Name, gateway.ProfessionStrategy))
continue
}
err := p.client.Set(ctx, genWorkerID(s.Config.Name, gateway.ProfessionStrategy), s)
if err != nil {
return err
}
}
// 删除多余配置
for _, v := range routerMap {
err := driver.ApiDelete(ctx, p.client, v)
+48
View File
@@ -0,0 +1,48 @@
package apinto
import (
"context"
"github.com/eolinker/eosc"
"github.com/APIParkLab/APIPark/gateway"
admin_client "github.com/eolinker/eosc/process-admin/client"
)
var _ gateway.IStrategyClient = &StrategyClient{}
type StrategyClient struct {
client admin_client.Client
}
func (s *StrategyClient) Online(ctx context.Context, resources ...*eosc.Base[gateway.StrategyRelease]) error {
s.client.Begin(ctx)
for _, r := range resources {
if r.Config.IsDelete {
s.client.Del(ctx, genWorkerID(r.Config.Name, gateway.ProfessionStrategy))
continue
}
err := s.client.Set(ctx, genWorkerID(r.Config.Name, gateway.ProfessionStrategy), r)
if err != nil {
s.client.Rollback(ctx)
return err
}
}
return s.client.Commit(ctx)
}
func (s *StrategyClient) Offline(ctx context.Context, resources ...*eosc.Base[gateway.StrategyRelease]) error {
s.client.Begin(ctx)
for _, r := range resources {
err := s.client.Del(ctx, genWorkerID(r.Config.Name, gateway.ProfessionStrategy))
if err != nil {
s.client.Rollback(ctx)
return err
}
}
return s.client.Commit(ctx)
}
func NewStrategyClient(client admin_client.Client) *StrategyClient {
return &StrategyClient{client: client}
}
+1
View File
@@ -21,6 +21,7 @@ type IClientDriver interface {
Application() IApplicationClient
Service() IServiceClient
Subscribe() ISubscribeClient
Strategy() IStrategyClient
Dynamic(resource string) (IDynamicClient, error)
PluginSetting() IPluginSetting
Commit(ctx context.Context) error
+5 -36
View File
@@ -5,6 +5,7 @@ const (
ProfessionCertificate = "certificate"
ProfessionRouter = "router"
ProfessionApplication = "app"
ProfessionStrategy = "strategy"
ProfessionService = "service"
ProfessionAIProvider = "ai-provider"
)
@@ -56,42 +57,10 @@ var dynamicResourceMap = map[string]Worker{
Profession: ProfessionCertificate,
Driver: "server",
},
//"openai": {
// Profession: ProfessionAIProvider,
// Driver: "openai",
//},
//"google": {
// Profession: ProfessionAIProvider,
// Driver: "google",
//},
//"anthropic": {
// Profession: ProfessionAIProvider,
// Driver: "anthropic",
//},
//"moonshot": {
// Profession: ProfessionAIProvider,
// Driver: "moonshot",
//},
//"tongyi": {
// Profession: ProfessionAIProvider,
// Driver: "tongyi",
//},
//"zhipuai": {
// Profession: ProfessionAIProvider,
// Driver: "zhipuai",
//},
//"fireworks": {
// Profession: ProfessionAIProvider,
// Driver: "fireworks",
//},
//"novita": {
// Profession: ProfessionAIProvider,
// Driver: "novita",
//},
//"mistralai": {
// Profession: ProfessionAIProvider,
// Driver: "mistralai",
//},
"loki": {
Profession: ProfessionOutput,
Driver: "loki",
},
}
type Worker struct {
+18 -4
View File
@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"github.com/eolinker/eosc"
"github.com/APIParkLab/APIPark/model/plugin_model"
)
@@ -15,6 +17,8 @@ type IServiceClient IResourceClient[ServiceRelease]
type ISubscribeClient IResourceClient[SubscribeRelease]
type IStrategyClient IResourceClient[eosc.Base[StrategyRelease]]
type IResourceClient[T any] interface {
Online(ctx context.Context, resources ...*T) error
Offline(ctx context.Context, resources ...*T) error
@@ -27,10 +31,11 @@ type IDynamicClient interface {
}
type ProjectRelease struct {
Id string `json:"id"`
Version string `json:"version"`
Apis []*ApiRelease `json:"apis"`
Upstream *UpstreamRelease `json:"upstreams"`
Id string `json:"id"`
Version string `json:"version"`
Apis []*ApiRelease `json:"apis"`
Upstream *UpstreamRelease `json:"upstreams"`
Strategies []*eosc.Base[StrategyRelease] `json:"strategies"`
}
type ApiRelease struct {
@@ -73,6 +78,15 @@ type UpstreamRelease struct {
Labels map[string]string
}
type StrategyRelease struct {
Name string `json:"name"`
Desc string `json:"description"`
Driver string `json:"driver"`
Priority int `json:"priority"`
Filters map[string][]string `json:"filters"`
IsDelete bool `json:"-"`
}
type MatchRule struct {
Position string
MatchType string
+5 -1
View File
@@ -6,7 +6,7 @@ go 1.21
require (
github.com/eolinker/ap-account v1.0.15
github.com/eolinker/eosc v0.17.3
github.com/eolinker/eosc v0.18.3
github.com/eolinker/go-common v1.1.1
github.com/gabriel-vasile/mimetype v1.4.4
github.com/getkin/kin-openapi v0.127.0
@@ -75,6 +75,10 @@ require (
gorm.io/driver/mysql v1.5.2 // indirect
)
//replace (
// github.com/eolinker/eosc => ../../eolinker/eosc
//)
//replace github.com/eolinker/ap-account => ../aoaccount
//
//replace github.com/eolinker/go-common => ../go-common
+2 -2
View File
@@ -30,8 +30,8 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eolinker/ap-account v1.0.15 h1:n6DJeL6RHZ8eLlZUcY2U3H4d/GPaA5oelAx3R0E6yL8=
github.com/eolinker/ap-account v1.0.15/go.mod h1:zm/Ivs6waJ/M/nEszhpPmM6g50y/MKO+5eABFAdeD0g=
github.com/eolinker/eosc v0.17.3 h1:sr2yT+v/AsqEdciRaaZZj0zL9pTufR5RvDW6+65hraQ=
github.com/eolinker/eosc v0.17.3/go.mod h1:xgq816hpanlMXFtZw7Ztdctb1eEk9UPHchY4NfFO6Cw=
github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
github.com/eolinker/go-common v1.1.1 h1:3WqqecGqcHDgpa8Ljp156c1uWeZKP1CKScdU+6sOfcc=
github.com/eolinker/go-common v1.1.1/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
+41
View File
@@ -0,0 +1,41 @@
package log_driver
import (
"time"
"github.com/eolinker/eosc"
)
type ILogDriver interface {
LogInfo(clusterId string, id string) (*LogInfo, error)
LogCount(clusterId string, conditions map[string]string, spendHour int64, group string) (map[string]int64, error)
Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*Log, int64, error)
}
var (
driverManager = NewDriverManager()
)
type DriverManager struct {
drivers eosc.Untyped[string, ILogDriver]
}
func NewDriverManager() *DriverManager {
return &DriverManager{drivers: eosc.BuildUntyped[string, ILogDriver]()}
}
func (m *DriverManager) Set(name string, driver ILogDriver) {
m.drivers.Set(name, driver)
}
func (m *DriverManager) Get(name string) (ILogDriver, bool) {
return m.drivers.Get(name)
}
func SetDriver(name string, driver ILogDriver) {
driverManager.Set(name, driver)
}
func GetDriver(name string) (ILogDriver, bool) {
return driverManager.Get(name)
}
+25
View File
@@ -0,0 +1,25 @@
package log_driver
import (
"time"
)
type Log struct {
ID string
Service string
Method string
Url string
RemoteIP string
Consumer string
Authorization string
RecordTime time.Time
}
type LogInfo struct {
ID string
ContentType string
RequestBody string
ProxyBody string
ProxyResponseBody string
ResponseBody string
}
+39
View File
@@ -0,0 +1,39 @@
package log_driver
import "github.com/eolinker/eosc"
var (
defaultFactoryManager = NewFactoryManager()
)
type IFactory interface {
Create(config string) (ILogDriver, map[string]interface{}, error)
}
type factoryManager struct {
factories eosc.Untyped[string, IFactory]
}
func NewFactoryManager() *factoryManager {
return &factoryManager{factories: eosc.BuildUntyped[string, IFactory]()}
}
func (m *factoryManager) Set(name string, factory IFactory) {
m.factories.Set(name, factory)
}
func (m *factoryManager) Get(name string) (IFactory, bool) {
return m.factories.Get(name)
}
func RegisterFactory(name string, factory IFactory) {
defaultFactoryManager.Set(name, factory)
}
func GetFactory(name string) (IFactory, bool) {
return defaultFactoryManager.Get(name)
}
func Drivers() []string {
return defaultFactoryManager.factories.Keys()
}
+77
View File
@@ -0,0 +1,77 @@
package loki
import (
"fmt"
"net/url"
)
type DriverConfig struct {
URL string `json:"url"`
Header []*Param `json:"headers"`
}
type Param struct {
Key string `json:"key"`
Value string `json:"value"`
}
func (d *DriverConfig) Check() error {
if d.URL == "" {
return fmt.Errorf("url is empty")
}
u, err := url.Parse(d.URL)
if err != nil {
return err
}
if u.Host == "" {
return fmt.Errorf("host is empty")
}
if u.Scheme == "" {
u.Scheme = "http"
}
d.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Host)
return nil
}
type Response[T any] struct {
Data *Data[T] `json:"data"`
Status string `json:"status"`
}
type Data[T any] struct {
ResultType string `json:"resultType"`
Result []*T `json:"result"`
}
type LogCount struct {
Metric map[string]string `json:"metric"`
Value []interface{} `json:"value"`
}
type LogInfo struct {
Stream *LogDetail `json:"stream"`
}
type LogDetail struct {
Api string `json:"api"`
Application string `json:"application"`
Strategy string `json:"strategy"`
ContentType string `json:"content_type"`
Cluster string `json:"cluster"`
Msec string `json:"msec"`
Node string `json:"node"`
RequestId string `json:"request_id"`
RequestMethod string `json:"request_method"`
RequestScheme string `json:"request_scheme"`
RequestTime string `json:"request_time"`
RequestUri string `json:"request_uri"`
RequestBody string `json:"request_body"`
ProxyBody string `json:"proxy_body"`
ResponseBody string `json:"response_body"`
ProxyResponseBody string `json:"proxy_response_body"`
Service string `json:"service"`
Provider string `json:"provider"`
Authorization string `json:"authorization"`
SrcIp string `json:"src_ip"`
Status string `json:"status"`
}
+296
View File
@@ -0,0 +1,296 @@
package loki
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/eolinker/eosc/log"
log_driver "github.com/APIParkLab/APIPark/log-driver"
)
func init() {
log_driver.RegisterFactory("loki", &factory{})
}
type factory struct {
}
func (f *factory) Create(config string) (log_driver.ILogDriver, map[string]interface{}, error) {
return NewDriver(config)
}
var (
client = http.Client{}
)
type Driver struct {
url string
headers map[string]string
}
func NewDriver(config string) (*Driver, map[string]interface{}, error) {
cfg := new(DriverConfig)
err := json.Unmarshal([]byte(config), cfg)
if err != nil {
return nil, nil, err
}
err = cfg.Check()
if err != nil {
return nil, nil, err
}
headers := map[string]string{}
for _, h := range cfg.Header {
headers[h.Key] = h.Value
}
return &Driver{
url: cfg.URL,
headers: headers,
}, map[string]interface{}{
"url": cfg.URL,
"headers": headers,
}, nil
}
func (d *Driver) LogInfo(clusterId string, id string) (*log_driver.LogInfo, error) {
if id == "" {
return nil, fmt.Errorf("id is empty")
}
queries := url.Values{}
queries.Set("query", fmt.Sprintf("{cluster=\"%s\"} | json | request_id = `%s`", clusterId, id))
now := time.Now()
start := now.Add(-time.Hour * 24 * 30)
queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
queries.Set("limit", "1")
log.Debug("query is ", queries.Get("query"))
list, err := send[LogInfo](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query_range", d.url), d.headers, queries, "")
if err != nil {
return nil, err
}
if len(list) < 1 || list[0].Stream == nil {
return nil, fmt.Errorf("no log found")
}
stream := list[0].Stream
return &log_driver.LogInfo{
ID: stream.RequestId,
ContentType: stream.ContentType,
RequestBody: stream.RequestBody,
ProxyBody: stream.ProxyBody,
ProxyResponseBody: stream.ProxyResponseBody,
ResponseBody: stream.ResponseBody,
}, nil
}
func (d *Driver) LogCount(clusterId string, conditions map[string]string, spendHour int64, group string) (map[string]int64, error) {
cs := make([]string, 0, len(conditions))
for k, v := range conditions {
if strings.HasPrefix(k, "#") {
cs = append(cs, v)
continue
}
cs = append(cs, fmt.Sprintf("%s=\"%s\"", k, v))
}
tmpCondition := ""
if len(conditions) > 0 {
tmpCondition = "|" + strings.Join(cs, "|")
}
queries := url.Values{}
queries.Set("query", fmt.Sprintf("sum(count_over_time({cluster=\"%s\"} | json %s [%dh])) by (%s)", clusterId, tmpCondition, spendHour, group))
sendRequestTime := time.Now()
list, err := send[LogCount](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query", d.url), d.headers, queries, "")
if err != nil {
return nil, err
}
log.DebugF("send request spend time: %v", time.Now().Sub(sendRequestTime))
log.Debug("query is ", queries.Get("query"))
result := make(map[string]int64)
for _, l := range list {
if len(l.Value) != 2 {
continue
}
value, ok := l.Value[1].(string)
if !ok {
continue
}
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
continue
}
result[l.Metric[group]] = v
}
return result, nil
}
func (d *Driver) Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*log_driver.Log, int64, error) {
if start.After(end) {
return nil, 0, fmt.Errorf("start time is greater than end time")
}
if len(conditions) < 1 {
return nil, 0, fmt.Errorf("conditions is empty")
}
if offset < 1 {
offset = 1
}
if limit < 1 {
limit = 15
}
count, err := d.logCount(clusterId, conditions, start, end)
if err != nil {
return nil, 0, err
}
if count == 0 {
return nil, 0, nil
}
if count < (offset-1)*limit {
return nil, 0, fmt.Errorf("offset is greater than count")
}
cs := make([]string, 0, len(conditions))
for k, v := range conditions {
if strings.HasPrefix(k, "#") {
cs = append(cs, v)
continue
}
cs = append(cs, fmt.Sprintf("%s=~\"%s\"", k, v))
}
queries := url.Values{}
queries.Set("query", fmt.Sprintf("{cluster=\"%s\"} | json | %s", clusterId, strings.Join(cs, " | ")))
queries.Set("limit", strconv.FormatInt(limit, 10))
queries.Set("direction", "backward")
queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
log.Debug("query is ", queries.Get("query"))
logs, err := d.recuseLogs(queries, end, offset)
if err != nil {
return nil, 0, err
}
return logs, count, nil
}
func (d *Driver) recuseLogs(queries url.Values, end time.Time, offset int64) ([]*log_driver.Log, error) {
queries.Set("end", strconv.FormatInt(end.UnixNano(), 10))
list, err := send[LogInfo](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query_range", d.url), d.headers, queries, "")
if err != nil {
return nil, err
}
if len(list) < 1 {
return nil, nil
}
if offset > 1 {
// 获取list最后一个元素的时间戳
last := list[len(list)-1].Stream
if last == nil {
return nil, fmt.Errorf("last log is empty")
}
msec, err := strconv.ParseInt(last.Msec, 10, 64)
if err != nil {
return nil, fmt.Errorf("parse last log time error: %v", err)
}
return d.recuseLogs(queries, time.UnixMilli(msec), offset-1)
}
logs := make([]*log_driver.Log, 0, len(list))
for _, l := range list {
if l.Stream == nil {
continue
}
detail := l.Stream
msec, _ := strconv.ParseInt(detail.Msec, 10, 64)
logs = append(logs, &log_driver.Log{
ID: detail.RequestId,
Service: detail.Provider,
Method: detail.RequestMethod,
Url: detail.RequestUri,
RemoteIP: detail.SrcIp,
Consumer: detail.Application,
Authorization: detail.Authorization,
RecordTime: time.UnixMilli(msec),
})
}
sort.Slice(logs, func(i, j int) bool {
return logs[i].RecordTime.After(logs[j].RecordTime)
})
return logs, nil
}
func (d *Driver) logCount(clusterId string, conditions map[string]string, start time.Time, end time.Time) (int64, error) {
// 先查在这段时间内符合条件的日志数量
queries := url.Values{}
queries.Add("start", strconv.FormatInt(start.UnixNano(), 10))
queries.Add("end", strconv.FormatInt(end.UnixNano(), 10))
cs := make([]string, 0, len(conditions))
for k, v := range conditions {
if strings.HasPrefix(k, "#") {
cs = append(cs, v)
continue
}
cs = append(cs, fmt.Sprintf("%s=\"%s\"", k, v))
}
tmpCondition := ""
if len(conditions) > 0 {
tmpCondition = "|" + strings.Join(cs, "|")
}
queries.Set("query", fmt.Sprintf("sum(count_over_time({cluster=\"%s\"} | json %s [720h]))", clusterId, tmpCondition))
list, err := send[LogCount](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query", d.url), d.headers, queries, "")
if err != nil {
return 0, err
}
if len(list) < 1 || len(list[0].Value) < 2 {
return 0, nil
}
value, ok := list[0].Value[1].(string)
if !ok {
return 0, nil
}
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
return v, nil
}
func send[T any](method string, uri string, headers map[string]string, queries url.Values, body string) ([]*T, error) {
if queries != nil && len(queries) > 0 {
uri = fmt.Sprintf("%s?%s", uri, queries.Encode())
}
req, err := http.NewRequest(method, uri, strings.NewReader(body))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w,uri is %s", err, uri)
}
for key, value := range headers {
req.Header.Set(key, value)
}
log.DebugF("do request: %s", uri)
doRequestTime := time.Now()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
log.DebugF("do request spend time: %v", time.Now().Sub(doRequestTime))
defer resp.Body.Close()
respData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode > 399 {
return nil, fmt.Errorf("failed to send request: %s,body is %s", resp.Status, string(respData))
}
result := new(Response[T])
err = json.Unmarshal(respData, result)
if err != nil {
return nil, fmt.Errorf("failed to decode response: %w,body is %s", err, string(respData))
}
return result.Data.Result, nil
}
+95
View File
@@ -0,0 +1,95 @@
package loki
import (
"testing"
"time"
)
func TestLoki(t *testing.T) {
d, err := NewDriver(`{"url":"http://localhost:3100","header":{"Content-Type":"application/json","X-Scope-OrgID":"tenant1"}}`)
if err != nil {
t.Fatalf("failed to create driver: %v", err)
}
logCountResult, err := d.LogCount("apinto", nil, 720, "strategy")
if err != nil {
t.Fatalf("failed to get log count: %v", err)
}
t.Log(logCountResult)
logs, count, err := d.Logs("apinto", map[string]string{"strategy": "03899736-5d79-4f26-bd6a-c312a5880780"}, time.Now().Add(-time.Hour*24), time.Now(), 1, 1)
if err != nil {
t.Fatalf("failed to get logs: %v", err)
}
t.Log(logs, count)
info, err := d.LogInfo("apinto", "c9f6b19c-7dfe-496b-9b39-4d049232fe95")
if err != nil {
t.Fatalf("failed to get log info: %v", err)
}
t.Log(info)
}
//func TestLokiLog(t *testing.T) {
//
// headers := make(map[string]string)
// headers["Content-Type"] = "application/json"
// headers["X-Scope-OrgID"] = "tenant1"
// queries := url.Values{}
// queries.Set("query", "{cluster=\"apinto\"} | json | request_id = `c9f6b19c-7dfe-496b-9b39-4d049232fe95`")
// now := time.Now()
// start := now.Add(-time.Hour * 24 * 30)
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
// queries.Set("limit", "100")
// a := time.Now()
// result, err := send[LogInfo](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
// if err != nil {
// t.Fatalf("failed to send request: %v", err)
// }
// t.Log(time.Now().Sub(a))
// data, err := json.Marshal(result)
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
//}
//
//func TestLokiLogCount(t *testing.T) {
// headers := make(map[string]string)
// headers["Content-Type"] = "application/json"
// headers["X-Scope-OrgID"] = "tenant1"
// queries := url.Values{}
// //queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h])) by (strategy)")
// queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h]))")
// result, err := send[LogCount](http.MethodGet, "http://localhost:3100/loki/api/v1/query", headers, queries, "")
// if err != nil {
// t.Fatalf("failed to send request: %v", err)
// }
// data, err := json.Marshal(result)
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
//}
//
//func TestLokiLogs(t *testing.T) {
// headers := make(map[string]string)
// headers["Content-Type"] = "application/json"
// headers["X-Scope-OrgID"] = "tenant1"
// queries := url.Values{}
// queries.Set("query", "{cluster=\"apinto\"} | json | strategy=\"03899736-5d79-4f26-bd6a-c312a5880780\"")
// now := time.Now()
// start := now.Add(-time.Hour * 24 * 30)
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
// queries.Set("limit", "1")
// now = time.Now()
// result, err := send[map[string]interface{}](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
// t.Log(time.Now().Sub(now))
// if err != nil {
// t.Fatalf("failed to send request: %v", err)
// }
// data, err := json.Marshal(result)
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
//}
+1 -1
View File
@@ -34,7 +34,7 @@ func newAIUpstream(provider string, uri model_runtime.IProviderURI) *gateway.Dyn
"driver": "http",
"balance": "round-robin",
"nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())},
"pass_node": "node",
"pass_host": "node",
"scheme": uri.Scheme(),
"timeout": 300000,
},
+5 -1
View File
@@ -3,6 +3,7 @@ package api_doc
import (
"context"
"errors"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
"github.com/APIParkLab/APIPark/service/service"
@@ -20,16 +21,19 @@ type imlAPIDocModule struct {
}
func (i *imlAPIDocModule) UpdateDoc(ctx context.Context, serviceId string, input *api_doc_dto.UpdateDoc) (*api_doc_dto.ApiDocDetail, error) {
_, err := i.serviceService.Get(ctx, serviceId)
info, err := i.serviceService.Get(ctx, serviceId)
if err != nil {
return nil, err
}
if input.Id == "" {
input.Id = uuid.New().String()
}
// 每个API加上前缀
err = i.apiDocService.UpdateDoc(ctx, serviceId, &api_doc.UpdateDoc{
ID: input.Id,
Content: input.Content,
Prefix: info.Prefix,
})
if err != nil {
return nil, err
@@ -3,9 +3,9 @@ package aksk
import (
"encoding/json"
"fmt"
auth_driver "github.com/APIParkLab/APIPark/module/application-authorization/auth-driver"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
)
@@ -26,8 +26,7 @@ type Config struct {
}
func (a *Config) ID() string {
//TODO implement me
panic("implement me")
return a.Ak
}
func (a *Config) Valid() ([]byte, error) {
+6
View File
@@ -99,6 +99,9 @@ func (i *imlAuthorizationModule) getApplications(ctx context.Context, appIds []s
Expire: a.ExpireTime,
Config: authCfg,
HideCredential: a.HideCredential,
Label: map[string]string{
"authorization": a.UUID,
},
}
}),
}
@@ -153,6 +156,9 @@ func (i *imlAuthorizationModule) online(ctx context.Context, s *service.Service)
Expire: a.ExpireTime,
Config: authCfg,
HideCredential: a.HideCredential,
Label: map[string]string{
"authorization": a.UUID,
},
}
}),
}
+1
View File
@@ -41,6 +41,7 @@ type ServiceBasic struct {
ApprovalType string `json:"approval_type"`
ServiceKind string `json:"service_kind"`
InvokeAddress string `json:"invoke_address"`
SitePrefix string `json:"site_prefix"`
}
type ServiceApiBasic struct {
+2
View File
@@ -284,6 +284,7 @@ func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*ca
serviceDoc = commit.Data.Content
}
invokeAddress, _ := i.settingService.Get(ctx, setting.KeyInvokeAddress)
sitePrefix, _ := i.settingService.Get(ctx, setting.KeySitePrefix)
return &catalogue_dto.ServiceDetail{
Name: s.Name,
@@ -301,6 +302,7 @@ func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*ca
ApprovalType: s.ApprovalType.String(),
ServiceKind: s.Kind.String(),
InvokeAddress: invokeAddress,
SitePrefix: sitePrefix,
},
APIDoc: apiDoc,
}, nil
+6 -6
View File
@@ -145,8 +145,8 @@ func (m *imlClusterModule) ClusterNodes(ctx context.Context, clusterId string) (
// return m.Get(ctx, create.Id)
//}
//
//func (m *imlClusterModule) Search(ctx context.Context, keyword string) ([]*paritiondto.Item, error) {
// partitions, err := m.partitionService.Search(ctx, keyword, nil)
//func (m *imlClusterModule) SearchByDriver(ctx context.Context, keyword string) ([]*paritiondto.Item, error) {
// partitions, err := m.partitionService.SearchByDriver(ctx, keyword, nil)
// if err != nil {
// return nil, err
// }
@@ -185,7 +185,7 @@ func (m *imlClusterModule) ClusterNodes(ctx context.Context, clusterId string) (
// if err != nil {
// return nil, err
// }
// //oDetails, err := m.organizationService.Search(ctx, "")
// //oDetails, err := m.organizationService.SearchByDriver(ctx, "")
// //if err != nil {
// // return nil, err
// //}
@@ -248,7 +248,7 @@ func (m *imlClusterModule) ClusterNodes(ctx context.Context, clusterId string) (
//}
//
//func (m *imlClusterModule) Simple(ctx context.Context) ([]*paritiondto.Simple, error) {
// pm, err := m.partitionService.Search(ctx, "", nil)
// pm, err := m.partitionService.SearchByDriver(ctx, "", nil)
// if err != nil {
// return nil, err
// }
@@ -262,7 +262,7 @@ func (m *imlClusterModule) ClusterNodes(ctx context.Context, clusterId string) (
//}
//
//func (m *imlClusterModule) SimpleByIds(ctx context.Context, ids []string) ([]*paritiondto.Simple, error) {
// pm, err := m.partitionService.Search(ctx, "", map[string]interface{}{
// pm, err := m.partitionService.SearchByDriver(ctx, "", map[string]interface{}{
// "uuid": ids,
// })
// if err != nil {
@@ -278,7 +278,7 @@ func (m *imlClusterModule) ClusterNodes(ctx context.Context, clusterId string) (
//
//}
//func (m *imlClusterModule) SimpleWithCluster(ctx context.Context) ([]*paritiondto.SimpleWithCluster, error) {
// pm, err := m.partitionService.Search(ctx, "", nil)
// pm, err := m.partitionService.SearchByDriver(ctx, "", nil)
// if err != nil {
// return nil, err
// }
+7
View File
@@ -0,0 +1,7 @@
package log_dto
type Save struct {
ID string `json:"id"`
Cluster string `json:"cluster"`
Config map[string]interface{} `json:"config"`
}
+14
View File
@@ -0,0 +1,14 @@
package log_dto
import (
"github.com/eolinker/go-common/auto"
)
type LogSource struct {
ID string `json:"id"`
Config map[string]interface{} `json:"config"`
Creator auto.Label `json:"creator" aolabel:"user"`
Updater auto.Label `json:"updater" aolabel:"user"`
CreateAt auto.TimeLabel `json:"create_time"`
UpdateAt auto.TimeLabel `json:"update_time"`
}
+224
View File
@@ -0,0 +1,224 @@
package log
import (
"context"
"encoding/json"
"errors"
"time"
log_driver "github.com/APIParkLab/APIPark/log-driver"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/go-common/store"
"gorm.io/gorm"
"github.com/APIParkLab/APIPark/service/cluster"
"github.com/eolinker/go-common/auto"
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
"github.com/APIParkLab/APIPark/service/log"
log_print "github.com/eolinker/eosc/log"
)
var _ ILogModule = (*imlLogModule)(nil)
type imlLogModule struct {
service log.ILogService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
transaction store.ITransaction `autowired:""`
}
var labels = map[string]string{
"cluster": "$cluster",
"node": "$node",
}
var logFormatter = map[string]interface{}{
"fields": []string{
"$msec",
"$service",
"$provider",
"$scheme as request_scheme",
"$url as request_uri",
"$host as request_host",
"$header as request_header",
"$remote_addr",
"$request_body",
"$proxy_body",
"$proxy_method",
"$proxy_scheme",
"$proxy_uri",
"$api",
"$proxy_host",
"$proxy_header",
"$proxy_addr",
"$response_headers",
"$status",
"$content_type",
"$proxy_status",
"$request_time",
"$response_time",
"$node",
"$cluster",
"$application",
"$src_ip",
"$block_name as strategy",
"$request_id",
"$request_method",
"$authorization",
"$response_body",
"$proxy_response_body",
},
}
func (i *imlLogModule) Save(ctx context.Context, driver string, input *log_dto.Save) error {
factory, has := log_driver.GetFactory(driver)
if !has {
return errors.New("driver not found")
}
input.Cluster = cluster.DefaultClusterID
var cfg *string
if input.Config != nil {
data, _ := json.Marshal(input.Config)
tmp := string(data)
cfg = &tmp
}
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
err := i.service.UpdateLogSource(txCtx, driver, &log.Save{
ID: input.ID,
Cluster: &input.Cluster,
Config: cfg,
})
if err != nil {
return err
}
info, err := i.service.GetLogSource(txCtx, driver)
if err != nil {
return err
}
d, c, err := factory.Create(info.Config)
if err != nil {
return err
}
client, err := i.clusterService.GatewayClient(txCtx, input.Cluster)
if err != nil {
return err
}
defer client.Close(txCtx)
dynamicClient, err := client.Dynamic(driver)
if err != nil {
return err
}
attr := make(map[string]interface{})
attr["driver"] = driver
attr["formatter"] = logFormatter
attr["labels"] = labels
attr["method"] = "POST"
attr["scopes"] = []string{"access_log"}
attr["type"] = "json"
for k, v := range c {
attr[k] = v
}
err = dynamicClient.Online(txCtx, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: driver,
Description: "collect access log",
Version: time.Now().Format("20060102150405"),
Resource: gateway.ProfessionOutput,
},
Attr: attr,
})
if err != nil {
return err
}
log_driver.SetDriver(driver, d)
return nil
})
}
func (i *imlLogModule) Get(ctx context.Context, driver string) (*log_dto.LogSource, error) {
info, err := i.service.GetLogSource(ctx, driver)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
return nil, nil
}
cfg := make(map[string]interface{})
if info.Config != "" {
err = json.Unmarshal([]byte(info.Config), &cfg)
if err != nil {
return nil, err
}
}
return &log_dto.LogSource{
ID: info.ID,
Config: cfg,
Creator: auto.UUID(info.Creator),
Updater: auto.UUID(info.Updater),
CreateAt: auto.TimeLabel(info.CreateAt),
UpdateAt: auto.TimeLabel(info.UpdateAt),
}, nil
}
func (i *imlLogModule) OnComplete() {
}
func (i *imlLogModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
drivers := log_driver.Drivers()
if len(drivers) < 1 {
return nil
}
for _, driver := range drivers {
factory, has := log_driver.GetFactory(driver)
if !has {
log_print.Errorf("driver %s not found", driver)
continue
}
info, err := i.service.GetLogSource(ctx, driver)
if err != nil {
log_print.Errorf("get log source %s error: %s", driver, err)
continue
}
d, c, err := factory.Create(info.Config)
if err != nil {
log_print.Errorf("create driver %s error: %s,config: %s", driver, err, info.Config)
continue
}
log_driver.SetDriver(driver, d)
dynamicClient, err := clientDriver.Dynamic(driver)
if err != nil {
log_print.Errorf("get dynamic client %s error: %s", driver, err)
continue
}
attr := make(map[string]interface{})
attr["driver"] = driver
attr["formatter"] = logFormatter
attr["labels"] = labels
attr["method"] = "POST"
for k, v := range c {
attr[k] = v
}
err = dynamicClient.Online(ctx, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: driver,
Description: "collect access log",
Version: time.Now().Format("20060102150405"),
Resource: gateway.ProfessionOutput,
},
Attr: attr,
})
if err != nil {
log_print.Errorf("online driver %s error: %s", driver, err)
continue
}
}
return nil
}
+26
View File
@@ -0,0 +1,26 @@
package log
import (
"context"
"reflect"
"github.com/eolinker/go-common/autowire"
"github.com/APIParkLab/APIPark/gateway"
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
)
type ILogModule interface {
Save(ctx context.Context, driver string, input *log_dto.Save) error
Get(ctx context.Context, driver string) (*log_dto.LogSource, error)
}
func init() {
logModule := new(imlLogModule)
autowire.Auto[ILogModule](func() reflect.Value {
gateway.RegisterInitHandleFunc(logModule.initGateway)
return reflect.ValueOf(logModule)
})
}
+1 -1
View File
@@ -24,7 +24,7 @@ type CommonInput struct {
type StatisticInput struct {
Apis []string `json:"apis"`
Projects []string `json:"projects"`
Services []string `json:"services"`
Path string `json:"path"`
*CommonInput
}
+3 -3
View File
@@ -21,12 +21,12 @@ type ApiStatisticBasicItem struct {
*MonCommonData
}
type ProjectStatisticItem struct {
*ProjectStatisticBasicItem
type ServiceStatisticItem struct {
*ServiceStatisticBasicItem
IsRed bool `json:"is_red"` //是否标红
}
type ProjectStatisticBasicItem struct {
type ServiceStatisticBasicItem struct {
Id string `json:"id"` //订阅方ID
Name string `json:"name"` //订阅方名称
*MonCommonData
+447 -11
View File
@@ -5,14 +5,15 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"time"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/eosc/log"
"github.com/eolinker/go-common/auto"
"github.com/eolinker/go-common/store"
"github.com/eolinker/go-common/utils"
"gorm.io/gorm"
"sort"
"time"
"github.com/APIParkLab/APIPark/service/service"
@@ -42,6 +43,442 @@ type imlMonitorStatisticModule struct {
apiService api.IAPIService `autowired:""`
}
func (i *imlMonitorStatisticModule) ApiStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
wm := make(map[string]interface{})
if len(input.Apis) > 0 {
wm["uuid"] = input.Apis
wheres = append(wheres, monitor.MonWhereItem{
Key: "api",
Operation: "in",
Values: input.Apis,
})
}
if len(input.Services) > 0 {
wm["service"] = input.Services
wheres = append(wheres, monitor.MonWhereItem{
Key: "project",
Operation: "in",
Values: input.Services,
})
}
// 查询符合条件的API
apis, err := i.apiService.Search(ctx, input.Path, wm)
if err != nil {
return nil, err
}
if len(apis) < 1 {
// 没有符合条件的API
return make([]*monitor_dto.ApiStatisticBasicItem, 0), nil
}
apiIds := utils.SliceToSlice(apis, func(t *api.API) string {
return t.UUID
})
apiInfos, err := i.apiService.ListInfo(ctx, apiIds...)
if err != nil {
return nil, err
}
return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
}
func (i *imlMonitorStatisticModule) apiStatistics(ctx context.Context, clusterId string, apiInfos []*api.Info, start time.Time, end time.Time, wheres []monitor.MonWhereItem, limit int) ([]*monitor_dto.ApiStatisticBasicItem, error) {
statisticMap, err := i.statistics(ctx, clusterId, "api", start, end, wheres, limit)
if err != nil {
return nil, err
}
result := make([]*monitor_dto.ApiStatisticBasicItem, 0, len(statisticMap))
for _, item := range apiInfos {
statisticItem := &monitor_dto.ApiStatisticBasicItem{
Id: item.UUID,
Name: item.Name,
Path: item.Path,
Service: auto.UUID(item.Service),
MonCommonData: new(monitor_dto.MonCommonData),
}
if val, ok := statisticMap[item.UUID]; ok {
statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val)
delete(statisticMap, item.UUID)
}
result = append(result, statisticItem)
}
for key, item := range statisticMap {
statisticItem := &monitor_dto.ApiStatisticBasicItem{
Id: key,
Name: "未知API-" + key,
MonCommonData: monitor_dto.ToMonCommonData(item),
}
if key == "-" {
statisticItem.Name = "无API"
}
result = append(result, statisticItem)
}
sort.Slice(result, func(i, j int) bool {
return result[i].RequestTotal > result[j].RequestTotal
})
return result, nil
}
func (i *imlMonitorStatisticModule) SubscriberStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
apps, err := i.serviceService.AppList(ctx, input.Services...)
if err != nil {
return nil, err
}
appIds := utils.SliceToSlice(apps, func(p *service.Service) string {
return p.Id
})
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
if len(appIds) > 0 {
wheres = append(wheres, monitor.MonWhereItem{
Key: "app",
Operation: "in",
Values: appIds,
})
}
return i.serviceStatistics(ctx, clusterId, apps, "app", formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
}
func (i *imlMonitorStatisticModule) serviceStatistics(ctx context.Context, clusterId string, services []*service.Service, groupBy string, start time.Time, end time.Time, wheres []monitor.MonWhereItem, limit int) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
statisticMap, err := i.statistics(ctx, clusterId, groupBy, start, end, wheres, limit)
if err != nil {
return nil, err
}
result := make([]*monitor_dto.ServiceStatisticBasicItem, 0, len(statisticMap))
for _, item := range services {
statisticItem := &monitor_dto.ServiceStatisticBasicItem{
Id: item.Id,
Name: item.Name,
MonCommonData: new(monitor_dto.MonCommonData),
}
if val, ok := statisticMap[item.Id]; ok {
statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val)
delete(statisticMap, item.Id)
}
result = append(result, statisticItem)
}
for key, item := range statisticMap {
statisticItem := &monitor_dto.ServiceStatisticBasicItem{
Id: key,
Name: "未知-" + key,
MonCommonData: monitor_dto.ToMonCommonData(item),
}
if key == "-" {
statisticItem.Name = "-"
}
result = append(result, statisticItem)
}
sort.Slice(result, func(i, j int) bool {
return result[i].RequestTotal > result[j].RequestTotal
})
return result, nil
}
func (i *imlMonitorStatisticModule) ProviderStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
services, err := i.serviceService.ServiceList(ctx, input.Services...)
if err != nil {
return nil, err
}
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
if len(input.Services) > 0 {
wheres = append(wheres, monitor.MonWhereItem{
Key: "provider",
Operation: "in",
Values: input.Services,
})
}
return i.serviceStatistics(ctx, clusterId, services, "provider", formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
}
func (i *imlMonitorStatisticModule) APITrend(ctx context.Context, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, "", err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "api",
Operation: "=",
Values: []string{apiId},
})
executor, err := i.getExecutor(ctx, clusterId)
if err != nil {
return nil, "", err
}
result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres)
if err != nil {
return nil, "", err
}
return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil
}
func (i *imlMonitorStatisticModule) ProviderTrend(ctx context.Context, providerId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, "", err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "provider",
Operation: "=",
Values: []string{providerId},
})
executor, err := i.getExecutor(ctx, clusterId)
if err != nil {
return nil, "", err
}
result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres)
if err != nil {
return nil, "", err
}
return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil
}
func (i *imlMonitorStatisticModule) SubscriberTrend(ctx context.Context, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, "", err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "app",
Operation: "=",
Values: []string{subscriberId},
})
executor, err := i.getExecutor(ctx, clusterId)
if err != nil {
return nil, "", err
}
result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres)
if err != nil {
return nil, "", err
}
return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil
}
func (i *imlMonitorStatisticModule) InvokeTrendWithSubscriberAndApi(ctx context.Context, apiId string, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, "", err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "api",
Operation: "=",
Values: []string{apiId},
}, monitor.MonWhereItem{
Key: "app",
Operation: "=",
Values: []string{subscriberId},
})
executor, err := i.getExecutor(ctx, clusterId)
if err != nil {
return nil, "", err
}
result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres)
if err != nil {
return nil, "", err
}
return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil
}
func (i *imlMonitorStatisticModule) InvokeTrendWithProviderAndApi(ctx context.Context, providerId string, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, "", err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "api",
Operation: "=",
Values: []string{apiId},
}, monitor.MonWhereItem{
Key: "provider",
Operation: "=",
Values: []string{providerId},
})
executor, err := i.getExecutor(ctx, clusterId)
if err != nil {
return nil, "", err
}
result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres)
if err != nil {
return nil, "", err
}
return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil
}
func (i *imlMonitorStatisticModule) statisticOnApi(ctx context.Context, clusterId string, apiId string, groupBy string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
var service []*service.Service
switch groupBy {
case "app":
service, err = i.serviceService.AppList(ctx)
case "provider":
service, err = i.serviceService.ServiceList(ctx)
default:
return nil, errors.New("invalid group by")
}
if err != nil {
return nil, err
}
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "api",
Operation: "=",
Values: []string{apiId},
})
statisticMap, err := i.statistics(ctx, clusterId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
if err != nil {
return nil, err
}
result := make([]*monitor_dto.ServiceStatisticBasicItem, 0, len(statisticMap))
for _, item := range service {
statisticItem := &monitor_dto.ServiceStatisticBasicItem{
Id: item.Id,
Name: item.Name,
MonCommonData: new(monitor_dto.MonCommonData),
}
if val, ok := statisticMap[item.Id]; ok {
statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val)
delete(statisticMap, item.Id)
}
result = append(result, statisticItem)
}
for key, item := range statisticMap {
statisticItem := &monitor_dto.ServiceStatisticBasicItem{
Id: key,
Name: "未知-" + key,
MonCommonData: monitor_dto.ToMonCommonData(item),
}
if key == "-" {
statisticItem.Name = "-"
}
result = append(result, statisticItem)
}
sort.Slice(result, func(i, j int) bool {
return result[i].RequestTotal > result[j].RequestTotal
})
return result, nil
}
func (i *imlMonitorStatisticModule) ProviderStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
return i.statisticOnApi(ctx, clusterId, apiId, "provider", input)
}
func (i *imlMonitorStatisticModule) ApiStatisticsOnProvider(ctx context.Context, providerId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
apiInfos, err := i.apiService.ListInfoForService(ctx, providerId)
if err != nil {
return nil, err
}
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "provider",
Operation: "=",
Values: []string{providerId},
})
return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
}
func (i *imlMonitorStatisticModule) ApiStatisticsOnSubscriber(ctx context.Context, subscriberId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
return nil, err
}
// 根据订阅ID查询订阅的服务列表
subscriptions, err := i.subscribeService.MySubscribeServices(ctx, subscriberId, nil)
if err != nil {
return nil, err
}
serviceIds := utils.SliceToSlice(subscriptions, func(t *subscribe.Subscribe) string {
return t.Service
})
if len(serviceIds) < 1 {
return nil, nil
}
apiInfos, err := i.apiService.ListInfoForServices(ctx, serviceIds...)
if err != nil {
return nil, err
}
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
wheres = append(wheres, monitor.MonWhereItem{
Key: "app",
Operation: "=",
Values: []string{subscriberId},
})
return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0)
}
func (i *imlMonitorStatisticModule) SubscriberStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) {
clusterId := cluster.DefaultClusterID
return i.statisticOnApi(ctx, clusterId, apiId, "app", input)
}
func (i *imlMonitorStatisticModule) MessageTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonMessageTrend, string, error) {
clusterId := cluster.DefaultClusterID
wheres, err := i.genCommonWheres(ctx, clusterId)
@@ -173,7 +610,7 @@ func (i *imlMonitorStatisticModule) TopAPIStatistics(ctx context.Context, limit
}
func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) {
func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
@@ -182,7 +619,7 @@ func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context,
return i.topProjectStatistics(ctx, clusterId, "app", input, limit)
}
func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) {
func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) {
clusterId := cluster.DefaultClusterID
_, err := i.clusterService.Get(ctx, clusterId)
if err != nil {
@@ -191,12 +628,12 @@ func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, l
return i.topProjectStatistics(ctx, clusterId, "provider", input, limit)
}
func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, partitionId string, groupBy string, input *monitor_dto.CommonInput, limit int) ([]*monitor_dto.ProjectStatisticItem, error) {
wheres, err := i.genCommonWheres(ctx, partitionId)
func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, clusterId string, groupBy string, input *monitor_dto.CommonInput, limit int) ([]*monitor_dto.ServiceStatisticItem, error) {
wheres, err := i.genCommonWheres(ctx, clusterId)
if err != nil {
return nil, err
}
statisticMap, err := i.statistics(ctx, partitionId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, limit)
statisticMap, err := i.statistics(ctx, clusterId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, limit)
if err != nil {
return nil, err
}
@@ -216,10 +653,10 @@ func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, pa
return t.Id
})
result := make([]*monitor_dto.ProjectStatisticItem, 0, len(statisticMap))
result := make([]*monitor_dto.ServiceStatisticItem, 0, len(statisticMap))
for key, item := range statisticMap {
statisticItem := &monitor_dto.ProjectStatisticItem{
ProjectStatisticBasicItem: &monitor_dto.ProjectStatisticBasicItem{
statisticItem := &monitor_dto.ServiceStatisticItem{
ServiceStatisticBasicItem: &monitor_dto.ServiceStatisticBasicItem{
Id: key,
MonCommonData: monitor_dto.ToMonCommonData(item),
},
@@ -407,7 +844,6 @@ func (m *imlMonitorConfig) GetMonitorConfig(ctx context.Context) (*monitor_dto.M
Driver: info.Driver,
Config: cfg,
}, nil
return nil, nil
}
func (m *imlMonitorConfig) GetMonitorCluster(ctx context.Context) ([]*monitor_dto.MonitorCluster, error) {
+22 -4
View File
@@ -13,18 +13,36 @@ import (
type IMonitorStatisticModule interface {
TopAPIStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ApiStatisticItem, error)
TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error)
TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error)
TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error)
TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error)
// RequestSummary 请求概况
RequestSummary(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, error)
// ProxySummary 转发概况
ProxySummary(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, error)
// InvokeTrend 调用次数趋势
InvokeTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
// MessageTrend 消息趋势
MessageTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonMessageTrend, string, error)
ApiStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error)
SubscriberStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error)
ProviderStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error)
APITrend(ctx context.Context, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
ProviderTrend(ctx context.Context, providerId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
SubscriberTrend(ctx context.Context, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
InvokeTrendWithSubscriberAndApi(ctx context.Context, apiId string, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
InvokeTrendWithProviderAndApi(ctx context.Context, providerId string, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
ProviderStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error)
ApiStatisticsOnProvider(ctx context.Context, providerId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error)
ApiStatisticsOnSubscriber(ctx context.Context, subscriberId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error)
SubscriberStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error)
}
type IMonitorConfigModule interface {
+93 -128
View File
@@ -2,10 +2,18 @@ package publish
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
strategy_driver "github.com/APIParkLab/APIPark/module/strategy/driver"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/eolinker/eosc"
"github.com/APIParkLab/APIPark/service/strategy"
"github.com/eolinker/go-common/store"
"github.com/APIParkLab/APIPark/service/service"
@@ -43,6 +51,7 @@ type imlPublishModule struct {
publishService publish.IPublishService `autowired:""`
apiService api.IAPIService `autowired:""`
upstreamService upstream.IUpstreamService `autowired:""`
strategyService strategy.IStrategyService `autowired:""`
releaseService release.IReleaseService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
serviceService service.IServiceService `autowired:""`
@@ -59,7 +68,7 @@ func (m *imlPublishModule) initGateway(ctx context.Context, partitionId string,
return p.Id
})
for _, projectId := range projectIds {
releaseInfo, err := m.getProjectRelease(ctx, projectId, partitionId)
releaseInfo, err := m.GetProjectRelease(ctx, projectId, partitionId)
if err != nil {
return err
}
@@ -75,22 +84,15 @@ func (m *imlPublishModule) initGateway(ctx context.Context, partitionId string,
return nil
}
func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID string, partitionId string) (*gateway.ProjectRelease, error) {
releaseInfo, err := m.releaseService.GetRunning(ctx, projectID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
return nil, nil
}
commits, err := m.releaseService.GetCommits(ctx, releaseInfo.UUID)
func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID string, commitId string) (*gateway.ProjectRelease, error) {
commits, err := m.releaseService.GetCommits(ctx, commitId)
if err != nil {
return nil, err
}
apiIds := make([]string, 0, len(commits))
apiProxyCommitIds := make([]string, 0, len(commits))
upstreamCommitIds := make([]string, 0, len(commits))
strategyCommitIds := make([]string, 0, len(commits))
for _, c := range commits {
switch c.Type {
case release.CommitApiProxy:
@@ -98,6 +100,8 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
apiProxyCommitIds = append(apiProxyCommitIds, c.Commit)
case release.CommitUpstream:
upstreamCommitIds = append(upstreamCommitIds, c.Commit)
case release.CommitStrategy:
strategyCommitIds = append(strategyCommitIds, c.Commit)
}
}
@@ -114,99 +118,11 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
return c.Target, c.Data
})
upstreamCommits, err := m.upstreamService.ListCommit(ctx, upstreamCommitIds...)
if err != nil {
return nil, err
version := commitId
r := &gateway.ProjectRelease{
Id: projectID,
Version: version,
}
version := releaseInfo.UUID
apis := make([]*gateway.ApiRelease, 0, len(apiInfos))
for _, a := range apiInfos {
apiInfo := &gateway.ApiRelease{
BasicItem: &gateway.BasicItem{
ID: a.UUID,
Description: a.Description,
Version: version,
},
Path: a.Path,
Methods: a.Methods,
Service: a.Service,
}
proxy, ok := proxyCommitMap[a.UUID]
if ok {
apiInfo.ProxyPath = proxy.Path
apiInfo.ProxyHeaders = utils.SliceToSlice(proxy.Headers, func(h *api.Header) *gateway.ProxyHeader {
return &gateway.ProxyHeader{
Key: h.Key,
Value: h.Value,
}
})
apiInfo.Retry = proxy.Retry
apiInfo.Timeout = proxy.Timeout
}
apis = append(apis, apiInfo)
}
var upstreamRelease *gateway.UpstreamRelease
for _, c := range upstreamCommits {
if c.Key != partitionId {
continue
}
upstreamRelease = &gateway.UpstreamRelease{
BasicItem: &gateway.BasicItem{
ID: c.Target,
Version: version,
MatchLabels: map[string]string{
"serviceId": projectID,
},
},
PassHost: c.Data.PassHost,
Scheme: c.Data.Scheme,
Balance: c.Data.Balance,
Timeout: c.Data.Timeout,
Nodes: utils.SliceToSlice(c.Data.Nodes, func(n *upstream.NodeConfig) string {
return fmt.Sprintf("%s weight=%d", n.Address, n.Weight)
}),
}
}
return &gateway.ProjectRelease{
Id: projectID,
Version: version,
Apis: apis,
Upstream: upstreamRelease,
}, nil
}
func (m *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releaseId, version string, clusterIds []string) (map[string]*gateway.ProjectRelease, error) {
commits, err := m.releaseService.GetCommits(ctx, releaseId)
if err != nil {
return nil, err
}
apiIds := make([]string, 0, len(commits))
apiProxyCommitIds := make([]string, 0, len(commits))
upstreamCommitIds := make([]string, 0, len(commits))
for _, c := range commits {
switch c.Type {
case release.CommitApiProxy:
apiIds = append(apiIds, c.Target)
apiProxyCommitIds = append(apiProxyCommitIds, c.Commit)
case release.CommitUpstream:
upstreamCommitIds = append(upstreamCommitIds, c.Commit)
}
}
apiInfos, err := m.apiService.ListInfo(ctx, apiIds...)
if err != nil {
return nil, err
}
proxyCommits, err := m.apiService.ListProxyCommit(ctx, apiProxyCommitIds...)
if err != nil {
return nil, err
}
proxyCommitMap := utils.SliceToMapO(proxyCommits, func(c *commit.Commit[api.Proxy]) (string, *api.Proxy) {
return c.Target, c.Data
})
apis := make([]*gateway.ApiRelease, 0, len(apiInfos))
for _, a := range apiInfos {
apiInfo := &gateway.ApiRelease{
@@ -240,44 +156,93 @@ func (m *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releas
}
apis = append(apis, apiInfo)
}
projectReleaseMap := make(map[string]*gateway.ProjectRelease)
upstreamReleaseMap := make(map[string]*gateway.UpstreamRelease)
r.Apis = apis
var upstreamRelease *gateway.UpstreamRelease
if len(upstreamCommitIds) > 0 {
upstreamCommits, err := m.upstreamService.ListCommit(ctx, upstreamCommitIds...)
if err != nil {
return nil, err
}
for _, c := range upstreamCommits {
for _, partitionId := range clusterIds {
upstreamRelease := &gateway.UpstreamRelease{
BasicItem: &gateway.BasicItem{
ID: c.Target,
Version: version,
MatchLabels: map[string]string{
"serviceId": projectID,
},
upstreamRelease = &gateway.UpstreamRelease{
BasicItem: &gateway.BasicItem{
ID: c.Target,
Version: version,
MatchLabels: map[string]string{
"serviceId": projectID,
},
PassHost: c.Data.PassHost,
Scheme: c.Data.Scheme,
Balance: c.Data.Balance,
Timeout: c.Data.Timeout,
Nodes: utils.SliceToSlice(c.Data.Nodes, func(n *upstream.NodeConfig) string {
return fmt.Sprintf("%s weight=%d", n.Address, n.Weight)
}),
}
upstreamReleaseMap[partitionId] = upstreamRelease
},
PassHost: c.Data.PassHost,
Scheme: c.Data.Scheme,
Balance: c.Data.Balance,
Timeout: c.Data.Timeout,
Nodes: utils.SliceToSlice(c.Data.Nodes, func(n *upstream.NodeConfig) string {
return fmt.Sprintf("%s weight=%d", n.Address, n.Weight)
}),
}
}
r.Upstream = upstreamRelease
}
if len(strategyCommitIds) > 0 {
strategyCommits, err := m.strategyService.ListStrategyCommit(ctx, strategyCommitIds...)
if err != nil {
return nil, err
}
strategyReleases := make([]*eosc.Base[gateway.StrategyRelease], 0, len(strategyCommits))
for _, c := range strategyCommits {
s := c.Data
driver, has := strategy_driver.GetDriver(c.Data.Driver)
if !has {
continue
}
filters := make([]*strategy_dto.Filter, 0)
json.Unmarshal([]byte(s.Filters), &filters)
var cfg interface{}
json.Unmarshal([]byte(s.Config), &cfg)
strategyReleases = append(strategyReleases, driver.ToRelease(&strategy_dto.Strategy{
Id: fmt.Sprintf("%s", s.Id),
Name: s.Name,
Priority: s.Priority,
Filters: filters,
Config: cfg,
IsDelete: s.IsDelete || s.IsStop,
}, map[string][]string{
"provider": {projectID},
}, 0))
}
r.Strategies = strategyReleases
}
return r, nil
}
func (m *imlPublishModule) GetProjectRelease(ctx context.Context, projectID string, partitionId string) (*gateway.ProjectRelease, error) {
releaseInfo, err := m.releaseService.GetRunning(ctx, projectID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
return nil, nil
}
return m.getProjectRelease(ctx, projectID, releaseInfo.UUID)
}
func (m *imlPublishModule) getReleaseInfo(ctx context.Context, projectID, releaseId, version string, clusterIds []string) (map[string]*gateway.ProjectRelease, error) {
projectRelease, err := m.getProjectRelease(ctx, projectID, releaseId)
if err != nil {
return nil, err
}
projectReleaseMap := make(map[string]*gateway.ProjectRelease)
for _, clusterId := range clusterIds {
projectReleaseMap[clusterId] = &gateway.ProjectRelease{
Id: projectID,
Version: version,
Apis: apis,
Upstream: upstreamReleaseMap[clusterId],
Id: projectID,
Version: version,
Apis: projectRelease.Apis,
Upstream: projectRelease.Upstream,
Strategies: projectRelease.Strategies,
}
}
return projectReleaseMap, nil
+34 -3
View File
@@ -5,6 +5,10 @@ import (
"errors"
"fmt"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/APIParkLab/APIPark/service/strategy"
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
service_doc "github.com/APIParkLab/APIPark/service/service-doc"
@@ -40,11 +44,26 @@ type imlReleaseModule struct {
serviceDocService service_doc.IDocService `autowired:""`
upstreamService upstream.IUpstreamService `autowired:""`
publishService publish.IPublishService `autowired:""`
strategyService strategy.IStrategyService `autowired:""`
transaction store.ITransaction `autowired:""`
projectService service.IServiceService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
}
func (m *imlReleaseModule) latestStrategyCommits(ctx context.Context, serviceId string) ([]*commit.Commit[strategy.Commit], error) {
list, err := m.strategyService.All(ctx, 2, serviceId)
if err != nil {
return nil, fmt.Errorf("get latest strategy failed:%w", err)
}
for _, s := range list {
err = m.strategyService.CommitStrategy(ctx, strategy_dto.ScopeService, serviceId, s.Id, s)
if err != nil {
return nil, err
}
}
return m.strategyService.ListLatestStrategyCommit(ctx, strategy_dto.ScopeService, serviceId)
}
func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *dto.CreateInput) (string, error) {
proInfo, err := m.projectService.Check(ctx, serviceId, projectRuleMustServer)
@@ -83,7 +102,7 @@ func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *
return "", errors.New("api or document not found")
}
upstreams, err := m.upstreamService.ListLatestCommit(ctx, serviceId)
upstreams, err := m.upstreamService.ListLatestCommit(ctx, cluster.DefaultClusterID, serviceId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return "", errors.New("api config or document not found")
@@ -126,6 +145,9 @@ func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *
doc, err := m.apiDocService.GetDoc(ctx, serviceId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("api doc not found")
}
return err
}
err = m.apiDocService.CommitDoc(ctx, serviceId, doc)
@@ -151,8 +173,17 @@ func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *
}
serviceDocCommit, err := m.serviceDocService.LatestDocCommit(ctx, serviceId)
if err != nil {
return err
}
strategies, err := m.latestStrategyCommits(ctx, serviceId)
if err != nil {
return err
}
strategyCommits := utils.SliceToMapO(strategies, func(c *commit.Commit[strategy.Commit]) (string, string) {
return c.Target, c.UUID
})
if !m.releaseService.Completeness(utils.SliceToSlice(clusters, func(s *cluster.Cluster) string {
return s.Uuid
}), apiUUIDS, requestCommits, apiProxy, upstreams) {
@@ -161,7 +192,7 @@ func (m *imlReleaseModule) Create(ctx context.Context, serviceId string, input *
requestCommitMap := utils.SliceToMapO(requestCommits, func(c *commit.Commit[api.Request]) (string, string) {
return c.Target, c.UUID
})
newRelease, err = m.releaseService.CreateRelease(ctx, serviceId, input.Version, input.Remark, requestCommitMap, apiProxyCommits, docCommit.UUID, serviceDocCommit.UUID, upstreamCommitsForUKC)
newRelease, err = m.releaseService.CreateRelease(ctx, serviceId, input.Version, input.Remark, requestCommitMap, apiProxyCommits, docCommit.UUID, serviceDocCommit.UUID, upstreamCommitsForUKC, strategyCommits)
return err
})
if err != nil {
+4
View File
@@ -102,3 +102,7 @@ type ListInput struct {
type UpdateDoc struct {
Content string `json:"content"`
}
type InputSimpleAPI struct {
Services []string `json:"services"`
}
+1
View File
@@ -26,6 +26,7 @@ type Item struct {
type SimpleItem struct {
Id string `json:"id"`
Methods []string `json:"methods"`
Name string `json:"name"`
Path string `json:"request_path"`
}
+112
View File
@@ -0,0 +1,112 @@
package router
import (
"context"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
"github.com/eolinker/go-common/utils"
"github.com/eolinker/eosc/log"
"github.com/APIParkLab/APIPark/service/api"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
)
var _ strategy_filter.IRemoteFilter = (*imlRouterFilter)(nil)
type imlRouterFilter struct {
service api.IAPIService `autowired:""`
}
func (i *imlRouterFilter) Name() string {
return "api"
}
func (i *imlRouterFilter) Title() string {
return "API"
}
func (i *imlRouterFilter) Labels(values ...string) []string {
list, err := i.service.ListInfo(context.Background(), values...)
if err != nil {
log.Errorf("get api labels error: %v", err)
return nil
}
return utils.SliceToSlice(list, func(a *api.Info) string {
return a.Name
})
}
func (i *imlRouterFilter) Type() string {
return strategy_filter.TypeRemote
}
func (i *imlRouterFilter) Scopes() []string {
return []string{
//strategy_filter.ScopeGlobal,
strategy_filter.ScopeService,
}
}
func (i *imlRouterFilter) Option() *strategy_filter.Option {
return &strategy_filter.Option{
Name: i.Name(),
Title: i.Title(),
Type: i.Type(),
}
}
func (i *imlRouterFilter) Titles() []strategy_filter.OptionTitle {
return []strategy_filter.OptionTitle{
{
Field: "name",
Title: "api name",
},
{
Field: "methods",
Title: "methods",
},
{
Field: "request_path",
Title: "request path",
},
}
}
func (i *imlRouterFilter) Key() string {
return "id"
}
func (i *imlRouterFilter) Target() string {
return "list"
}
func (i *imlRouterFilter) RemoteList(ctx context.Context, keyword string, condition map[string]interface{}, page int, pageSize int) ([]any, int64, error) {
if pageSize == -1 {
// 获取全部
list, err := i.service.Search(ctx, keyword, condition)
if err != nil {
return nil, 0, err
}
return utils.SliceToSlice(list, func(s *api.API) any {
return &router_dto.SimpleItem{
Id: s.UUID,
Path: s.Path,
Methods: s.Method,
}
}), int64(len(list)), nil
}
list, total, err := i.service.SearchByPage(ctx, keyword, condition, page, pageSize, "update_at")
if err != nil {
return nil, 0, err
}
return utils.SliceToSlice(list, func(s *api.API) any {
return &router_dto.SimpleItem{
Id: s.UUID,
Path: s.Path,
Methods: s.Method,
}
}), total, nil
}
+22
View File
@@ -46,6 +46,28 @@ type imlRouterModule struct {
transaction store.ITransaction `autowired:""`
}
func (i *imlRouterModule) SimpleAPIs(ctx context.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error) {
list, err := i.apiService.ListForServices(ctx, input.Services...)
if err != nil {
return nil, err
}
apiInfos, err := i.apiService.ListInfo(ctx, utils.SliceToSlice(list, func(s *api.API) string {
return s.UUID
})...)
if err != nil {
return nil, err
}
return utils.SliceToSlice(apiInfos, func(item *api.Info) *router_dto.SimpleItem {
return &router_dto.SimpleItem{
Id: item.UUID,
Name: item.Name,
Methods: item.Methods,
Path: item.Path,
}
}), nil
}
func (i *imlRouterModule) ExportAll(ctx context.Context) ([]*router_dto.Export, error) {
apiList, err := i.apiService.ListInfo(ctx)
+8 -1
View File
@@ -2,9 +2,11 @@ package router
import (
"context"
"github.com/APIParkLab/APIPark/module/system"
"reflect"
"github.com/APIParkLab/APIPark/module/system"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
"github.com/eolinker/go-common/autowire"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
@@ -29,6 +31,7 @@ type IRouterModule interface {
// Prefix 获取API前缀
Prefix(ctx context.Context, serviceId string) (string, error)
SimpleAPIs(ctx context.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error)
//ExportAll(ctx context.Context) ([]*router_dto.Export, error)
}
@@ -45,4 +48,8 @@ func init() {
autowire.Auto[IExportRouterModule](func() reflect.Value {
return reflect.ValueOf(apiModule)
})
filter := new(imlRouterFilter)
autowire.Autowired(filter)
strategy_filter.RegisterRemoteFilter(filter)
}
+119 -21
View File
@@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"github.com/APIParkLab/APIPark/service/strategy"
"github.com/APIParkLab/APIPark/service/service"
"github.com/APIParkLab/APIPark/service/api"
@@ -23,6 +25,7 @@ type imlServiceDiff struct {
apiDocService api_doc.IAPIDocService `autowired:""`
upstreamService upstream.IUpstreamService `autowired:""`
releaseService release.IReleaseService `autowired:""`
strategyService strategy.IStrategyService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
}
@@ -79,6 +82,34 @@ func (m *imlServiceDiff) getBaseInfo(ctx context.Context, serviceId, baseRelease
return base, nil
}
func (m *imlServiceDiff) latestStrategyCommits(ctx context.Context, serviceId string) ([]*commit.Commit[strategy.Commit], error) {
list, err := m.strategyService.All(ctx, 2, serviceId)
if err != nil {
return nil, fmt.Errorf("get latest strategy failed:%w", err)
}
return utils.SliceToSlice(list, func(s *strategy.Strategy) *commit.Commit[strategy.Commit] {
key := fmt.Sprintf("service-%s", s.Id)
return &commit.Commit[strategy.Commit]{
Target: s.Id,
Key: key,
Data: &strategy.Commit{
Id: s.Id,
Name: s.Name,
Priority: s.Priority,
Filters: s.Filters,
Config: s.Config,
Driver: s.Driver,
IsStop: s.IsStop,
Version: s.UpdateAt.Format("20060102150405"),
},
}
}, func(s *strategy.Strategy) bool {
return !s.IsDelete
}), nil
}
func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, baseRelease string) (*service_diff.Diff, bool, error) {
serviceInfo, err := m.serviceService.Get(ctx, serviceId)
if err != nil {
@@ -88,6 +119,9 @@ func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, ba
if err != nil {
return nil, false, err
}
if len(apis) < 1 {
return nil, false, fmt.Errorf("api not found")
}
apiIds := utils.SliceToSlice(apis, func(i *api.API) string {
return i.UUID
@@ -122,7 +156,7 @@ func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, ba
return nil, false, err
}
upstreamCommits, err := m.upstreamService.ListLatestCommit(ctx, serviceId)
upstreamCommits, err := m.upstreamService.ListLatestCommit(ctx, cluster.DefaultClusterID, serviceId)
if err != nil {
return nil, false, err
}
@@ -130,6 +164,11 @@ func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, ba
return nil, false, fmt.Errorf("upstream not found")
}
strategyCommits, err := m.latestStrategyCommits(ctx, serviceId)
if err != nil {
return nil, false, err
}
base, err := m.getBaseInfo(ctx, serviceId, baseRelease)
if err != nil {
return nil, false, err
@@ -140,6 +179,7 @@ func (m *imlServiceDiff) DiffForLatest(ctx context.Context, serviceId string, ba
apiProxyCommits: proxy,
apiDocCommits: apiDocCommits,
upstreamCommits: upstreamCommits,
strategyCommits: strategyCommits,
}
clusters, err := m.clusterService.List(ctx)
if err != nil {
@@ -155,26 +195,26 @@ func (m *imlServiceDiff) getReleaseInfo(ctx context.Context, releaseId string) (
if err != nil {
return nil, err
}
apiRequestCommitIds := utils.SliceToSlice(commits, func(i *release.ProjectCommits) string {
return i.Target
}, func(c *release.ProjectCommits) bool {
return c.Type == release.CommitApiRequest
})
apiProxyCommitIds := utils.SliceToSlice(commits, func(i *release.ProjectCommits) string {
return i.Commit
}, func(c *release.ProjectCommits) bool {
return c.Type == release.CommitApiProxy
})
apiDocumentCommitIds := utils.SliceToSlice(commits, func(i *release.ProjectCommits) string {
return i.Commit
}, func(c *release.ProjectCommits) bool {
return c.Type == release.CommitApiDocument
})
upstreamCommitIds := utils.SliceToSlice(commits, func(i *release.ProjectCommits) string {
return i.Commit
}, func(c *release.ProjectCommits) bool {
return c.Type == release.CommitUpstream
})
apiRequestCommitIds := make([]string, 0)
apiProxyCommitIds := make([]string, 0)
apiDocumentCommitIds := make([]string, 0)
upstreamCommitIds := make([]string, 0)
strategyCommitIds := make([]string, 0)
for _, c := range commits {
switch c.Type {
case release.CommitApiRequest:
apiRequestCommitIds = append(apiRequestCommitIds, c.Commit)
case release.CommitApiProxy:
apiProxyCommitIds = append(apiProxyCommitIds, c.Commit)
case release.CommitApiDocument:
apiDocumentCommitIds = append(apiDocumentCommitIds, c.Commit)
case release.CommitUpstream:
upstreamCommitIds = append(upstreamCommitIds, c.Commit)
case release.CommitStrategy:
strategyCommitIds = append(strategyCommitIds, c.Commit)
}
}
var requestCommits []*commit.Commit[api.Request]
var proxyCommits []*commit.Commit[api.Proxy]
var documentCommits []*commit.Commit[api_doc.DocCommit]
@@ -203,14 +243,63 @@ func (m *imlServiceDiff) getReleaseInfo(ctx context.Context, releaseId string) (
return nil, err
}
}
var strategyCommits []*commit.Commit[strategy.Commit]
if len(strategyCommitIds) > 0 {
strategyCommits, err = m.strategyService.ListStrategyCommit(ctx, strategyCommitIds...)
if err != nil {
return nil, err
}
}
return &projectInfo{
apiRequestCommits: requestCommits,
apiProxyCommits: proxyCommits,
apiDocCommits: documentCommits,
upstreamCommits: upstreamCommits,
strategyCommits: strategyCommits,
}, nil
}
func (m *imlServiceDiff) diffStrategies(base, target []*commit.Commit[strategy.Commit]) []*service_diff.StrategyDiff {
baseStrategy := utils.SliceToMap(base, func(i *commit.Commit[strategy.Commit]) string {
return i.Target
})
targetStrategy := utils.SliceToMap(target, func(i *commit.Commit[strategy.Commit]) string {
return i.Target
})
out := make([]*service_diff.StrategyDiff, 0, len(target))
for _, tc := range targetStrategy {
//t := tc.Data
key := tc.Target
o := &service_diff.StrategyDiff{
Strategy: key,
Name: tc.Data.Name,
Priority: tc.Data.Priority,
Change: service_diff.ChangeTypeNone,
Status: 0,
}
b, hasB := baseStrategy[key]
if !hasB {
o.Change = service_diff.ChangeTypeNew
} else if tc.Data.Version != b.Data.Version {
o.Change = service_diff.ChangeTypeUpdate
}
delete(baseStrategy, key)
out = append(out, o)
}
for _, b := range baseStrategy {
o := &service_diff.StrategyDiff{
Strategy: b.Target,
Name: b.Data.Name,
Priority: b.Data.Priority,
Change: service_diff.ChangeTypeDelete,
Status: 0,
}
out = append(out, o)
}
return out
}
func (m *imlServiceDiff) diff(partitions []string, base, target *projectInfo) *service_diff.Diff {
out := &service_diff.Diff{
Apis: nil,
@@ -328,6 +417,7 @@ func (m *imlServiceDiff) diff(partitions []string, base, target *projectInfo) *s
}
}
}
out.Strategies = m.diffStrategies(base.strategyCommits, target.strategyCommits)
return out
}
@@ -373,5 +463,13 @@ func (m *imlServiceDiff) Out(ctx context.Context, diff *service_diff.Diff) (*Dif
}),
})
}
out.Strategies = utils.SliceToSlice(diff.Strategies, func(i *service_diff.StrategyDiff) *StrategyDiffOut {
return &StrategyDiffOut{
Name: i.Name,
Priority: i.Priority,
Change: i.Change,
Status: i.Status,
}
})
return out, nil
}
+12 -2
View File
@@ -4,13 +4,15 @@ import (
"github.com/APIParkLab/APIPark/service/api"
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
"github.com/APIParkLab/APIPark/service/service_diff"
"github.com/APIParkLab/APIPark/service/strategy"
"github.com/APIParkLab/APIPark/service/universally/commit"
"github.com/APIParkLab/APIPark/service/upstream"
)
type DiffOut struct {
Routers []*RouterDiffOut `json:"routers"`
Upstreams []*UpstreamDiffOut `json:"upstreams"`
Routers []*RouterDiffOut `json:"routers"`
Upstreams []*UpstreamDiffOut `json:"upstreams"`
Strategies []*StrategyDiffOut `json:"strategies"`
}
type RouterDiffOut struct {
@@ -30,6 +32,13 @@ type UpstreamDiffOut struct {
Addr []string `json:"addr,omitempty"`
}
type StrategyDiffOut struct {
Name string `json:"name"`
Priority int `json:"priority"`
Change service_diff.ChangeType `json:"change,omitempty"`
Status service_diff.StatusType `json:"status,omitempty"`
}
type projectInfo struct {
id string
//apis []*api.Info
@@ -37,4 +46,5 @@ type projectInfo struct {
apiProxyCommits []*commit.Commit[api.Proxy]
apiDocCommits []*commit.Commit[api_doc.DocCommit]
upstreamCommits []*commit.Commit[upstream.Config]
strategyCommits []*commit.Commit[strategy.Commit]
}
+146
View File
@@ -0,0 +1,146 @@
package service
import (
"context"
"github.com/APIParkLab/APIPark/service/subscribe"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/eolinker/go-common/auto"
"github.com/APIParkLab/APIPark/service/service"
"github.com/eolinker/eosc/log"
"github.com/eolinker/go-common/utils"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
)
var _ strategy_filter.IRemoteFilter = (*imlAppFilter)(nil)
type imlAppFilter struct {
service service.IServiceService `autowired:""`
subscriberService subscribe.ISubscribeService `autowired:""`
}
func (i *imlAppFilter) Name() string {
return "application"
}
func (i *imlAppFilter) Title() string {
return "consumer"
}
func (i *imlAppFilter) Labels(values ...string) []string {
if len(values) == 0 {
return nil
}
if values[0] == strategy_filter.ValuesALL {
return []string{
"all consumer",
}
}
apps, err := i.service.AppList(context.Background(), values...)
if err != nil {
log.Error(err)
return nil
}
return utils.SliceToSlice(apps, func(a *service.Service) string {
return a.Name
})
}
func (i *imlAppFilter) Type() string {
return strategy_filter.TypeRemote
}
func (i *imlAppFilter) Scopes() []string {
return []string{
strategy_filter.ScopeGlobal,
strategy_filter.ScopeService,
}
}
func (i *imlAppFilter) Option() *strategy_filter.Option {
return &strategy_filter.Option{
Name: i.Name(),
Title: i.Title(),
Type: i.Type(),
}
}
func (i *imlAppFilter) Titles() []strategy_filter.OptionTitle {
return []strategy_filter.OptionTitle{
{
Field: "name",
Title: "consumer",
},
{
Field: "id",
Title: "consumer id",
},
{
Field: "description",
Title: "description",
},
}
}
func (i *imlAppFilter) Key() string {
return "id"
}
func (i *imlAppFilter) Target() string {
return "list"
}
func (i *imlAppFilter) RemoteList(ctx context.Context, keyword string, condition map[string]interface{}, page int, pageSize int) ([]any, int64, error) {
if condition == nil {
condition = make(map[string]interface{})
}
if serviceId, ok := condition["service"]; ok {
// 查询订阅了该服务的消费者
v, ok := serviceId.(string)
if ok {
subscribers, err := i.subscriberService.Subscribers(ctx, v, subscribe.ApplyStatusSubscribe)
if err != nil {
return nil, 0, err
}
if len(subscribers) > 0 {
appIds := utils.SliceToSlice(subscribers, func(s *subscribe.Subscribe) string {
return s.Application
})
condition["uuid"] = appIds
}
}
delete(condition, "service")
}
condition["as_app"] = true
if pageSize == -1 {
// 获取全部
list, err := i.service.Search(ctx, keyword, condition, "update_at")
if err != nil {
return nil, 0, err
}
return utils.SliceToSlice(list, func(s *service.Service) any {
return &service_dto.SimpleAppItem{
Id: s.Id,
Name: s.Name,
Team: auto.UUID(s.Team),
Description: s.Description,
}
}), int64(len(list)), nil
}
list, total, err := i.service.SearchByPage(ctx, keyword, condition, page, pageSize, "update_at")
if err != nil {
return nil, 0, err
}
return utils.SliceToSlice(list, func(s *service.Service) any {
return &service_dto.SimpleAppItem{
Id: s.Id,
Name: s.Name,
Team: auto.UUID(s.Team),
Description: s.Description,
}
}), total, nil
}
+43 -45
View File
@@ -109,9 +109,7 @@ func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.Export
Catalogue: s.Catalogue,
Logo: s.Logo,
}
//if v, ok := docMap[s.Id]; ok {
// info.Doc = v.Doc
//}
if tags, ok := serviceTagMap[s.Id]; ok {
info.Tags = tags
}
@@ -176,7 +174,7 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
//func (i *imlServiceModule) SimpleAPPS(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// w := make(map[string]interface{})
// w["as_app"] = true
// services, err := i.serviceService.Search(ctx, keyword, w)
// services, err := i.serviceService.SearchByDriver(ctx, keyword, w)
// if err != nil {
// return nil, err
// }
@@ -191,47 +189,47 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
// }), nil
//}
//func (i *imlServiceModule) Simple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// w := make(map[string]interface{})
// w["as_server"] = true
//
// services, err := i.serviceService.Search(ctx, keyword, w)
// if err != nil {
// return nil, err
// }
//
// items := make([]*service_dto.SimpleServiceItem, 0, len(services))
// for _, p := range services {
//
// items = append(items, &service_dto.SimpleServiceItem{
// Id: p.Id,
// Name: p.Name,
// Description: p.Description,
// Team: auto.UUID(p.Team),
// })
// }
// return items, nil
//}
//
//func (i *imlServiceModule) MySimple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
// services, err := i.searchMyServices(ctx, "", keyword)
//
// if err != nil {
// return nil, err
// }
//
// items := make([]*service_dto.SimpleServiceItem, 0, len(services))
// for _, p := range services {
//
// items = append(items, &service_dto.SimpleServiceItem{
// Id: p.Id,
// Name: p.Name,
// Description: p.Description,
// Team: auto.UUID(p.Team),
// })
// }
// return items, nil
//}
func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
w := make(map[string]interface{})
w["as_server"] = true
services, err := i.serviceService.Search(ctx, "", w)
if err != nil {
return nil, err
}
items := make([]*service_dto.SimpleServiceItem, 0, len(services))
for _, p := range services {
items = append(items, &service_dto.SimpleServiceItem{
Id: p.Id,
Name: p.Name,
Description: p.Description,
Team: auto.UUID(p.Team),
})
}
return items, nil
}
func (i *imlServiceModule) MySimple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
services, err := i.searchMyServices(ctx, "", "")
if err != nil {
return nil, err
}
items := make([]*service_dto.SimpleServiceItem, 0, len(services))
for _, p := range services {
items = append(items, &service_dto.SimpleServiceItem{
Id: p.Id,
Name: p.Name,
Description: p.Description,
Team: auto.UUID(p.Team),
})
}
return items, nil
}
func (i *imlServiceModule) Get(ctx context.Context, id string) (*service_dto.Service, error) {
now := time.Now()
+10 -4
View File
@@ -4,6 +4,8 @@ import (
"context"
"reflect"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
"github.com/APIParkLab/APIPark/module/system"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
@@ -25,11 +27,11 @@ type IServiceModule interface {
// Delete 删除项目
Delete(ctx context.Context, id string) error
// Simple 获取简易项目列表
//Simple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error)
//Simple 获取简易项目列表
Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error)
// MySimple 获取我的简易项目列表
//MySimple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error)
//MySimple 获取我的简易项目列表
MySimple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error)
}
type IServiceDocModule interface {
@@ -83,4 +85,8 @@ func init() {
return reflect.ValueOf(serviceDocModule)
})
filter := new(imlAppFilter)
autowire.Autowired(filter)
strategy_filter.RegisterRemoteFilter(filter)
}
@@ -0,0 +1,22 @@
package data_masking
type Config struct {
Rules []*Rule `json:"rules"`
}
type Rule struct {
Match *BasicItem `json:"match"`
Mask *Mask `json:"mask"`
}
type BasicItem struct {
Type string `json:"type"`
Value string `json:"value"`
}
type Mask struct {
Type string `json:"type"`
Begin int `json:"begin"`
Length int `json:"length"`
Replace *BasicItem `json:"replace"`
}
@@ -0,0 +1,94 @@
package data_masking
import (
"encoding/json"
"errors"
"fmt"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/eosc"
strategy_driver "github.com/APIParkLab/APIPark/module/strategy/driver"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
)
func init() {
strategy_driver.Register(&strategyDriver{confName: "data_mask"})
}
type strategyDriver struct {
confName string
}
func (d *strategyDriver) Driver() string {
return "data-masking"
}
func (d *strategyDriver) ToRelease(s *strategy_dto.Strategy, labels map[string][]string, initStep int) *eosc.Base[gateway.StrategyRelease] {
filters := make(map[string][]string)
for _, f := range s.Filters {
filters[f.Name] = f.Values
}
for key, value := range labels {
filters[key] = value
}
base := eosc.NewBase[gateway.StrategyRelease]()
base.Config = &gateway.StrategyRelease{
Name: s.Id,
Desc: s.Name,
Driver: "data_mask",
Priority: initStep + s.Priority,
Filters: filters,
IsDelete: s.IsDelete,
}
cfg := make(map[string]interface{})
cfg[d.confName] = s.Config
base.Append = cfg
return base
}
func (d *strategyDriver) Check(config interface{}) error {
if config == nil {
return nil
}
data, err := json.Marshal(config)
if err != nil {
return err
}
var cfg Config
err = json.Unmarshal(data, &cfg)
if err != nil {
return err
}
for _, r := range cfg.Rules {
if r.Match == nil {
return errors.New("match can't be null. ")
}
if r.Mask == nil {
return errors.New("mask can't be null. ")
}
if _, ok := validMatchTypes[r.Match.Type]; !ok {
return fmt.Errorf("match type %s is illegal. ", r.Match.Type)
}
if r.Match.Type == "inner" {
if _, ok := validMatchInnerValues[r.Match.Value]; !ok {
return fmt.Errorf("match value %s is illegal. ", r.Match.Value)
}
}
if _, ok := validMaskTypes[r.Mask.Type]; !ok {
return fmt.Errorf("mask type %s is illegal. ", r.Mask.Type)
}
if r.Mask.Replace != nil {
if _, ok := validReplaceTypes[r.Mask.Replace.Type]; !ok {
return fmt.Errorf("replace type %s is illegal. ", r.Mask.Replace.Type)
}
}
}
return nil
}
@@ -0,0 +1,30 @@
package data_masking
var validMatchInnerValues = map[string]struct{}{
"name": {},
"phone": {},
"email": {},
"id-card": {},
"bank-card": {},
"date": {},
"amount": {},
}
var validMatchTypes = map[string]struct{}{
"inner": {},
"keyword": {},
"regex": {},
"json_path": {},
}
var validMaskTypes = map[string]struct{}{
"partial-display": {},
"partial-masking": {},
"truncation": {},
"replacement": {},
"shuffling": {},
}
var validReplaceTypes = map[string]struct{}{
"random": {},
"custom": {},
}
+13
View File
@@ -0,0 +1,13 @@
package strategy_driver
import (
"github.com/APIParkLab/APIPark/gateway"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/eolinker/eosc"
)
type IStrategyDriver interface {
Driver() string
ToRelease(s *strategy_dto.Strategy, labels map[string][]string, initStep int) *eosc.Base[gateway.StrategyRelease]
Check(config interface{}) error
}
+52
View File
@@ -0,0 +1,52 @@
package strategy_driver
import (
"fmt"
"github.com/eolinker/eosc"
)
var manager = newManager()
func newManager() *Manager {
return &Manager{
drivers: eosc.BuildUntyped[string, IStrategyDriver](),
}
}
type Manager struct {
drivers eosc.Untyped[string, IStrategyDriver]
}
func (m *Manager) AddDriver(driver IStrategyDriver) {
m.drivers.Set(driver.Driver(), driver)
}
func (m *Manager) GetDriver(driver string) (IStrategyDriver, bool) {
return m.drivers.Get(driver)
}
func (m *Manager) GetDrivers() []string {
return m.drivers.Keys()
}
func (m *Manager) Delete(name string) {
m.drivers.Del(name)
}
func GetDriver(name string) (IStrategyDriver, bool) {
return manager.GetDriver(name)
}
func Register(driver IStrategyDriver) {
manager.AddDriver(driver)
}
func CheckConfig(name string, config interface{}) error {
driver, has := manager.GetDriver(name)
if !has {
return fmt.Errorf("driver %s not found", name)
}
return driver.Check(config)
}
+44
View File
@@ -0,0 +1,44 @@
package strategy_dto
const (
ScopeGlobal = "global"
ScopeTeam = "team"
ScopeService = "service"
PublishStatusOnline = "online"
PublishStatusOffline = "offline"
PublishStatusUpdate = "update"
PublishStatusDelete = "delete"
)
type Scope int
func (s Scope) String() string {
switch s {
case 0:
return ScopeGlobal
case 1:
return ScopeTeam
case 2:
return ScopeService
default:
return ScopeGlobal
}
}
func (s Scope) Int() int {
return int(s)
}
func ToScope(s string) Scope {
switch s {
case ScopeGlobal:
return 0
case ScopeTeam:
return 1
case ScopeService:
return 2
default:
return 0
}
}
+29
View File
@@ -0,0 +1,29 @@
package strategy_dto
type Create struct {
Scope Scope `json:"-"`
Target string `json:"-"`
Driver string `json:"-"`
ID string `json:"id"`
Name string `json:"name"`
Priority int `json:"priority"`
Desc string `json:"desc"`
Filters []*Filter `json:"filters"`
Config interface{} `json:"config"`
}
type Edit struct {
Name *string `json:"name"`
Priority *int `json:"priority"`
Desc *string `json:"desc"`
Filters *[]*Filter `json:"filters"`
Config *interface{} `json:"config"`
}
type Filter struct {
Name string `json:"name"`
Values []string `json:"values"`
Type string `json:"type"`
Label string `json:"label"`
Title string `json:"title"`
}
+134
View File
@@ -0,0 +1,134 @@
package strategy_dto
import (
"encoding/json"
"time"
"github.com/APIParkLab/APIPark/service/strategy"
"github.com/eolinker/go-common/auto"
)
func StrategyStatus(s *strategy.Strategy, publishVersion string) string {
publishStatus := PublishStatusOffline
if publishVersion != "" {
if s.IsDelete {
publishStatus = PublishStatusDelete
} else {
version := s.UpdateAt.Format("20060102150405")
if version != publishVersion {
publishStatus = PublishStatusUpdate
} else {
publishStatus = PublishStatusOnline
}
}
}
return publishStatus
}
func ToStrategyItem(s *strategy.Strategy, publishVersion string, filters string, processedTotal int64) *StrategyItem {
publishStatus := PublishStatusOffline
if publishVersion != "" {
if s.IsDelete {
publishStatus = PublishStatusDelete
} else {
version := s.UpdateAt.Format("20060102150405")
if version != publishVersion {
publishStatus = PublishStatusUpdate
} else {
publishStatus = PublishStatusOnline
}
}
}
return &StrategyItem{
Id: s.Id,
Name: s.Name,
Priority: s.Priority,
Desc: s.Desc,
Filters: filters,
Updater: auto.UUID(s.Updater),
UpdateTime: auto.TimeLabel(s.UpdateAt),
ProcessedTotal: processedTotal,
PublishStatus: publishStatus,
IsStop: s.IsStop,
IsDelete: s.IsDelete,
}
}
func ToStrategy(s *strategy.Strategy) *Strategy {
filters := make([]*Filter, 0)
json.Unmarshal([]byte(s.Filters), &filters)
var cfg interface{}
json.Unmarshal([]byte(s.Config), &cfg)
return &Strategy{
Id: s.Id,
Name: s.Name,
Priority: s.Priority,
Desc: s.Desc,
Filters: filters,
Config: cfg,
IsDelete: s.IsDelete || s.IsStop,
}
}
type Strategy struct {
Id string `json:"id"`
Name string `json:"name"`
Priority int `json:"priority"`
Desc string `json:"desc"`
Filters []*Filter `json:"filters"`
Config interface{} `json:"config"`
IsDelete bool `json:"is_delete"`
}
type StrategyItem struct {
Id string `json:"id"`
Name string `json:"name"`
Priority int `json:"priority"`
Desc string `json:"desc"`
Filters string `json:"filters"`
Updater auto.Label `json:"updater" aolabel:"user"`
UpdateTime auto.TimeLabel `json:"update_time"`
ProcessedTotal int64 `json:"processed_total"`
PublishStatus string `json:"publish_status"`
IsStop bool `json:"is_stop"`
IsDelete bool `json:"is_delete"`
}
type FilterOption struct {
Name string `json:"name"`
Title string `json:"title"`
Type string `json:"type"`
Pattern string `json:"pattern"`
Options []string `json:"options"`
}
type Title struct {
Field string `json:"field"`
Title string `json:"title" aoi18n:""`
}
type ToPublishItem struct {
Name string `json:"name"`
Priority int `json:"priority"`
Status string `json:"status"`
OptTime time.Time `json:"opt_time"`
}
type LogItem struct {
ID string `json:"id"`
Service auto.Label `json:"service" aolabel:"service"`
Method string `json:"method"`
Url string `json:"url"`
RemoteIP string `json:"remote_ip"`
Consumer auto.Label `json:"consumer" aolabel:"service"`
Authorization auto.Label `json:"authorization" aolabel:"service_authorization"`
RecordTime auto.TimeLabel `json:"record_time"`
}
type LogInfo struct {
ID string `json:"id"`
ContentType string `json:"content_type"`
ProxyResponseBody string `json:"origin"`
ResponseBody string `json:"target"`
}
+394
View File
@@ -0,0 +1,394 @@
package strategy
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"time"
"github.com/APIParkLab/APIPark/service/service"
"github.com/eolinker/go-common/auto"
"github.com/APIParkLab/APIPark/service/cluster"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/eosc"
log2 "github.com/APIParkLab/APIPark/service/log"
"github.com/eolinker/eosc/log"
"gorm.io/gorm"
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
"github.com/eolinker/go-common/store"
"github.com/APIParkLab/APIPark/service/universally/commit"
"github.com/eolinker/go-common/utils"
"github.com/google/uuid"
strategy_driver "github.com/APIParkLab/APIPark/module/strategy/driver"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
"github.com/APIParkLab/APIPark/service/strategy"
)
var _ IStrategyModule = (*imlStrategyModule)(nil)
type imlStrategyModule struct {
strategyService strategy.IStrategyService `autowired:""`
appService service.IServiceService `autowired:""`
logService log2.ILogService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
transaction store.ITransaction `autowired:""`
}
func (i *imlStrategyModule) StrategyLogInfo(ctx context.Context, id string) (*strategy_dto.LogInfo, error) {
c, err := i.clusterService.Get(ctx, cluster.DefaultClusterID)
if err != nil {
return nil, fmt.Errorf("cluster %s not found", cluster.DefaultClusterID)
}
info, err := i.logService.LogInfo(ctx, "loki", c.Cluster, id)
if err != nil {
return nil, err
}
return &strategy_dto.LogInfo{
ID: info.ID,
ContentType: info.ContentType,
ProxyResponseBody: info.ProxyResponseBody,
ResponseBody: info.ResponseBody,
}, nil
}
func (i *imlStrategyModule) GetStrategyLogs(ctx context.Context, keyword string, strategyID string, start time.Time, end time.Time, limit int64, offset int64) ([]*strategy_dto.LogItem, int64, error) {
if strategyID == "" {
return nil, 0, errors.New("strategy id required")
}
conditions := map[string]string{
"strategy": strategyID,
}
if keyword != "" {
// 查询符合条件的应用ID
apps, err := i.appService.Search(ctx, keyword, map[string]interface{}{
"as_app": true,
})
if err != nil {
return nil, 0, err
}
orCondition := fmt.Sprintf("request_uri =~ \".*%s.*\"", keyword)
if len(apps) > 0 {
appIds := utils.SliceToSlice(apps, func(a *service.Service) string { return a.Id })
orCondition = fmt.Sprintf("%s or application =~ \"%s\"", orCondition, strings.Join(appIds, "|"))
}
conditions["#1"] = orCondition
}
c, err := i.clusterService.Get(ctx, cluster.DefaultClusterID)
if err != nil {
return nil, 0, fmt.Errorf("cluster %s not found", cluster.DefaultClusterID)
}
items, total, err := i.logService.Logs(ctx, "loki", c.Cluster, conditions, start, end, limit, offset)
if err != nil {
return nil, 0, err
}
result := make([]*strategy_dto.LogItem, 0, len(items))
for _, item := range items {
result = append(result, &strategy_dto.LogItem{
ID: item.ID,
Service: auto.UUID(item.Service),
Method: item.Method,
Url: item.Url,
RemoteIP: item.RemoteIP,
Consumer: auto.UUID(item.Consumer),
Authorization: auto.UUID(item.Authorization),
RecordTime: auto.TimeLabel(item.RecordTime),
})
}
return result, total, nil
}
func (i *imlStrategyModule) Restore(ctx context.Context, id string) error {
return i.strategyService.Restore(ctx, id)
}
func (i *imlStrategyModule) DeleteServiceStrategy(ctx context.Context, serviceId string, id string) error {
_, err := i.strategyService.LatestStrategyCommit(ctx, strategy_dto.ScopeService, serviceId, id)
if err != nil {
// 判断是否已经发布,如果未发布则直接删除
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
return i.strategyService.Delete(ctx, id)
}
return i.strategyService.SortDelete(ctx, id)
}
func (i *imlStrategyModule) ToPublish(ctx context.Context, driver string) ([]*strategy_dto.ToPublishItem, error) {
scope := strategy_dto.ToScope(strategy_dto.ScopeGlobal)
list, err := i.strategyService.SearchAllByDriver(ctx, "", driver, scope.Int(), "")
if err != nil {
return nil, err
}
strategyIds := utils.SliceToSlice(list, func(l *strategy.Strategy) string { return l.Id })
commits, err := i.strategyService.ListLatestStrategyCommit(ctx, scope.String(), "", strategyIds...)
if err != nil {
return nil, err
}
commitMap := utils.SliceToMapO(commits, func(c *commit.Commit[strategy.Commit]) (string, string) { return c.Data.Id, c.Data.Version })
items := make([]*strategy_dto.ToPublishItem, 0, len(list))
for _, l := range list {
status := strategy_dto.StrategyStatus(l, commitMap[l.Id])
if status == strategy_dto.PublishStatusOnline {
continue
}
items = append(items, &strategy_dto.ToPublishItem{
Name: l.Name,
Priority: l.Priority,
Status: status,
OptTime: l.UpdateAt,
})
}
sort.Slice(items, func(i, j int) bool {
return items[i].Priority < items[j].Priority
})
return items, nil
}
func (i *imlStrategyModule) Search(ctx context.Context, keyword string, driver string, scope strategy_dto.Scope, target string, page int, pageSize int, filters []string, order ...string) ([]*strategy_dto.StrategyItem, int64, error) {
list, total, err := i.strategyService.SearchByDriver(ctx, keyword, driver, scope.Int(), target, page, pageSize, filters, order...)
if err != nil {
return nil, 0, err
}
if len(list) < 1 {
return nil, 0, nil
}
strategyIds := utils.SliceToSlice(list, func(l *strategy.Strategy) string { return l.Id })
commits, err := i.strategyService.ListLatestStrategyCommit(ctx, scope.String(), target, strategyIds...)
if err != nil {
return nil, 0, err
}
commitMap := utils.SliceToMapO(commits, func(c *commit.Commit[strategy.Commit]) (string, string) { return c.Data.Id, c.Data.Version })
items := make([]*strategy_dto.StrategyItem, 0, len(list))
countMap := make(map[string]int64)
c, err := i.clusterService.Get(ctx, cluster.DefaultClusterID)
if err == nil {
countMap, err = i.logService.LogCount(ctx, "loki", c.Cluster, map[string]string{
"#1": fmt.Sprintf("strategy =~ \"%s\"", strings.Join(strategyIds, "|")),
}, 720,
"strategy")
if err != nil {
log.Errorf("get log count error: %v", err)
}
}
for _, l := range list {
fs := make([]*strategy_dto.Filter, 0)
json.Unmarshal([]byte(l.Filters), &fs)
filterList := make([]string, 0, len(fs))
for _, f := range fs {
info, err := strategy_filter.FilterLabel(f.Name, f.Values)
if err != nil {
log.Errorf("get filter label error: %v", err)
continue
}
filterList = append(filterList, fmt.Sprintf("[%s:%s]", info.Title, info.Label))
}
item := strategy_dto.ToStrategyItem(l, commitMap[l.Id], strings.Join(filterList, ";"), countMap[l.Id])
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
return items[i].Priority < items[j].Priority
})
return items, total, nil
}
func (i *imlStrategyModule) Get(ctx context.Context, id string) (*strategy_dto.Strategy, error) {
info, err := i.strategyService.Get(ctx, id)
if err != nil {
return nil, err
}
s := strategy_dto.ToStrategy(info)
for _, f := range s.Filters {
ff, has := strategy_filter.FilterGet(f.Name)
if !has {
return nil, fmt.Errorf("filter not found: %s", f.Name)
}
f.Title = ff.Title()
f.Type = ff.Type()
f.Label = strings.Join(ff.Labels(f.Values...), ",")
}
return s, nil
}
func (i *imlStrategyModule) Create(ctx context.Context, input *strategy_dto.Create) error {
if input.Name == "" {
return fmt.Errorf("name required")
}
if input.ID == "" {
input.ID = uuid.NewString()
}
if input.Priority < 1 {
input.Priority = 1000
}
err := strategy_filter.CheckFilters(input.Driver, input.Scope, input.Filters)
if err != nil {
return err
}
err = strategy_driver.CheckConfig(input.Driver, input.Config)
if err != nil {
return err
}
filters, _ := json.Marshal(input.Filters)
cfg, _ := json.Marshal(input.Config)
return i.strategyService.Create(ctx, &strategy.Create{
Id: input.ID,
Name: input.Name,
Priority: input.Priority,
Desc: input.Desc,
Filters: string(filters),
Config: string(cfg),
Scope: input.Scope.Int(),
Target: input.Target,
Driver: input.Driver,
})
}
func (i *imlStrategyModule) Edit(ctx context.Context, id string, input *strategy_dto.Edit) error {
if input.Name != nil && *input.Name == "" {
return fmt.Errorf("name required")
}
info, err := i.strategyService.Get(ctx, id)
if err != nil {
return err
}
if input.Priority != nil && *input.Priority < 1 {
*input.Priority = 1000
}
filters := info.Filters
if input.Filters != nil {
err = strategy_filter.CheckFilters(info.Driver, strategy_dto.Scope(info.Scope), *input.Filters)
if err != nil {
return err
}
data, _ := json.Marshal(input.Filters)
filters = string(data)
}
cfg := info.Config
if input.Config != nil {
err = strategy_driver.CheckConfig(info.Driver, input.Config)
if err != nil {
return err
}
data, _ := json.Marshal(input.Config)
cfg = string(data)
}
return i.strategyService.Save(ctx, id, &strategy.Edit{
Name: input.Name,
Priority: input.Priority,
Desc: input.Desc,
Filters: &filters,
Config: &cfg,
})
}
func (i *imlStrategyModule) Enable(ctx context.Context, id string) error {
stop := false
return i.strategyService.Save(ctx, id, &strategy.Edit{IsStop: &stop})
}
func (i *imlStrategyModule) Disable(ctx context.Context, id string) error {
stop := true
return i.strategyService.Save(ctx, id, &strategy.Edit{IsStop: &stop})
}
func (i *imlStrategyModule) Publish(ctx context.Context, driver string, scope string, target string) error {
d, has := strategy_driver.GetDriver(driver)
if !has {
return fmt.Errorf("driver not found: %s", driver)
}
list, err := i.strategyService.AllByDriver(ctx, driver, strategy_dto.ToScope(scope).Int(), target)
if err != nil {
return err
}
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
publishStrategies := make([]*eosc.Base[gateway.StrategyRelease], 0, len(list))
for _, l := range list {
if l.IsDelete {
err = i.strategyService.Delete(ctx, l.Id)
if err != nil {
return err
}
}
publishStrategies = append(publishStrategies, d.ToRelease(strategy_dto.ToStrategy(l), nil, 5000))
err = i.strategyService.CommitStrategy(txCtx, scope, target, l.Id, l)
if err != nil {
return err
}
}
client, err := i.clusterService.GatewayClient(ctx, cluster.DefaultClusterID)
if err != nil {
return err
}
defer func() {
_ = client.Close(ctx)
}()
return client.Strategy().Online(ctx, publishStrategies...)
})
}
func (i *imlStrategyModule) Delete(ctx context.Context, id string) error {
_, err := i.strategyService.LatestStrategyCommit(ctx, strategy_dto.ScopeGlobal, "", id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return i.strategyService.Delete(ctx, id)
}
return i.strategyService.SortDelete(ctx, id)
}
func (i *imlStrategyModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
commits, err := i.strategyService.ListLatestStrategyCommit(ctx, strategy_dto.ScopeGlobal, "")
if err != nil {
return err
}
publishStrategies := make([]*eosc.Base[gateway.StrategyRelease], 0, len(commits))
for _, c := range commits {
l := c.Data
if l.IsDelete {
err = i.strategyService.Delete(ctx, l.Id)
if err != nil {
return err
}
}
d, has := strategy_driver.GetDriver(l.Driver)
if !has {
continue
}
publishStrategies = append(publishStrategies, d.ToRelease(strategy_dto.ToStrategy(&strategy.Strategy{
Id: l.Id,
Name: l.Name,
Priority: l.Priority,
Filters: l.Filters,
Config: l.Config,
Driver: l.Driver,
IsStop: l.IsStop,
IsDelete: l.IsDelete,
}), nil, 5000))
}
return clientDriver.Strategy().Online(ctx, publishStrategies...)
}
+40
View File
@@ -0,0 +1,40 @@
package strategy
import (
"context"
"reflect"
"time"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/go-common/autowire"
_ "github.com/APIParkLab/APIPark/module/strategy/driver/data-masking"
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
)
type IStrategyModule interface {
Search(ctx context.Context, keyword string, driver string, scope strategy_dto.Scope, target string, page int, pageSize int, filters []string, order ...string) ([]*strategy_dto.StrategyItem, int64, error)
Get(ctx context.Context, id string) (*strategy_dto.Strategy, error)
Create(ctx context.Context, i *strategy_dto.Create) error
Edit(ctx context.Context, id string, i *strategy_dto.Edit) error
Enable(ctx context.Context, id string) error
Disable(ctx context.Context, id string) error
Publish(ctx context.Context, driver string, scope string, target string) error
Delete(ctx context.Context, id string) error
ToPublish(ctx context.Context, driver string) ([]*strategy_dto.ToPublishItem, error)
Restore(ctx context.Context, id string) error
DeleteServiceStrategy(ctx context.Context, serviceId string, id string) error
StrategyLogInfo(ctx context.Context, id string) (*strategy_dto.LogInfo, error)
GetStrategyLogs(ctx context.Context, keyword string, strategyID string, start time.Time, end time.Time, limit int64, offset int64) ([]*strategy_dto.LogItem, int64, error)
}
func init() {
strategyModule := new(imlStrategyModule)
autowire.Auto[IStrategyModule](func() reflect.Value {
gateway.RegisterInitHandleFunc(strategyModule.initGateway)
return reflect.ValueOf(strategyModule)
})
}
+1
View File
@@ -7,6 +7,7 @@ import (
type InputSetting struct {
InvokeAddress string `json:"invoke_address" key:"system.node.invoke_address"`
SitePrefix string `json:"site_prefix" key:"system.setting.site_prefix"`
}
func (i *InputSetting) Validate() error {
+1
View File
@@ -7,6 +7,7 @@ import (
type Setting struct {
InvokeAddress string `json:"invoke_address" key:"system.node.invoke_address"`
SitePrefix string `json:"site_prefix" key:"system.setting.site_prefix"`
}
func MapStringToStruct[T any](m map[string]string) *T {

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