diff --git a/controllers/application.go b/controllers/application.go index cbd10a90..7b0dd5a8 100644 --- a/controllers/application.go +++ b/controllers/application.go @@ -33,13 +33,17 @@ func (c *ApiController) GetApplications() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetApplications(owner) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner))) - applications := object.GetPaginationApplications(owner, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value))) + applications := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(applications, paginator.Nums()) } } diff --git a/controllers/organization.go b/controllers/organization.go index 03894bb0..c347e585 100644 --- a/controllers/organization.go +++ b/controllers/organization.go @@ -33,13 +33,17 @@ func (c *ApiController) GetOrganizations() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner)) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner))) - organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit)) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value))) + organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) c.ResponseOk(organizations, paginator.Nums()) } } diff --git a/controllers/provider.go b/controllers/provider.go index c10c6f8e..b0dd6448 100644 --- a/controllers/provider.go +++ b/controllers/provider.go @@ -16,7 +16,6 @@ package controllers import ( "encoding/json" - "github.com/astaxie/beego/utils/pagination" "github.com/casbin/casdoor/object" "github.com/casbin/casdoor/util" @@ -33,13 +32,17 @@ func (c *ApiController) GetProviders() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner)) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner))) - providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit)) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value))) + providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)) c.ResponseOk(providers, paginator.Nums()) } } diff --git a/controllers/record.go b/controllers/record.go index a0955ed0..b217982b 100644 --- a/controllers/record.go +++ b/controllers/record.go @@ -31,13 +31,17 @@ import ( func (c *ApiController) GetRecords() { limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetRecords() c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount())) - records := object.GetPaginationRecords(paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value))) + records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(records, paginator.Nums()) } } diff --git a/controllers/resource.go b/controllers/resource.go index c7355c67..683cf164 100644 --- a/controllers/resource.go +++ b/controllers/resource.go @@ -35,13 +35,17 @@ func (c *ApiController) GetResources() { user := c.Input().Get("user") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetResources(owner, user) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user))) - resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value))) + resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(resources, paginator.Nums()) } } diff --git a/controllers/syncer.go b/controllers/syncer.go index a924934a..0f2d5cdd 100644 --- a/controllers/syncer.go +++ b/controllers/syncer.go @@ -33,13 +33,17 @@ func (c *ApiController) GetSyncers() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetSyncers(owner) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner))) - syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value))) + syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(syncers, paginator.Nums()) } } diff --git a/controllers/token.go b/controllers/token.go index 4cd58b2b..e77a0543 100644 --- a/controllers/token.go +++ b/controllers/token.go @@ -35,13 +35,17 @@ func (c *ApiController) GetTokens() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetTokens(owner) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner))) - tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value))) + tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(tokens, paginator.Nums()) } } diff --git a/controllers/user.go b/controllers/user.go index 78f4da96..ce2b4342 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -33,13 +33,17 @@ import ( func (c *ApiController) GetGlobalUsers() { limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers()) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount())) - users := object.GetPaginationGlobalUsers(paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value))) + users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(users, paginator.Nums()) } } @@ -55,13 +59,17 @@ func (c *ApiController) GetUsers() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner)) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner))) - users := object.GetPaginationUsers(owner, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value))) + users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(users, paginator.Nums()) } } @@ -322,7 +330,7 @@ func (c *ApiController) GetUserCount() { count := 0 if isOnline == "" { - count = object.GetUserCount(owner) + count = object.GetUserCount(owner, "", "") } else { count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline)) } diff --git a/controllers/webhook.go b/controllers/webhook.go index af5a9ebb..e2c0e9de 100644 --- a/controllers/webhook.go +++ b/controllers/webhook.go @@ -33,13 +33,17 @@ func (c *ApiController) GetWebhooks() { owner := c.Input().Get("owner") limit := c.Input().Get("pageSize") page := c.Input().Get("p") + field := c.Input().Get("field") + value := c.Input().Get("value") + sortField := c.Input().Get("sortField") + sortOrder := c.Input().Get("sortOrder") if limit == "" || page == "" { c.Data["json"] = object.GetWebhooks(owner) c.ServeJSON() } else { limit := util.ParseInt(limit) - paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner))) - webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit) + paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value))) + webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder) c.ResponseOk(webhooks, paginator.Nums()) } } diff --git a/object/adapter.go b/object/adapter.go index 32c6066b..c08cedaa 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -16,6 +16,7 @@ package object import ( "fmt" + "github.com/casbin/casdoor/util" "runtime" "github.com/astaxie/beego" @@ -166,3 +167,22 @@ func (a *Adapter) createTable() { panic(err) } } + +func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session { + session := adapter.Engine.Limit(limit, offset).Where("1=1") + if owner != "" { + session = session.And("owner=?", owner) + } + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + if sortField == "" || sortOrder == "" { + sortField = "created_time" + } + if sortOrder == "ascend" { + session = session.Asc(util.SnakeString(sortField)) + } else { + session = session.Desc(util.SnakeString(sortField)) + } + return session +} \ No newline at end of file diff --git a/object/application.go b/object/application.go index 870e727b..1f0cab9e 100644 --- a/object/application.go +++ b/object/application.go @@ -53,8 +53,12 @@ type Application struct { SigninHtml string `xorm:"mediumtext" json:"signinHtml"` } -func GetApplicationCount(owner string) int { - count, err := adapter.Engine.Count(&Application{Owner: owner}) +func GetApplicationCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Application{}) if err != nil { panic(err) } @@ -72,9 +76,10 @@ func GetApplications(owner string) []*Application { return applications } -func GetPaginationApplications(owner string, offset, limit int) []*Application { +func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application { applications := []*Application{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&applications, &Application{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&applications) if err != nil { panic(err) } diff --git a/object/organization.go b/object/organization.go index 1119a956..47ecf628 100644 --- a/object/organization.go +++ b/object/organization.go @@ -15,6 +15,8 @@ package object import ( + "fmt" + "github.com/casbin/casdoor/cred" "github.com/casbin/casdoor/util" "xorm.io/core" @@ -36,8 +38,12 @@ type Organization struct { EnableSoftDeletion bool `json:"enableSoftDeletion"` } -func GetOrganizationCount(owner string) int { - count, err := adapter.Engine.Count(&Organization{Owner: owner}) +func GetOrganizationCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Organization{}) if err != nil { panic(err) } @@ -55,9 +61,10 @@ func GetOrganizations(owner string) []*Organization { return organizations } -func GetPaginationOrganizations(owner string, offset, limit int) []*Organization { +func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization { organizations := []*Organization{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&organizations, &Provider{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&organizations) if err != nil { panic(err) } diff --git a/object/provider.go b/object/provider.go index 849185ed..0b3ae9bd 100644 --- a/object/provider.go +++ b/object/provider.go @@ -80,8 +80,12 @@ func GetMaskedProviders(providers []*Provider) []*Provider { return providers } -func GetProviderCount(owner string) int { - count, err := adapter.Engine.Count(&Provider{Owner: owner}) +func GetProviderCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Provider{}) if err != nil { panic(err) } @@ -99,9 +103,10 @@ func GetProviders(owner string) []*Provider { return providers } -func GetPaginationProviders(owner string, offset, limit int) []*Provider { +func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider { providers := []*Provider{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&providers, &Provider{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&providers) if err != nil { panic(err) } diff --git a/object/record.go b/object/record.go index e79b54eb..35750d6e 100644 --- a/object/record.go +++ b/object/record.go @@ -99,8 +99,12 @@ func AddRecord(record *Record) bool { return affected != 0 } -func GetRecordCount() int { - count, err := adapter.Engine.Count(&Record{}) +func GetRecordCount(field, value string) int { + session := adapter.Engine.Where("1=1") + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Record{}) if err != nil { panic(err) } @@ -118,9 +122,10 @@ func GetRecords() []*Record { return records } -func GetPaginationRecords(offset, limit int) []*Record { +func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string) []*Record { records := []*Record{} - err := adapter.Engine.Desc("id").Limit(limit, offset).Find(&records) + session := GetSession("", offset, limit, field, value, sortField, sortOrder) + err := session.Find(&records) if err != nil { panic(err) } diff --git a/object/resource.go b/object/resource.go index c9156642..93310564 100644 --- a/object/resource.go +++ b/object/resource.go @@ -39,8 +39,12 @@ type Resource struct { Description string `xorm:"varchar(1000)" json:"description"` } -func GetResourceCount(owner string, user string) int { - count, err := adapter.Engine.Count(&Resource{Owner: owner, User: user}) +func GetResourceCount(owner, user, field, value string) int { + session := adapter.Engine.Where("owner=? and user=?", owner, user) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Resource{}) if err != nil { panic(err) } @@ -63,14 +67,15 @@ func GetResources(owner string, user string) []*Resource { return resources } -func GetPaginationResources(owner, user string, offset, limit int) []*Resource { +func GetPaginationResources(owner, user string, offset, limit int, field, value, sortField, sortOrder string) []*Resource { if owner == "built-in" { owner = "" user = "" } resources := []*Resource{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&resources, &Resource{Owner: owner, User: user}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&resources, &Resource{User: user}) if err != nil { panic(err) } diff --git a/object/syncer.go b/object/syncer.go index 076be4a7..ce40e884 100644 --- a/object/syncer.go +++ b/object/syncer.go @@ -54,8 +54,12 @@ type Syncer struct { Adapter *Adapter `xorm:"-" json:"-"` } -func GetSyncerCount(owner string) int { - count, err := adapter.Engine.Count(&Syncer{Owner: owner}) +func GetSyncerCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Syncer{}) if err != nil { panic(err) } @@ -73,9 +77,10 @@ func GetSyncers(owner string) []*Syncer { return syncers } -func GetPaginationSyncers(owner string, offset, limit int) []*Syncer { +func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer { syncers := []*Syncer{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&syncers, &Syncer{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&syncers) if err != nil { panic(err) } diff --git a/object/token.go b/object/token.go index 80cb04cd..8fcbcc0d 100644 --- a/object/token.go +++ b/object/token.go @@ -53,8 +53,12 @@ type TokenWrapper struct { Scope string `json:"scope"` } -func GetTokenCount(owner string) int { - count, err := adapter.Engine.Count(&Token{Owner: owner}) +func GetTokenCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Token{}) if err != nil { panic(err) } @@ -72,9 +76,10 @@ func GetTokens(owner string) []*Token { return tokens } -func GetPaginationTokens(owner string, offset, limit int) []*Token { +func GetPaginationTokens(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Token { tokens := []*Token{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&tokens, &Token{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&tokens) if err != nil { panic(err) } diff --git a/object/user.go b/object/user.go index 66c25bb8..b23ba0b2 100644 --- a/object/user.go +++ b/object/user.go @@ -86,8 +86,12 @@ type User struct { Properties map[string]string `json:"properties"` } -func GetGlobalUserCount() int { - count, err := adapter.Engine.Count(&User{}) +func GetGlobalUserCount(field, value string) int { + session := adapter.Engine.Where("1=1") + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&User{}) if err != nil { panic(err) } @@ -105,9 +109,10 @@ func GetGlobalUsers() []*User { return users } -func GetPaginationGlobalUsers(offset, limit int) []*User { +func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) []*User { users := []*User{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users) + session := GetSession("", offset, limit, field, value, sortField, sortOrder) + err := session.Find(&users) if err != nil { panic(err) } @@ -115,8 +120,12 @@ func GetPaginationGlobalUsers(offset, limit int) []*User { return users } -func GetUserCount(owner string) int { - count, err := adapter.Engine.Count(&User{Owner: owner}) +func GetUserCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&User{}) if err != nil { panic(err) } @@ -153,9 +162,10 @@ func GetSortedUsers(owner string, sorter string, limit int) []*User { return users } -func GetPaginationUsers(owner string, offset, limit int) []*User { +func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*User { users := []*User{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users, &User{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&users) if err != nil { panic(err) } diff --git a/object/webhook.go b/object/webhook.go index c6db31df..e64bd7a2 100644 --- a/object/webhook.go +++ b/object/webhook.go @@ -33,8 +33,12 @@ type Webhook struct { Organization string `xorm:"varchar(100) index" json:"organization"` } -func GetWebhookCount(owner string) int { - count, err := adapter.Engine.Count(&Webhook{Owner: owner}) +func GetWebhookCount(owner, field, value string) int { + session := adapter.Engine.Where("owner=?", owner) + if field != "" && value != "" { + session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value)) + } + count, err := session.Count(&Webhook{}) if err != nil { panic(err) } @@ -52,9 +56,10 @@ func GetWebhooks(owner string) []*Webhook { return webhooks } -func GetPaginationWebhooks(owner string, offset, limit int) []*Webhook { +func GetPaginationWebhooks(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook { webhooks := []*Webhook{} - err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&webhooks, &Webhook{Owner: owner}) + session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) + err := session.Find(&webhooks) if err != nil { panic(err) } diff --git a/util/string.go b/util/string.go index eab4527d..65e9d823 100644 --- a/util/string.go +++ b/util/string.go @@ -162,3 +162,21 @@ func WriteBytesToPath(b []byte, path string) { panic(err) } } + +// SnakeString XxYy to xx_yy +func SnakeString(s string) string { + data := make([]byte, 0, len(s)*2) + j := false + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true + } + data = append(data, d) + } + return strings.ToLower(string(data[:])) +} diff --git a/web/package.json b/web/package.json index 44532b7e..4acdf558 100644 --- a/web/package.json +++ b/web/package.json @@ -16,6 +16,7 @@ "i18n-iso-countries": "^7.0.0", "i18next": "^19.8.9", "moment": "^2.29.1", + "qs": "^6.10.2", "react": "^17.0.2", "react-codemirror2": "^7.2.1", "react-cropper": "^2.1.7", @@ -23,6 +24,7 @@ "react-dom": "^17.0.2", "react-github-corner": "^2.5.0", "react-helmet": "^6.1.0", + "react-highlight-words": "^0.17.0", "react-i18next": "^11.8.7", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", diff --git a/web/src/ApplicationListPage.js b/web/src/ApplicationListPage.js index 73e600a7..d8cb982b 100644 --- a/web/src/ApplicationListPage.js +++ b/web/src/ApplicationListPage.js @@ -20,32 +20,10 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as ApplicationBackend from "./backend/ApplicationBackend"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class ApplicationListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - applications: null, - total: 0, - }; - } - - UNSAFE_componentWillMount() { - this.getApplications(1, 10); - } - - getApplications(page, pageSize) { - ApplicationBackend.getApplications("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - applications: res.data, - total: res.data2 - }); - } - }); - } +class ApplicationListPage extends BaseListPage { newApplication() { const randomName = Setting.getRandomName(); @@ -80,10 +58,6 @@ class ApplicationListPage extends React.Component { ApplicationBackend.addApplication(newApplication) .then((res) => { Setting.showMessage("success", `Application added successfully`); - this.setState({ - applications: Setting.prependRow(this.state.applications, newApplication), - total: this.state.total + 1 - }); this.props.history.push(`/applications/${newApplication.name}`); } ) @@ -93,12 +67,12 @@ class ApplicationListPage extends React.Component { } deleteApplication(i) { - ApplicationBackend.deleteApplication(this.state.applications[i]) + ApplicationBackend.deleteApplication(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Application deleted successfully`); this.setState({ - applications: Setting.deleteRow(this.state.applications, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -115,7 +89,8 @@ class ApplicationListPage extends React.Component { key: 'name', width: '150px', fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -129,7 +104,7 @@ class ApplicationListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '160px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -139,7 +114,8 @@ class ApplicationListPage extends React.Component { dataIndex: 'displayName', key: 'displayName', // width: '100px', - sorter: (a, b) => a.displayName.localeCompare(b.displayName), + sorter: true, + ...this.getColumnSearchProps('displayName'), }, { title: 'Logo', @@ -159,7 +135,8 @@ class ApplicationListPage extends React.Component { dataIndex: 'organization', key: 'organization', width: '150px', - sorter: (a, b) => a.organization.localeCompare(b.organization), + sorter: true, + ...this.getColumnSearchProps('organization'), render: (text, record, index) => { return ( @@ -172,6 +149,7 @@ class ApplicationListPage extends React.Component { title: i18next.t("general:Providers"), dataIndex: 'providers', key: 'providers', + ...this.getColumnSearchProps('providers'), // width: '600px', render: (text, record, index) => { const providers = text; @@ -247,12 +225,10 @@ class ApplicationListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getApplications(page, pageSize), - onShowSizeChange: (current, size) => this.getApplications(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -264,21 +240,33 @@ class ApplicationListPage extends React.Component { )} - loading={applications === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.applications) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + this.setState({ loading: true }); + ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default ApplicationListPage; diff --git a/web/src/BaseListPage.js b/web/src/BaseListPage.js new file mode 100644 index 00000000..b38dd0e3 --- /dev/null +++ b/web/src/BaseListPage.js @@ -0,0 +1,138 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {Button, Input, Space} from "antd"; +import {SearchOutlined} from "@ant-design/icons"; +import Highlighter from "react-highlight-words"; + +class BaseListPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + data: [], + pagination: { + current: 1, + pageSize: 10, + }, + loading: false, + searchText: '', + searchedColumn: '', + }; + } + + UNSAFE_componentWillMount() { + const { pagination } = this.state; + this.fetch({ pagination }); + } + + getColumnSearchProps = dataIndex => ({ + filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( +
+ { + this.searchInput = node; + }} + placeholder={`Search ${dataIndex}`} + value={selectedKeys[0]} + onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])} + onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} + style={{ marginBottom: 8, display: 'block' }} + /> + + + + + +
+ ), + filterIcon: filtered => , + onFilter: (value, record) => + record[dataIndex] + ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) + : '', + onFilterDropdownVisibleChange: visible => { + if (visible) { + setTimeout(() => this.searchInput.select(), 100); + } + }, + render: text => + this.state.searchedColumn === dataIndex ? ( + + ) : ( + text + ), + }); + + handleSearch = (selectedKeys, confirm, dataIndex) => { + this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination}); + }; + + handleReset = clearFilters => { + clearFilters(); + const { pagination } = this.state; + this.fetch({ pagination }); + }; + + handleTableChange = (pagination, filters, sorter) => { + this.fetch({ + sortField: sorter.field, + sortOrder: sorter.order, + pagination, + ...filters, + searchText: this.state.searchText, + searchedColumn: this.state.searchedColumn, + }); + }; + + render() { + return ( +
+ { + this.renderTable(this.state.data) + } +
+ ); + } +} + +export default BaseListPage; \ No newline at end of file diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js index cc913891..4dd23b95 100644 --- a/web/src/OrganizationListPage.js +++ b/web/src/OrganizationListPage.js @@ -19,32 +19,9 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as OrganizationBackend from "./backend/OrganizationBackend"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; -class OrganizationListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - organizations: null, - total: 0 - }; - } - - UNSAFE_componentWillMount() { - this.getOrganizations(1, 10); - } - - getOrganizations(page, pageSize) { - OrganizationBackend.getOrganizations("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - organizations: res.data, - total: res.data2 - }); - } - }); - } +class OrganizationListPage extends BaseListPage { newOrganization() { const randomName = Setting.getRandomName(); @@ -69,10 +46,6 @@ class OrganizationListPage extends React.Component { OrganizationBackend.addOrganization(newOrganization) .then((res) => { Setting.showMessage("success", `Organization added successfully`); - this.setState({ - organizations: Setting.prependRow(this.state.organizations, newOrganization), - total: this.state.total + 1 - }); this.props.history.push(`/organizations/${newOrganization.name}`); } ) @@ -82,12 +55,12 @@ class OrganizationListPage extends React.Component { } deleteOrganization(i) { - OrganizationBackend.deleteOrganization(this.state.organizations[i]) + OrganizationBackend.deleteOrganization(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Organization deleted successfully`); this.setState({ - organizations: Setting.deleteRow(this.state.organizations, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -104,7 +77,8 @@ class OrganizationListPage extends React.Component { key: 'name', width: '120px', fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -118,7 +92,7 @@ class OrganizationListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '160px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -128,7 +102,8 @@ class OrganizationListPage extends React.Component { dataIndex: 'displayName', key: 'displayName', // width: '100px', - sorter: (a, b) => a.displayName.localeCompare(b.displayName), + sorter: true, + ...this.getColumnSearchProps('displayName'), }, { title: i18next.t("organization:Favicon"), @@ -148,7 +123,8 @@ class OrganizationListPage extends React.Component { dataIndex: 'websiteUrl', key: 'websiteUrl', width: '300px', - sorter: (a, b) => a.websiteUrl.localeCompare(b.websiteUrl), + sorter: true, + ...this.getColumnSearchProps('websiteUrl'), render: (text, record, index) => { return ( @@ -162,14 +138,21 @@ class OrganizationListPage extends React.Component { dataIndex: 'passwordType', key: 'passwordType', width: '150px', - sorter: (a, b) => a.passwordType.localeCompare(b.passwordType), + sorter: true, + filterMultiple: false, + filters: [ + {text: 'plain', value: 'plain'}, + {text: 'salt', value: 'salt'}, + {text: 'md5-salt', value: 'md5-salt'}, + ], }, { title: i18next.t("general:Password salt"), dataIndex: 'passwordSalt', key: 'passwordSalt', width: '150px', - sorter: (a, b) => a.passwordSalt.localeCompare(b.passwordSalt), + sorter: true, + ...this.getColumnSearchProps('passwordSalt'), }, { title: i18next.t("organization:Default avatar"), @@ -189,7 +172,7 @@ class OrganizationListPage extends React.Component { dataIndex: 'enableSoftDeletion', key: 'enableSoftDeletion', width: '140px', - sorter: (a, b) => a.enableSoftDeletion - b.enableSoftDeletion, + sorter: true, render: (text, record, index) => { return ( @@ -221,12 +204,10 @@ class OrganizationListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getOrganizations(page, pageSize), - onShowSizeChange: (current, size) => this.getOrganizations(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -238,21 +219,37 @@ class OrganizationListPage extends React.Component { )} - loading={organizations === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.organizations) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + if (params.passwordType !== undefined && params.passwordType !== null) { + field = "passwordType"; + value = params.passwordType; + } + this.setState({ loading: true }); + OrganizationBackend.getOrganizations("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default OrganizationListPage; diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index dcc2370c..5b1bd02c 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -64,60 +64,6 @@ class ProviderEditPage extends React.Component { }); } - getProviderTypeOptions(provider) { - if (provider.category === "OAuth") { - return ( - [ - {id: 'Google', name: 'Google'}, - {id: 'GitHub', name: 'GitHub'}, - {id: 'QQ', name: 'QQ'}, - {id: 'WeChat', name: 'WeChat'}, - {id: 'Facebook', name: 'Facebook'}, - {id: 'DingTalk', name: 'DingTalk'}, - {id: 'Weibo', name: 'Weibo'}, - {id: 'Gitee', name: 'Gitee'}, - {id: 'LinkedIn', name: 'LinkedIn'}, - {id: 'WeCom', name: 'WeCom'}, - {id: 'Lark', name: 'Lark'}, - {id: 'GitLab', name: 'GitLab'}, - {id: 'Apple', name: 'Apple'}, - {id: 'AzureAD', name: 'AzureAD'}, - {id: 'Slack', name: 'Slack'}, - ] - ); - } else if (provider.category === "Email") { - return ( - [ - {id: 'Default', name: 'Default'}, - ] - ); - } else if (provider.category === "SMS") { - return ( - [ - {id: 'Aliyun SMS', name: 'Aliyun SMS'}, - {id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'}, - {id: 'Volc Engine SMS', name: 'Volc Engine SMS'}, - ] - ); - } else if (provider.category === "Storage") { - return ( - [ - {id: 'Local File System', name: 'Local File System'}, - {id: 'AWS S3', name: 'AWS S3'}, - {id: 'Aliyun OSS', name: 'Aliyun OSS'}, - {id: 'Tencent Cloud COS', name: 'Tencent Cloud COS'}, - ] - ); - } else if (provider.category === "SAML") { - return ([ - {id: 'Aliyun IDaaS', name: 'Aliyun IDaaS'}, - {id: 'Keycloak', name: 'Keycloak'}, - ]); - } else { - return []; - } - } - getClientIdLabel() { switch (this.state.provider.category) { case "Email": @@ -252,7 +198,7 @@ class ProviderEditPage extends React.Component { } })}> { - this.getProviderTypeOptions(this.state.provider).map((providerType, index) => ) + Setting.getProviderTypeOptions(this.state.provider.category).map((providerType, index) => ) } diff --git a/web/src/ProviderListPage.js b/web/src/ProviderListPage.js index 8b468410..3882f1ee 100644 --- a/web/src/ProviderListPage.js +++ b/web/src/ProviderListPage.js @@ -20,32 +20,9 @@ import * as Setting from "./Setting"; import * as ProviderBackend from "./backend/ProviderBackend"; import * as Provider from "./auth/Provider"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; -class ProviderListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - providers: null, - total: 0, - }; - } - - UNSAFE_componentWillMount() { - this.getProviders(1, 10); - } - - getProviders(page, pageSize) { - ProviderBackend.getProviders("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - providers: res.data, - total: res.data2 - }); - } - }); - } +class ProviderListPage extends BaseListPage { newProvider() { const randomName = Setting.getRandomName(); @@ -71,10 +48,6 @@ class ProviderListPage extends React.Component { ProviderBackend.addProvider(newProvider) .then((res) => { Setting.showMessage("success", `Provider added successfully`); - this.setState({ - providers: Setting.prependRow(this.state.providers, newProvider), - total: this.state.total + 1 - }); this.props.history.push(`/providers/${newProvider.name}`); } ) @@ -84,12 +57,12 @@ class ProviderListPage extends React.Component { } deleteProvider(i) { - ProviderBackend.deleteProvider(this.state.providers[i]) + ProviderBackend.deleteProvider(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Provider deleted successfully`); this.setState({ - providers: Setting.deleteRow(this.state.providers, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -106,7 +79,8 @@ class ProviderListPage extends React.Component { key: 'name', width: '120px', fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -120,7 +94,7 @@ class ProviderListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '180px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -130,14 +104,23 @@ class ProviderListPage extends React.Component { dataIndex: 'displayName', key: 'displayName', // width: '100px', - sorter: (a, b) => a.displayName.localeCompare(b.displayName), + sorter: true, + ...this.getColumnSearchProps('displayName'), }, { title: i18next.t("provider:Category"), dataIndex: 'category', key: 'category', + filterMultiple: false, + filters: [ + {text: 'OAuth', value: 'OAuth'}, + {text: 'Email', value: 'Email'}, + {text: 'SMS', value: 'SMS'}, + {text: 'Storage', value: 'Storage'}, + {text: 'SAML', value: 'SAML'}, + ], width: '100px', - sorter: (a, b) => a.category.localeCompare(b.category), + sorter: true, }, { title: i18next.t("provider:Type"), @@ -145,7 +128,15 @@ class ProviderListPage extends React.Component { key: 'type', width: '80px', align: 'center', - sorter: (a, b) => a.type.localeCompare(b.type), + filterMultiple: false, + filters: [ + {text: 'OAuth', value: 'OAuth', children: Setting.getProviderTypeOptions('OAuth').map((o) => {return {text:o.id, value:o.name}})}, + {text: 'Email', value: 'Email', children: Setting.getProviderTypeOptions('Email').map((o) => {return {text:o.id, value:o.name}})}, + {text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})}, + {text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})}, + {text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})}, + ], + sorter: true, render: (text, record, index) => { return Provider.getProviderLogoWidget(record); } @@ -155,7 +146,8 @@ class ProviderListPage extends React.Component { dataIndex: 'clientId', key: 'clientId', width: '100px', - sorter: (a, b) => a.clientId.localeCompare(b.clientId), + sorter: true, + ...this.getColumnSearchProps('clientId'), render: (text, record, index) => { return Setting.getShortText(text); } @@ -172,7 +164,8 @@ class ProviderListPage extends React.Component { dataIndex: 'providerUrl', key: 'providerUrl', width: '150px', - sorter: (a, b) => a.providerUrl.localeCompare(b.providerUrl), + sorter: true, + ...this.getColumnSearchProps('providerUrl'), render: (text, record, index) => { return (
@@ -206,12 +199,10 @@ class ProviderListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getProviders(page, pageSize), - onShowSizeChange: (current, size) => this.getProviders(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -223,21 +214,40 @@ class ProviderListPage extends React.Component { )} - loading={providers === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.providers) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + if (params.category !== undefined && params.category !== null) { + field = "category"; + value = params.category; + } else if (params.type !== undefined && params.type !== null) { + field = "type"; + value = params.type; + } + this.setState({ loading: true }); + ProviderBackend.getProviders("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default ProviderListPage; diff --git a/web/src/RecordListPage.js b/web/src/RecordListPage.js index b87289ae..bc4d3fd6 100644 --- a/web/src/RecordListPage.js +++ b/web/src/RecordListPage.js @@ -19,31 +19,15 @@ import * as Setting from "./Setting"; import * as RecordBackend from "./backend/RecordBackend"; import i18next from "i18next"; import moment from "moment"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class RecordListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - records: null, - total: 0, - }; - } +class RecordListPage extends BaseListPage { UNSAFE_componentWillMount() { - this.getRecords(1, 20); - } - - getRecords(page, pageSize) { - RecordBackend.getRecords(page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - records: res.data, - total: res.data2 - }); - } - }); + this.state.pagination.pageSize = 20; + const { pagination } = this.state; + this.fetch({ pagination }); } newRecord() { @@ -68,21 +52,24 @@ class RecordListPage extends React.Component { dataIndex: 'name', key: 'name', width: '320px', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), }, { title: i18next.t("general:ID"), dataIndex: 'id', key: 'id', width: '90px', - sorter: (a, b) => a.id - b.id, + sorter: true, + ...this.getColumnSearchProps('id'), }, { title: i18next.t("general:Client IP"), dataIndex: 'clientIp', key: 'clientIp', width: '150px', - sorter: (a, b) => a.clientIp.localeCompare(b.clientIp), + sorter: true, + ...this.getColumnSearchProps('clientIp'), render: (text, record, index) => { return (
@@ -96,7 +83,7 @@ class RecordListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '180px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -106,7 +93,8 @@ class RecordListPage extends React.Component { dataIndex: 'organization', key: 'organization', width: '80px', - sorter: (a, b) => a.organization.localeCompare(b.organization), + sorter: true, + ...this.getColumnSearchProps('organization'), render: (text, record, index) => { return ( @@ -120,7 +108,8 @@ class RecordListPage extends React.Component { dataIndex: 'user', key: 'user', width: '120px', - sorter: (a, b) => a.user.localeCompare(b.user), + sorter: true, + ...this.getColumnSearchProps('user'), render: (text, record, index) => { return ( @@ -134,21 +123,35 @@ class RecordListPage extends React.Component { dataIndex: 'method', key: 'method', width: '100px', - sorter: (a, b) => a.method.localeCompare(b.method), + sorter: true, + filterMultiple: false, + filters: [ + {text: 'GET', value: 'GET'}, + {text: 'HEAD', value: 'HEAD'}, + {text: 'POST', value: 'POST'}, + {text: 'PUT', value: 'PUT'}, + {text: 'DELETE', value: 'DELETE'}, + {text: 'CONNECT', value: 'CONNECT'}, + {text: 'OPTIONS', value: 'OPTIONS'}, + {text: 'TRACE', value: 'TRACE'}, + {text: 'PATCH', value: 'PATCH'}, + ], }, { title: i18next.t("general:Request URI"), dataIndex: 'requestUri', key: 'requestUri', // width: '300px', - sorter: (a, b) => a.requestUri.localeCompare(b.requestUri), + sorter: true, + ...this.getColumnSearchProps('requestUri'), }, { title: i18next.t("general:Action"), dataIndex: 'action', key: 'action', width: '200px', - sorter: (a, b) => a.action.localeCompare(b.action), + sorter: true, + ...this.getColumnSearchProps('action'), fixed: (Setting.isMobile()) ? "false" : "right", render: (text, record, index) => { return text; @@ -159,7 +162,7 @@ class RecordListPage extends React.Component { dataIndex: 'isTriggered', key: 'isTriggered', width: '140px', - sorter: (a, b) => a.isTriggered - b.isTriggered, + sorter: true, fixed: (Setting.isMobile()) ? "false" : "right", render: (text, record, index) => { if (!["signup", "login", "logout", "update-user"].includes(record.action)) { @@ -174,13 +177,11 @@ class RecordListPage extends React.Component { ]; const paginationProps = { - pageSize: 20, - total: this.state.total, + total: this.state.pagination.total, + pageSize: this.state.pagination.pageSize, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getRecords(page, pageSize), - onShowSizeChange: (current, size) => this.getRecords(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -191,21 +192,37 @@ class RecordListPage extends React.Component { {i18next.t("general:Records")}     )} - loading={records === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.records) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + if (params.method !== undefined && params.method !== null) { + field = "method"; + value = params.method; + } + this.setState({ loading: true }); + RecordBackend.getRecords(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default RecordListPage; diff --git a/web/src/ResourceListPage.js b/web/src/ResourceListPage.js index 8b72aa1c..ace420ed 100644 --- a/web/src/ResourceListPage.js +++ b/web/src/ResourceListPage.js @@ -20,42 +20,34 @@ import * as Setting from "./Setting"; import * as ResourceBackend from "./backend/ResourceBackend"; import i18next from "i18next"; import {Link} from "react-router-dom"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class ResourceListPage extends React.Component { +class ResourceListPage extends BaseListPage { constructor(props) { super(props); this.state = { classes: props, - resources: null, + data: [], + pagination: { + current: 1, + pageSize: 10, + }, + loading: false, + searchText: '', + searchedColumn: '', fileList: [], uploading: false, - total: 0, }; } - UNSAFE_componentWillMount() { - this.getResources(1, 10); - } - - getResources(page, pageSize) { - ResourceBackend.getResources(this.props.account.owner, this.props.account.name, page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - resources: res.data, - total: res.data2 - }); - } - }); - } - deleteResource(i) { - ResourceBackend.deleteResource(this.state.resources[i]) + ResourceBackend.deleteResource(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Resource deleted successfully`); this.setState({ - resources: Setting.deleteRow(this.state.resources, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -100,7 +92,8 @@ class ResourceListPage extends React.Component { key: 'provider', width: '150px', fixed: 'left', - sorter: (a, b) => a.provider.localeCompare(b.provider), + sorter: true, + ...this.getColumnSearchProps('provider'), render: (text, record, index) => { return ( @@ -114,7 +107,8 @@ class ResourceListPage extends React.Component { dataIndex: 'application', key: 'application', width: '80px', - sorter: (a, b) => a.application.localeCompare(b.application), + sorter: true, + ...this.getColumnSearchProps('application'), render: (text, record, index) => { return ( @@ -128,7 +122,8 @@ class ResourceListPage extends React.Component { dataIndex: 'user', key: 'user', width: '80px', - sorter: (a, b) => a.user.localeCompare(b.user), + sorter: true, + ...this.getColumnSearchProps('user'), render: (text, record, index) => { return ( @@ -142,21 +137,23 @@ class ResourceListPage extends React.Component { dataIndex: 'parent', key: 'parent', width: '80px', - sorter: (a, b) => a.parent.localeCompare(b.parent), + sorter: true, + ...this.getColumnSearchProps('parent'), }, { title: i18next.t("general:Name"), dataIndex: 'name', key: 'name', width: '150px', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), }, { title: i18next.t("general:Created time"), dataIndex: 'createdTime', key: 'createdTime', width: '150px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -166,7 +163,8 @@ class ResourceListPage extends React.Component { dataIndex: 'tag', key: 'tag', width: '80px', - sorter: (a, b) => a.tag.localeCompare(b.tag), + sorter: true, + ...this.getColumnSearchProps('tag'), }, // { // title: i18next.t("resource:File name"), @@ -180,21 +178,23 @@ class ResourceListPage extends React.Component { dataIndex: 'fileType', key: 'fileType', width: '80px', - sorter: (a, b) => a.fileType.localeCompare(b.fileType), + sorter: true, + ...this.getColumnSearchProps('fileType'), }, { title: i18next.t("resource:Format"), dataIndex: 'fileFormat', key: 'fileFormat', width: '80px', - sorter: (a, b) => a.fileFormat.localeCompare(b.fileFormat), + sorter: true, + ...this.getColumnSearchProps('fileFormat'), }, { title: i18next.t("resource:File size"), dataIndex: 'fileSize', key: 'fileSize', width: '100px', - sorter: (a, b) => a.fileSize - b.fileSize, + sorter: true, render: (text, record, index) => { return Setting.getFriendlyFileSize(text); } @@ -266,12 +266,10 @@ class ResourceListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getResources(page, pageSize), - onShowSizeChange: (current, size) => this.getResources(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -286,21 +284,33 @@ class ResourceListPage extends React.Component { } )} - loading={resources === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.resources) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + this.setState({ loading: true }); + ResourceBackend.getResources(this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default ResourceListPage; diff --git a/web/src/Setting.js b/web/src/Setting.js index 2007c33c..372b90b3 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -382,6 +382,60 @@ export function getProviderLogo(provider) { ) } +export function getProviderTypeOptions(category) { + if (category === "OAuth") { + return ( + [ + {id: 'Google', name: 'Google'}, + {id: 'GitHub', name: 'GitHub'}, + {id: 'QQ', name: 'QQ'}, + {id: 'WeChat', name: 'WeChat'}, + {id: 'Facebook', name: 'Facebook'}, + {id: 'DingTalk', name: 'DingTalk'}, + {id: 'Weibo', name: 'Weibo'}, + {id: 'Gitee', name: 'Gitee'}, + {id: 'LinkedIn', name: 'LinkedIn'}, + {id: 'WeCom', name: 'WeCom'}, + {id: 'Lark', name: 'Lark'}, + {id: 'GitLab', name: 'GitLab'}, + {id: 'Apple', name: 'Apple'}, + {id: 'AzureAD', name: 'AzureAD'}, + {id: 'Slack', name: 'Slack'}, + ] + ); + } else if (category === "Email") { + return ( + [ + {id: 'Default', name: 'Default'}, + ] + ); + } else if (category === "SMS") { + return ( + [ + {id: 'Aliyun SMS', name: 'Aliyun SMS'}, + {id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'}, + {id: 'Volc Engine SMS', name: 'Volc Engine SMS'}, + ] + ); + } else if (category === "Storage") { + return ( + [ + {id: 'Local File System', name: 'Local File System'}, + {id: 'AWS S3', name: 'AWS S3'}, + {id: 'Aliyun OSS', name: 'Aliyun OSS'}, + {id: 'Tencent Cloud COS', name: 'Tencent Cloud COS'}, + ] + ); + } else if (category === "SAML") { + return ([ + {id: 'Aliyun IDaaS', name: 'Aliyun IDaaS'}, + {id: 'Keycloak', name: 'Keycloak'}, + ]); + } else { + return []; + } +} + export function renderLogo(application) { if (application === null) { return null; diff --git a/web/src/SyncerListPage.js b/web/src/SyncerListPage.js index 4142525e..ebfb470f 100644 --- a/web/src/SyncerListPage.js +++ b/web/src/SyncerListPage.js @@ -19,32 +19,10 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as SyncerBackend from "./backend/SyncerBackend"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class SyncerListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - syncers: null, - total: 0, - }; - } - - UNSAFE_componentWillMount() { - this.getSyncers(1, 10); - } - - getSyncers(page, pageSize) { - SyncerBackend.getSyncers("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - syncers: res.data, - total: res.data2 - }); - } - }); - } +class SyncerListPage extends BaseListPage { newSyncer() { const randomName = Setting.getRandomName(); @@ -74,10 +52,6 @@ class SyncerListPage extends React.Component { SyncerBackend.addSyncer(newSyncer) .then((res) => { Setting.showMessage("success", `Syncer added successfully`); - this.setState({ - syncers: Setting.prependRow(this.state.syncers, newSyncer), - total: this.state.total + 1 - }); this.props.history.push(`/syncers/${newSyncer.name}`); } ) @@ -87,12 +61,12 @@ class SyncerListPage extends React.Component { } deleteSyncer(i) { - SyncerBackend.deleteSyncer(this.state.syncers[i]) + SyncerBackend.deleteSyncer(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Syncer deleted successfully`); this.setState({ - syncers: Setting.deleteRow(this.state.syncers, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -108,7 +82,8 @@ class SyncerListPage extends React.Component { dataIndex: 'organization', key: 'organization', width: '120px', - sorter: (a, b) => a.organization.localeCompare(b.organization), + sorter: true, + ...this.getColumnSearchProps('organization'), render: (text, record, index) => { return ( @@ -123,7 +98,8 @@ class SyncerListPage extends React.Component { key: 'name', width: '150px', fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -137,7 +113,7 @@ class SyncerListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '180px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -147,35 +123,44 @@ class SyncerListPage extends React.Component { dataIndex: 'type', key: 'type', width: '100px', - sorter: (a, b) => a.type.localeCompare(b.type), + sorter: true, + filterMultiple: false, + filters: [ + {text: 'Database', value: 'Database'}, + {text: 'LDAP', value: 'LDAP'}, + ], }, { title: i18next.t("provider:Host"), dataIndex: 'host', key: 'host', width: '120px', - sorter: (a, b) => a.host.localeCompare(b.host), + sorter: true, + ...this.getColumnSearchProps('host'), }, { title: i18next.t("provider:Port"), dataIndex: 'port', key: 'port', width: '100px', - sorter: (a, b) => a.port - b.port, + sorter: true, + ...this.getColumnSearchProps('port'), }, { title: i18next.t("general:User"), dataIndex: 'user', key: 'user', width: '120px', - sorter: (a, b) => a.user.localeCompare(b.user), + sorter: true, + ...this.getColumnSearchProps('user'), }, { title: i18next.t("general:Password"), dataIndex: 'password', key: 'password', width: '120px', - sorter: (a, b) => a.password.localeCompare(b.password), + sorter: true, + ...this.getColumnSearchProps('password'), }, { title: i18next.t("syncer:Database type"), @@ -189,28 +174,29 @@ class SyncerListPage extends React.Component { dataIndex: 'database', key: 'database', width: '120px', - sorter: (a, b) => a.database.localeCompare(b.database), + sorter: true, }, { title: i18next.t("syncer:Table"), dataIndex: 'table', key: 'table', width: '120px', - sorter: (a, b) => a.table.localeCompare(b.table), + sorter: true, }, { title: i18next.t("syncer:Sync interval"), dataIndex: 'syncInterval', key: 'syncInterval', width: '120px', - sorter: (a, b) => a.syncInterval.localeCompare(b.syncInterval), + sorter: true, + ...this.getColumnSearchProps('syncInterval'), }, { title: i18next.t("syncer:Is enabled"), dataIndex: 'isEnabled', key: 'isEnabled', width: '120px', - sorter: (a, b) => a.isEnabled - b.isEnabled, + sorter: true, render: (text, record, index) => { return ( @@ -240,12 +226,10 @@ class SyncerListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getSyncers(page, pageSize), - onShowSizeChange: (current, size) => this.getSyncers(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -257,21 +241,37 @@ class SyncerListPage extends React.Component { )} - loading={syncers === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.syncers) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + if (params.type !== undefined && params.type !== null) { + field = "type"; + value = params.type; + } + this.setState({ loading: true }); + SyncerBackend.getSyncers("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default SyncerListPage; diff --git a/web/src/TokenListPage.js b/web/src/TokenListPage.js index 44df318d..b4c48cab 100644 --- a/web/src/TokenListPage.js +++ b/web/src/TokenListPage.js @@ -19,32 +19,10 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as TokenBackend from "./backend/TokenBackend"; import i18next from "i18next"; +import * as ResourceBackend from "./backend/ResourceBackend"; +import BaseListPage from "./BaseListPage"; -class TokenListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - tokens: null, - total: 0, - }; - } - - UNSAFE_componentWillMount() { - this.getTokens(1, 10); - } - - getTokens(page, pageSize) { - TokenBackend.getTokens("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - tokens: res.data, - total: res.data2 - }); - } - }); - } +class TokenListPage extends BaseListPage { newToken() { const randomName = Setting.getRandomName(); @@ -67,10 +45,6 @@ class TokenListPage extends React.Component { TokenBackend.addToken(newToken) .then((res) => { Setting.showMessage("success", `Token added successfully`); - this.setState({ - tokens: Setting.prependRow(this.state.tokens, newToken), - total: this.state.total + 1 - }); this.props.history.push(`/tokens/${newToken.name}`); } ) @@ -80,12 +54,12 @@ class TokenListPage extends React.Component { } deleteToken(i) { - TokenBackend.deleteToken(this.state.tokens[i]) + TokenBackend.deleteToken(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Token deleted successfully`); this.setState({ - tokens: Setting.deleteRow(this.state.tokens, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -102,7 +76,8 @@ class TokenListPage extends React.Component { key: 'name', width: (Setting.isMobile()) ? "100px" : "300px", fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -116,7 +91,7 @@ class TokenListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '160px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -126,7 +101,8 @@ class TokenListPage extends React.Component { dataIndex: 'application', key: 'application', width: '120px', - sorter: (a, b) => a.application.localeCompare(b.application), + sorter: true, + ...this.getColumnSearchProps('application'), render: (text, record, index) => { return ( @@ -140,7 +116,8 @@ class TokenListPage extends React.Component { dataIndex: 'organization', key: 'organization', width: '120px', - sorter: (a, b) => a.organization.localeCompare(b.organization), + sorter: true, + ...this.getColumnSearchProps('organization'), render: (text, record, index) => { return ( @@ -154,7 +131,8 @@ class TokenListPage extends React.Component { dataIndex: 'user', key: 'user', width: '120px', - sorter: (a, b) => a.user.localeCompare(b.user), + sorter: true, + ...this.getColumnSearchProps('user'), render: (text, record, index) => { return ( @@ -168,7 +146,8 @@ class TokenListPage extends React.Component { dataIndex: 'code', key: 'code', // width: '150px', - sorter: (a, b) => a.code.localeCompare(b.code), + sorter: true, + ...this.getColumnSearchProps('code'), render: (text, record, index) => { return Setting.getClickable(text); } @@ -178,8 +157,9 @@ class TokenListPage extends React.Component { dataIndex: 'accessToken', key: 'accessToken', // width: '150px', - sorter: (a, b) => a.accessToken.localeCompare(b.accessToken), + sorter: true, ellipsis: true, + ...this.getColumnSearchProps('accessToken'), render: (text, record, index) => { return Setting.getClickable(text); } @@ -189,14 +169,16 @@ class TokenListPage extends React.Component { dataIndex: 'expiresIn', key: 'expiresIn', width: '120px', - sorter: (a, b) => a.expiresIn - b.expiresIn, + sorter: true, + ...this.getColumnSearchProps('expiresIn'), }, { title: i18next.t("token:Scope"), dataIndex: 'scope', key: 'scope', width: '100px', - sorter: (a, b) => a.scope.localeCompare(b.scope), + sorter: true, + ...this.getColumnSearchProps('scope'), }, // { // title: i18next.t("token:Token type"), @@ -228,12 +210,10 @@ class TokenListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getTokens(page, pageSize), - onShowSizeChange: (current, size) => this.getTokens(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -245,21 +225,33 @@ class TokenListPage extends React.Component { )} - loading={tokens === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.tokens) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + this.setState({ loading: true }); + TokenBackend.getTokens("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default TokenListPage; diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index 8650361b..f496b22d 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -19,46 +19,26 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as UserBackend from "./backend/UserBackend"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class UserListPage extends React.Component { +class UserListPage extends BaseListPage { constructor(props) { super(props); this.state = { classes: props, - users: null, organizationName: props.match.params.organizationName, - total: 0, + data: [], + pagination: { + current: 1, + pageSize: 10, + }, + loading: false, + searchText: '', + searchedColumn: '', }; } - UNSAFE_componentWillMount() { - this.getUsers(1, 10); - } - - getUsers(page, pageSize) { - if (this.state.organizationName === undefined) { - UserBackend.getGlobalUsers(page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - users: res.data, - total: res.data2 - }); - } - }); - } else { - UserBackend.getUsers(this.state.organizationName, page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - users: res.data, - total: res.data2 - }); - } - }); - } - } - newUser() { const randomName = Setting.getRandomName(); return { @@ -90,10 +70,6 @@ class UserListPage extends React.Component { UserBackend.addUser(newUser) .then((res) => { Setting.showMessage("success", `User added successfully`); - this.setState({ - users: Setting.prependRow(this.state.users, newUser), - total: this.state.total + 1 - }); this.props.history.push(`/users/${newUser.owner}/${newUser.name}`); } ) @@ -103,12 +79,12 @@ class UserListPage extends React.Component { } deleteUser(i) { - UserBackend.deleteUser(this.state.users[i]) + UserBackend.deleteUser(this.state.data[i]) .then((res) => { Setting.showMessage("success", `User deleted successfully`); this.setState({ - users: Setting.deleteRow(this.state.users, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -132,7 +108,8 @@ class UserListPage extends React.Component { key: 'owner', width: (Setting.isMobile()) ? "100px" : "120px", fixed: 'left', - sorter: (a, b) => a.owner.localeCompare(b.owner), + sorter: true, + ...this.getColumnSearchProps('owner'), render: (text, record, index) => { return ( @@ -147,7 +124,8 @@ class UserListPage extends React.Component { key: 'signupApplication', width: (Setting.isMobile()) ? "100px" : "120px", fixed: 'left', - sorter: (a, b) => a.owner.localeCompare(b.owner), + sorter: true, + ...this.getColumnSearchProps('signupApplication'), render: (text, record, index) => { return ( @@ -162,7 +140,8 @@ class UserListPage extends React.Component { key: 'name', width: (Setting.isMobile()) ? "80px" : "100px", fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -176,7 +155,7 @@ class UserListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '160px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -186,7 +165,8 @@ class UserListPage extends React.Component { dataIndex: 'displayName', key: 'displayName', width: '100px', - sorter: (a, b) => a.displayName.localeCompare(b.displayName), + sorter: true, + ...this.getColumnSearchProps('displayName'), }, { title: i18next.t("general:Avatar"), @@ -206,7 +186,8 @@ class UserListPage extends React.Component { dataIndex: 'email', key: 'email', width: '160px', - sorter: (a, b) => a.email.localeCompare(b.email), + sorter: true, + ...this.getColumnSearchProps('email'), render: (text, record, index) => { return (
@@ -220,7 +201,8 @@ class UserListPage extends React.Component { dataIndex: 'phone', key: 'phone', width: '120px', - sorter: (a, b) => a.phone.localeCompare(b.phone), + sorter: true, + ...this.getColumnSearchProps('phone'), }, // { // title: 'Phone', @@ -234,28 +216,31 @@ class UserListPage extends React.Component { dataIndex: 'affiliation', key: 'affiliation', width: '120px', - sorter: (a, b) => a.affiliation.localeCompare(b.affiliation), + sorter: true, + ...this.getColumnSearchProps('affiliation'), }, { title: i18next.t("user:Country/Region"), dataIndex: 'region', key: 'region', width: '120px', - sorter: (a, b) => a.region.localeCompare(b.region), + sorter: true, + ...this.getColumnSearchProps('region'), }, { title: i18next.t("user:Tag"), dataIndex: 'tag', key: 'tag', width: '100px', - sorter: (a, b) => a.tag.localeCompare(b.tag), + sorter: true, + ...this.getColumnSearchProps('tag'), }, { title: i18next.t("user:Is admin"), dataIndex: 'isAdmin', key: 'isAdmin', width: '110px', - sorter: (a, b) => a.isAdmin - b.isAdmin, + sorter: true, render: (text, record, index) => { return ( @@ -267,7 +252,7 @@ class UserListPage extends React.Component { dataIndex: 'isGlobalAdmin', key: 'isGlobalAdmin', width: '110px', - sorter: (a, b) => a.isGlobalAdmin - b.isGlobalAdmin, + sorter: true, render: (text, record, index) => { return ( @@ -279,7 +264,7 @@ class UserListPage extends React.Component { dataIndex: 'isForbidden', key: 'isForbidden', width: '110px', - sorter: (a, b) => a.isForbidden - b.isForbidden, + sorter: true, render: (text, record, index) => { return ( @@ -291,7 +276,7 @@ class UserListPage extends React.Component { dataIndex: 'isDeleted', key: 'isDeleted', width: '110px', - sorter: (a, b) => a.isDeleted - b.isDeleted, + sorter: true, render: (text, record, index) => { return ( @@ -321,12 +306,10 @@ class UserListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getUsers(page, pageSize), - onShowSizeChange: (current, size) => this.getUsers(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -338,21 +321,51 @@ class UserListPage extends React.Component { )} - loading={users === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.users) - } -
- ); - } + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + this.setState({ loading: true }); + if (this.state.organizationName === undefined) { + UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); + } + }); + } else { + UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); + } + }); + } + }; } export default UserListPage; diff --git a/web/src/WebhookListPage.js b/web/src/WebhookListPage.js index 4613b57d..0cb40347 100644 --- a/web/src/WebhookListPage.js +++ b/web/src/WebhookListPage.js @@ -19,32 +19,10 @@ import moment from "moment"; import * as Setting from "./Setting"; import * as WebhookBackend from "./backend/WebhookBackend"; import i18next from "i18next"; +import BaseListPage from "./BaseListPage"; +import * as ProviderBackend from "./backend/ProviderBackend"; -class WebhookListPage extends React.Component { - constructor(props) { - super(props); - this.state = { - classes: props, - webhooks: null, - total: 0, - }; - } - - UNSAFE_componentWillMount() { - this.getWebhooks(1, 10); - } - - getWebhooks(page, pageSize) { - WebhookBackend.getWebhooks("admin", page, pageSize) - .then((res) => { - if (res.status === "ok") { - this.setState({ - webhooks: res.data, - total: res.data2 - }); - } - }); - } +class WebhookListPage extends BaseListPage { newWebhook() { const randomName = Setting.getRandomName(); @@ -64,10 +42,6 @@ class WebhookListPage extends React.Component { WebhookBackend.addWebhook(newWebhook) .then((res) => { Setting.showMessage("success", `Webhook added successfully`); - this.setState({ - webhooks: Setting.prependRow(this.state.webhooks, newWebhook), - total: this.state.total + 1 - }); this.props.history.push(`/webhooks/${newWebhook.name}`); } ) @@ -77,12 +51,12 @@ class WebhookListPage extends React.Component { } deleteWebhook(i) { - WebhookBackend.deleteWebhook(this.state.webhooks[i]) + WebhookBackend.deleteWebhook(this.state.data[i]) .then((res) => { Setting.showMessage("success", `Webhook deleted successfully`); this.setState({ - webhooks: Setting.deleteRow(this.state.webhooks, i), - total: this.state.total - 1 + data: Setting.deleteRow(this.state.data, i), + pagination: {total: this.state.pagination.total - 1}, }); } ) @@ -98,7 +72,8 @@ class WebhookListPage extends React.Component { dataIndex: 'organization', key: 'organization', width: '80px', - sorter: (a, b) => a.organization.localeCompare(b.organization), + sorter: true, + ...this.getColumnSearchProps('organization'), render: (text, record, index) => { return ( @@ -113,7 +88,8 @@ class WebhookListPage extends React.Component { key: 'name', width: '150px', fixed: 'left', - sorter: (a, b) => a.name.localeCompare(b.name), + sorter: true, + ...this.getColumnSearchProps('name'), render: (text, record, index) => { return ( @@ -127,7 +103,7 @@ class WebhookListPage extends React.Component { dataIndex: 'createdTime', key: 'createdTime', width: '180px', - sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), + sorter: true, render: (text, record, index) => { return Setting.getFormattedDate(text); } @@ -137,7 +113,8 @@ class WebhookListPage extends React.Component { dataIndex: 'url', key: 'url', width: '300px', - sorter: (a, b) => a.url.localeCompare(b.url), + sorter: true, + ...this.getColumnSearchProps('url'), render: (text, record, index) => { return (
@@ -153,14 +130,20 @@ class WebhookListPage extends React.Component { dataIndex: 'contentType', key: 'contentType', width: '150px', - sorter: (a, b) => a.contentType.localeCompare(b.contentType), + sorter: true, + filterMultiple: false, + filters: [ + {text: 'application/json', value: 'application/json'}, + {text: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded'}, + ] }, { title: i18next.t("webhook:Events"), dataIndex: 'events', key: 'events', // width: '100px', - sorter: (a, b) => a.events.localeCompare(b.events), + sorter: true, + ...this.getColumnSearchProps('events'), render: (text, record, index) => { return Setting.getTags(text); } @@ -188,12 +171,10 @@ class WebhookListPage extends React.Component { ]; const paginationProps = { - total: this.state.total, + total: this.state.pagination.total, showQuickJumper: true, showSizeChanger: true, - showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.total), - onChange: (page, pageSize) => this.getWebhooks(page, pageSize), - onShowSizeChange: (current, size) => this.getWebhooks(current, size), + showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), }; return ( @@ -205,21 +186,37 @@ class WebhookListPage extends React.Component { )} - loading={webhooks === null} + loading={this.state.loading} + onChange={this.handleTableChange} /> ); } - render() { - return ( -
- { - this.renderTable(this.state.webhooks) + fetch = (params = {}) => { + let field = params.searchedColumn, value = params.searchText; + let sortField = params.sortField, sortOrder = params.sortOrder; + if (params.contentType !== undefined && params.contentType !== null) { + field = "contentType"; + value = params.contentType; + } + this.setState({ loading: true }); + WebhookBackend.getWebhooks("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) + .then((res) => { + if (res.status === "ok") { + this.setState({ + loading: false, + data: res.data, + pagination: { + ...params.pagination, + total: res.data2, + }, + searchText: params.searchText, + searchedColumn: params.searchedColumn, + }); } -
- ); - } + }); + }; } export default WebhookListPage; diff --git a/web/src/backend/ApplicationBackend.js b/web/src/backend/ApplicationBackend.js index f1a5cc63..3efbe549 100644 --- a/web/src/backend/ApplicationBackend.js +++ b/web/src/backend/ApplicationBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getApplications(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getApplications(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-applications?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/OrganizationBackend.js b/web/src/backend/OrganizationBackend.js index 4864ea4d..d2494925 100644 --- a/web/src/backend/OrganizationBackend.js +++ b/web/src/backend/OrganizationBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getOrganizations(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-organizations?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getOrganizations(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-organizations?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/ProviderBackend.js b/web/src/backend/ProviderBackend.js index 0ddbdcef..cd1611a0 100644 --- a/web/src/backend/ProviderBackend.js +++ b/web/src/backend/ProviderBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getProviders(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-providers?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getProviders(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-providers?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/RecordBackend.js b/web/src/backend/RecordBackend.js index a49e3c71..72269e57 100644 --- a/web/src/backend/RecordBackend.js +++ b/web/src/backend/RecordBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getRecords(page, pageSize) { - return fetch(`${Setting.ServerUrl}/api/get-records?pageSize=${pageSize}&p=${page}`, { +export function getRecords(page, pageSize, field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-records?pageSize=${pageSize}&p=${page}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/ResourceBackend.js b/web/src/backend/ResourceBackend.js index 79acb988..91f61f3f 100644 --- a/web/src/backend/ResourceBackend.js +++ b/web/src/backend/ResourceBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getResources(owner, user, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}`, { +export function getResources(owner, user, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/SyncerBackend.js b/web/src/backend/SyncerBackend.js index 21d87b2c..0eec9bf3 100644 --- a/web/src/backend/SyncerBackend.js +++ b/web/src/backend/SyncerBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getSyncers(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-syncers?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getSyncers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-syncers?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/TokenBackend.js b/web/src/backend/TokenBackend.js index 22b7ee57..0100b6fa 100644 --- a/web/src/backend/TokenBackend.js +++ b/web/src/backend/TokenBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getTokens(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-tokens?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getTokens(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-tokens?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index 3ee4313a..a43d1b93 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -15,15 +15,15 @@ import * as Setting from "../Setting"; import i18next from "i18next"; -export function getGlobalUsers(page, pageSize) { - return fetch(`${Setting.ServerUrl}/api/get-global-users?p=${page}&pageSize=${pageSize}`, { +export function getGlobalUsers(page, pageSize, field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-global-users?p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); } -export function getUsers(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getUsers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/src/backend/WebhookBackend.js b/web/src/backend/WebhookBackend.js index c662fafd..92a862ef 100644 --- a/web/src/backend/WebhookBackend.js +++ b/web/src/backend/WebhookBackend.js @@ -14,8 +14,8 @@ import * as Setting from "../Setting"; -export function getWebhooks(owner, page = "", pageSize = "") { - return fetch(`${Setting.ServerUrl}/api/get-webhooks?owner=${owner}&p=${page}&pageSize=${pageSize}`, { +export function getWebhooks(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { + return fetch(`${Setting.ServerUrl}/api/get-webhooks?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { method: "GET", credentials: "include" }).then(res => res.json()); diff --git a/web/yarn.lock b/web/yarn.lock index 78a4ee4b..96ed7dd5 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -5721,6 +5721,11 @@ hex-color-regex@^1.1.0: resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +highlight-words-core@^1.2.0: + version "1.2.2" + resolved "https://registry.npm.taobao.org/highlight-words-core/download/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa" + integrity sha1-Hv9tfZ8KIvFVBCoAeRI3eRse6qo= + history@^4.9.0: version "4.10.1" resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -7479,6 +7484,11 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memoize-one@^4.0.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/memoize-one/download/memoize-one-4.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fmemoize-one%2Fdownload%2Fmemoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906" + integrity sha1-ojh8WMA//yfKOQwxt2Snmt3z+QY= + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -9254,7 +9264,7 @@ prompts@2.4.0, prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -9343,6 +9353,13 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.10.2: + version "6.10.2" + resolved "https://registry.npmmirror.com/qs/download/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe" + integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.2" resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -9873,6 +9890,15 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" +react-highlight-words@^0.17.0: + version "0.17.0" + resolved "https://registry.npm.taobao.org/react-highlight-words/download/react-highlight-words-0.17.0.tgz#e79a559a2de301548339d7216264d6cd0f1eed6f" + integrity sha1-55pVmi3jAVSDOdchYmTWzQ8e7W8= + dependencies: + highlight-words-core "^1.2.0" + memoize-one "^4.0.0" + prop-types "^15.5.8" + react-i18next@^11.8.7: version "11.8.12" resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-11.8.12.tgz#6a9f57277062fb6a6129cad4db5e6198d5c60b58"