mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 21:33:36 +08:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f124ff140 | |||
d5f802ec7d | |||
64d3b7e87f | |||
dfce1bd74c | |||
067ae5448f | |||
9943e3c316 | |||
0c665edcbc | |||
5015bf1c7d | |||
2ec947d488 | |||
10a85f2386 | |||
0d13512eb1 | |||
b60856be5e | |||
4b4c9be71b | |||
e79e3c36d0 | |||
cc8c9b32ef | |||
efdcb3279d | |||
3818492065 | |||
f4890a6a22 | |||
fa45d3083a | |||
caa1ffe3c8 | |||
c88edc4d3e | |||
f5bc76016d | |||
8345295d0c | |||
3bdc0e0d1b | |||
8dcb56ea71 | |||
b95f107a60 | |||
d6c2d0f3e8 | |||
7cfece3019 | |||
3efbcc739d |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
@ -88,7 +88,15 @@ jobs:
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
push: true
|
||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
target: ALLINONE
|
||||
push: true
|
||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||
|
14
Dockerfile
14
Dockerfile
@ -10,6 +10,19 @@ COPY ./web .
|
||||
RUN yarn config set registry https://registry.npm.taobao.org
|
||||
RUN yarn install && yarn run build
|
||||
|
||||
|
||||
FROM debian:latest AS ALLINONE
|
||||
RUN apt update && apt install -y mariadb-server mariadb-client&&mkdir -p web/build && chmod 777 /tmp
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD chmod 777 /tmp && service mariadb start&&\
|
||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
||||
./wait-for-it localhost:3306 -- ./server
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add curl
|
||||
@ -20,3 +33,4 @@ COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD ./wait-for-it db:3306 -- ./server
|
||||
|
||||
|
33
README.md
33
README.md
@ -34,6 +34,9 @@
|
||||
<a href="https://github.com/casbin/casdoor/network">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/casdoor-site">
|
||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
@ -41,8 +44,7 @@
|
||||
Deployed site: https://door.casbin.com/
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run your own casdoor program in a few minutes:smiley:
|
||||
Run your own casdoor program in a few minutes.
|
||||
|
||||
### Download
|
||||
|
||||
@ -117,9 +119,26 @@ go build main.go && sudo ./main
|
||||
|
||||
### Docker
|
||||
|
||||
Casdoor provide 2 kinds of image:
|
||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
||||
|
||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
#### Simple configuration
|
||||
### Start casdoor with casbin/casdoor-all-in-one
|
||||
if the image is not pulled, pull it from dockerhub
|
||||
```shell
|
||||
docker pull casbin/casdoor-all-in-one
|
||||
```
|
||||
Start it with
|
||||
```shell
|
||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
||||
```
|
||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
||||
|
||||
### Start casdoor with casbin/casdoor
|
||||
#### modify the configurations
|
||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
||||
|
||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
||||
@ -138,14 +157,6 @@ docker-compose up
|
||||
|
||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
||||
|
||||
### Docker Hub
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
```bash
|
||||
docker pull casbin/casdoor
|
||||
```
|
||||
|
||||
## Detailed documentation
|
||||
|
||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
||||
|
@ -38,6 +38,7 @@ type RequestForm struct {
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
IdCard string `json:"idCard"`
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
@ -151,6 +152,7 @@ func (c *ApiController) Signup() {
|
||||
Phone: form.Phone,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
Region: form.Region,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce)
|
||||
resp = codeToResponse(code)
|
||||
|
||||
if application.HasPromptPage() {
|
||||
if application.EnableSigninSession || application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
@ -206,7 +206,15 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
} else if provider.Category == "OAuth" {
|
||||
// OAuth
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.ClientId, provider.ClientSecret, form.RedirectUri)
|
||||
|
||||
clientId := provider.ClientId
|
||||
clientSecret := provider.ClientSecret
|
||||
if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") {
|
||||
clientId = provider.ClientId2
|
||||
clientSecret = provider.ClientSecret2
|
||||
}
|
||||
|
||||
idProvider := idp.GetIdProvider(provider.Type, clientId, clientSecret, form.RedirectUri)
|
||||
if idProvider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||
return
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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.GetProviders(owner)
|
||||
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.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())
|
||||
}
|
||||
}
|
||||
@ -53,7 +56,7 @@ func (c *ApiController) GetProviders() {
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetProvider(id)
|
||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
23
cred/bcrypt.go
Normal file
23
cred/bcrypt.go
Normal file
@ -0,0 +1,23 @@
|
||||
package cred
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
type BcryptCredManager struct{}
|
||||
|
||||
func NewBcryptCredManager() *BcryptCredManager {
|
||||
cm := &BcryptCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
||||
return err == nil
|
||||
}
|
@ -15,7 +15,8 @@
|
||||
package cred
|
||||
|
||||
type CredManager interface {
|
||||
GetSealedPassword(password string, userSalt string, organizationSalt string) string
|
||||
GetHashedPassword(password string, userSalt string, organizationSalt string) string
|
||||
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
|
||||
}
|
||||
|
||||
func GetCredManager(passwordType string) CredManager {
|
||||
@ -25,7 +26,8 @@ func GetCredManager(passwordType string) CredManager {
|
||||
return NewSha256SaltCredManager()
|
||||
} else if passwordType == "md5-salt" {
|
||||
return NewMd5UserSaltCredManager()
|
||||
} else if passwordType == "bcrypt" {
|
||||
return NewBcryptCredManager()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -37,8 +37,12 @@ func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getMd5HexDigest(password)
|
||||
res := getMd5HexDigest(hash + userSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
return password
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == plainPwd
|
||||
}
|
||||
|
@ -37,8 +37,12 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash + organizationSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ func TestGetSaltedPassword(t *testing.T) {
|
||||
password := "123456"
|
||||
salt := "123"
|
||||
cm := NewSha256SaltCredManager()
|
||||
fmt.Printf("%s -> %s\n", password, cm.GetSealedPassword(password, "", salt))
|
||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -26,7 +26,8 @@ require (
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
|
15
go.sum
15
go.sum
@ -375,8 +375,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -440,8 +441,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -493,15 +494,17 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
23
idp/goth.go
23
idp/goth.go
@ -21,6 +21,7 @@ import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/providers/amazon"
|
||||
"github.com/markbates/goth/providers/apple"
|
||||
@ -244,11 +245,27 @@ func getUser(gothUser goth.User) *UserInfo {
|
||||
//Some idp return an empty Name
|
||||
//so construct the Name with firstname and lastname or nickname
|
||||
if user.Username == "" {
|
||||
user.Username = fmt.Sprintf("%v%v", gothUser.FirstName, gothUser.LastName)
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.Username = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.Username = gothUser.NickName
|
||||
}
|
||||
}
|
||||
if user.Username == "" {
|
||||
user.Username = gothUser.NickName
|
||||
if user.DisplayName == "" {
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.DisplayName = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.DisplayName = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func getName(firstName, lastName string) string {
|
||||
if util.IsChinese(firstName) || util.IsChinese(lastName) {
|
||||
return fmt.Sprintf("%s%s", lastName, firstName)
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s", firstName, lastName)
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,10 @@ import (
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/conf"
|
||||
"github.com/casbin/casdoor/util"
|
||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@ -73,25 +75,13 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) createDatabase() error {
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Adapter) open() {
|
||||
if a.driverName != "postgres" {
|
||||
if err := a.createDatabase(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dataSourceName := a.dataSourceName + a.dbName
|
||||
if a.driverName != "mysql" {
|
||||
dataSourceName = a.dataSourceName
|
||||
}
|
||||
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -160,3 +150,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
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -24,17 +26,18 @@ type Application struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@ -51,8 +54,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)
|
||||
}
|
||||
@ -70,9 +77,20 @@ 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)
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
func getApplicationsByOrganizationName(owner string, organization string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -88,7 +106,7 @@ func getProviderMap(owner string) map[string]*Provider {
|
||||
// continue
|
||||
//}
|
||||
|
||||
m[provider.Name] = getMaskedProvider(provider)
|
||||
m[provider.Name] = GetMaskedProvider(provider)
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -206,6 +224,10 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "app-built-in" {
|
||||
application.Name = name
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
@ -234,6 +256,10 @@ func AddApplication(application *Application) bool {
|
||||
}
|
||||
|
||||
func DeleteApplication(application *Application) bool {
|
||||
if application.Name == "app-built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -241,3 +267,7 @@ func DeleteApplication(application *Application) bool {
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (application *Application) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
}
|
||||
|
@ -107,12 +107,13 @@ func CheckPassword(user *User, password string) string {
|
||||
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" && organization.MasterPassword == password {
|
||||
return ""
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
sealedPassword := credManager.GetSealedPassword(password, user.PasswordSalt, organization.PasswordSalt)
|
||||
if user.Password == sealedPassword {
|
||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
return "password incorrect"
|
||||
|
@ -50,26 +50,28 @@ func initBuiltInUser() {
|
||||
}
|
||||
|
||||
user = &User{
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ranking: 1,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
Properties: make(map[string]string),
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ranking: 1,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: "built-in-app",
|
||||
CreatedIp: "127.0.0.1",
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
AddUser(user)
|
||||
}
|
||||
|
@ -15,6 +15,9 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/cred"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -35,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)
|
||||
}
|
||||
@ -54,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)
|
||||
}
|
||||
@ -111,6 +119,26 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "built-in" {
|
||||
organization.Name = name
|
||||
}
|
||||
|
||||
if name != organization.Name {
|
||||
applications := getApplicationsByOrganizationName("admin", name)
|
||||
for _, application := range applications {
|
||||
application.Organization = organization.Name
|
||||
UpdateApplication(application.GetId(), application)
|
||||
}
|
||||
}
|
||||
|
||||
if organization.MasterPassword != "" {
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||
organization.MasterPassword = hashedPassword
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -129,6 +157,10 @@ func AddOrganization(organization *Organization) bool {
|
||||
}
|
||||
|
||||
func DeleteOrganization(organization *Organization) bool {
|
||||
if organization.Name == "built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -26,12 +26,14 @@ type Provider struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
@ -56,22 +58,34 @@ type Provider struct {
|
||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||
}
|
||||
|
||||
func getMaskedProvider(provider *Provider) *Provider {
|
||||
p := &Provider{
|
||||
Owner: provider.Owner,
|
||||
Name: provider.Name,
|
||||
CreatedTime: provider.CreatedTime,
|
||||
DisplayName: provider.DisplayName,
|
||||
Category: provider.Category,
|
||||
Type: provider.Type,
|
||||
Method: provider.Method,
|
||||
ClientId: provider.ClientId,
|
||||
func GetMaskedProvider(provider *Provider) *Provider {
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
|
||||
if provider.ClientSecret != "" {
|
||||
provider.ClientSecret = "***"
|
||||
}
|
||||
if provider.ClientSecret2 != "" {
|
||||
provider.ClientSecret2 = "***"
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func GetProviderCount(owner string) int {
|
||||
count, err := adapter.Engine.Count(&Provider{Owner: owner})
|
||||
func GetMaskedProviders(providers []*Provider) []*Provider {
|
||||
for _, provider := range providers {
|
||||
provider = GetMaskedProvider(provider)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -89,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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
@ -141,6 +146,10 @@ func GetRecordsByField(record *Record) []*Record {
|
||||
func SendWebhooks(record *Record) error {
|
||||
webhooks := getWebhooksByOrganization(record.Organization)
|
||||
for _, webhook := range webhooks {
|
||||
if !webhook.IsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
matched := false
|
||||
for _, event := range webhook.Events {
|
||||
if record.Action == event {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ type Syncer struct {
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
|
||||
@ -53,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)
|
||||
}
|
||||
@ -72,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)
|
||||
}
|
||||
@ -158,3 +164,19 @@ func DeleteSyncer(syncer *Syncer) bool {
|
||||
func (syncer *Syncer) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", syncer.Owner, syncer.Name)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTableColumnsTypeMap() map[string]string {
|
||||
m := map[string]string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
m[tableColumn.Name] = tableColumn.Type
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTable() string {
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
return fmt.Sprintf("[%s]", syncer.Table)
|
||||
} else {
|
||||
return syncer.Table
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,10 @@ func (syncer *Syncer) syncUsers() {
|
||||
oUsers, oUserMap := syncer.getOriginalUserMap()
|
||||
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
||||
|
||||
_, affiliationMap := syncer.getAffiliationMap()
|
||||
var affiliationMap map[int]string
|
||||
if syncer.AffiliationTable != "" {
|
||||
_, affiliationMap = syncer.getAffiliationMap()
|
||||
}
|
||||
|
||||
newUsers := []*User{}
|
||||
for _, oUser := range oUsers {
|
||||
@ -74,7 +77,9 @@ func (syncer *Syncer) syncUsers() {
|
||||
for _, user := range users {
|
||||
id := user.Id
|
||||
if _, ok := oUserMap[id]; !ok {
|
||||
panic(fmt.Sprintf("New original user: cannot create now, user = %v", user))
|
||||
newOUser := syncer.createOriginalUserFromUser(user)
|
||||
syncer.addUser(newOUser)
|
||||
fmt.Printf("New oUser: %v\n", newOUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -27,7 +26,7 @@ import (
|
||||
type OriginalUser = User
|
||||
|
||||
func (syncer *Syncer) getOriginalUsers() []*OriginalUser {
|
||||
sql := fmt.Sprintf("select * from %s", syncer.Table)
|
||||
sql := fmt.Sprintf("select * from %s", syncer.getTable())
|
||||
results, err := syncer.Adapter.Engine.QueryString(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -48,7 +47,15 @@ func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*Origina
|
||||
|
||||
func (syncer *Syncer) addUser(user *OriginalUser) bool {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
affected, err := syncer.Adapter.Engine.Table(syncer.Table).Insert(m)
|
||||
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
|
||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -56,7 +63,7 @@ func (syncer *Syncer) addUser(user *OriginalUser) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalColumns() []string {
|
||||
/*func (syncer *Syncer) getOriginalColumns() []string {
|
||||
res := []string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
if tableColumn.CasdoorName != "Id" {
|
||||
@ -64,7 +71,7 @@ func (syncer *Syncer) getOriginalColumns() []string {
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}*/
|
||||
|
||||
func (syncer *Syncer) getCasdoorColumns() []string {
|
||||
res := []string{}
|
||||
@ -79,8 +86,17 @@ func (syncer *Syncer) getCasdoorColumns() []string {
|
||||
|
||||
func (syncer *Syncer) updateUser(user *OriginalUser) bool {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
columns := syncer.getOriginalColumns()
|
||||
affected, err := syncer.Adapter.Engine.Table(syncer.Table).ID(syncer.TablePrimaryKey).Cols(columns...).Update(m)
|
||||
pkValue := m[syncer.TablePrimaryKey]
|
||||
delete(m, syncer.TablePrimaryKey)
|
||||
setString := syncer.getSqlSetStringFromMap(m)
|
||||
|
||||
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue)
|
||||
res, err := syncer.Adapter.Engine.Exec(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -90,7 +106,7 @@ func (syncer *Syncer) updateUser(user *OriginalUser) bool {
|
||||
|
||||
func (syncer *Syncer) updateUserForOriginalFields(user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
||||
oldUser := getUser(owner, name)
|
||||
oldUser := getUserById(owner, name)
|
||||
if oldUser == nil {
|
||||
return false
|
||||
}
|
||||
@ -101,7 +117,7 @@ func (syncer *Syncer) updateUserForOriginalFields(user *User) bool {
|
||||
|
||||
columns := syncer.getCasdoorColumns()
|
||||
columns = append(columns, "affiliation", "hash", "pre_hash")
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols(columns...).Update(user)
|
||||
affected, err := adapter.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -124,8 +140,18 @@ func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
||||
|
||||
func (syncer *Syncer) initAdapter() {
|
||||
if syncer.Adapter == nil {
|
||||
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
syncer.Adapter = NewAdapter(beego.AppConfig.String("driverName"), dataSourceName, syncer.Database)
|
||||
var dataSourceName string
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
||||
} else {
|
||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
}
|
||||
|
||||
if !isCloudIntranet {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||
}
|
||||
|
||||
syncer.Adapter = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
|
||||
return avatar
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(avatar, "https://") {
|
||||
if !strings.HasPrefix(avatar, "http") {
|
||||
return fmt.Sprintf("%s%s", syncer.AvatarBaseUrl, avatar)
|
||||
}
|
||||
return avatar
|
||||
@ -43,31 +43,42 @@ func (syncer *Syncer) getPartialAvatarUrl(avatar string) string {
|
||||
func (syncer *Syncer) createUserFromOriginalUser(originalUser *OriginalUser, affiliationMap map[int]string) *User {
|
||||
user := *originalUser
|
||||
user.Owner = syncer.Organization
|
||||
|
||||
if user.Name == "" {
|
||||
user.Name = originalUser.Id
|
||||
}
|
||||
|
||||
if user.CreatedTime == "" {
|
||||
user.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
if user.Type == "" {
|
||||
user.Type = "normal-user"
|
||||
}
|
||||
|
||||
user.Avatar = syncer.getFullAvatarUrl(user.Avatar)
|
||||
if originalUser.Score != 0 {
|
||||
affiliation, ok := affiliationMap[originalUser.Score]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Affiliation not found: %d", originalUser.Score))
|
||||
|
||||
if affiliationMap != nil {
|
||||
if originalUser.Score != 0 {
|
||||
affiliation, ok := affiliationMap[originalUser.Score]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Affiliation not found: %d", originalUser.Score))
|
||||
}
|
||||
user.Affiliation = affiliation
|
||||
}
|
||||
user.Affiliation = affiliation
|
||||
}
|
||||
|
||||
if user.Properties == nil {
|
||||
user.Properties = map[string]string{}
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func (syncer *Syncer) createOriginalUserFromUser(user *User) *OriginalUser {
|
||||
return user
|
||||
originalUser := *user
|
||||
originalUser.Avatar = syncer.getPartialAvatarUrl(user.Avatar)
|
||||
return &originalUser
|
||||
}
|
||||
|
||||
func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
|
||||
@ -148,7 +159,11 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
|
||||
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
|
||||
users := []*OriginalUser{}
|
||||
for _, result := range results {
|
||||
originalUser := &OriginalUser{}
|
||||
originalUser := &OriginalUser{
|
||||
Address: []string{},
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
|
||||
}
|
||||
@ -202,3 +217,34 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
|
||||
|
||||
return m2
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getSqlSetStringFromMap(m map[string]string) string {
|
||||
typeMap := syncer.getTableColumnsTypeMap()
|
||||
|
||||
tokens := []string{}
|
||||
for k, v := range m {
|
||||
token := fmt.Sprintf("%s = %s", k, v)
|
||||
if typeMap[k] == "string" {
|
||||
token = fmt.Sprintf("%s = '%s'", k, v)
|
||||
}
|
||||
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
return strings.Join(tokens, ", ")
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getSqlKeyValueStringFromMap(m map[string]string) (string, string) {
|
||||
typeMap := syncer.getTableColumnsTypeMap()
|
||||
|
||||
keys := []string{}
|
||||
values := []string{}
|
||||
for k, v := range m {
|
||||
if typeMap[k] == "string" {
|
||||
v = fmt.Sprintf("'%s'", v)
|
||||
}
|
||||
|
||||
keys = append(keys, k)
|
||||
values = append(values, v)
|
||||
}
|
||||
return strings.Join(keys, ", "), strings.Join(values, ", ")
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
|
||||
|
||||
m := map[string]*User{}
|
||||
for _, user := range users {
|
||||
m[user.Name] = user
|
||||
m[user.Id] = user
|
||||
}
|
||||
return users, m
|
||||
}
|
||||
|
@ -46,15 +46,20 @@ type Token struct {
|
||||
}
|
||||
|
||||
type TokenWrapper struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
IdToken string `json:"id_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
AccessToken string `json:"access_token"`
|
||||
IdToken string `json:"id_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
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 +77,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)
|
||||
}
|
||||
@ -185,6 +191,12 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
if user.IsForbidden {
|
||||
return &Code{
|
||||
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
|
||||
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
if msg != "" {
|
||||
@ -279,11 +291,12 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
}
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper
|
||||
@ -334,6 +347,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
}
|
||||
// generate a new token
|
||||
user := getUser(application.Owner, token.User)
|
||||
if user.IsForbidden {
|
||||
return &Code{
|
||||
Message: "error: the user is forbidden to sign in, please contact the administrator",
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -32,8 +32,8 @@ type User struct {
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(255)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(255)" json:"permanentAvatar"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100)" json:"email"`
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
@ -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)
|
||||
}
|
||||
@ -181,6 +191,24 @@ func getUser(owner string, name string) *User {
|
||||
}
|
||||
}
|
||||
|
||||
func getUserById(owner string, id string) *User {
|
||||
if owner == "" || id == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := User{Owner: owner, Id: id}
|
||||
existed, err := adapter.Engine.Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByEmail(owner string, email string) *User {
|
||||
if owner == "" || email == "" {
|
||||
return nil
|
||||
|
@ -33,7 +33,7 @@ func (user *User) UpdateUserHash() {
|
||||
func (user *User) UpdateUserPassword(organization *Organization) {
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
sealedPassword := credManager.GetSealedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = sealedPassword
|
||||
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
|
||||
user.Password = hashedPassword
|
||||
}
|
||||
}
|
||||
|
@ -21,20 +21,32 @@ import (
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Header struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||
Events []string `xorm:"varchar(100)" json:"events"`
|
||||
|
||||
Organization string `xorm:"varchar(100) index" json:"organization"`
|
||||
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ContentType string `xorm:"varchar(100)" json:"contentType"`
|
||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||
Events []string `xorm:"varchar(100)" json:"events"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
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 +64,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)
|
||||
}
|
||||
|
@ -26,13 +26,17 @@ func sendWebhook(webhook *Webhook, record *Record) error {
|
||||
|
||||
body := strings.NewReader(util.StructToJson(record))
|
||||
|
||||
req, err := http.NewRequest("POST", webhook.Url, body)
|
||||
req, err := http.NewRequest(webhook.Method, webhook.Url, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", webhook.ContentType)
|
||||
|
||||
for _, header := range webhook.Headers {
|
||||
req.Header.Set(header.Name, header.Value)
|
||||
}
|
||||
|
||||
_, err = client.Do(req)
|
||||
return err
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@ -162,3 +163,32 @@ 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[:]))
|
||||
}
|
||||
|
||||
func IsChinese(str string) bool {
|
||||
var flag bool
|
||||
for _, v := range str {
|
||||
if unicode.Is(unicode.Han, v) {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
project_id: '463556'
|
||||
project_id: '491513'
|
||||
api_token_env: 'CROWDIN_PERSONAL_TOKEN'
|
||||
preserve_hierarchy: true
|
||||
files: [
|
||||
|
@ -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",
|
||||
|
@ -132,7 +132,7 @@ class ApplicationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.application.name} onChange={e => {
|
||||
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
|
||||
this.updateApplicationField('name', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@ -291,6 +291,16 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
|
||||
this.updateApplicationField('enableSigninSession', checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
|
||||
|
@ -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();
|
||||
@ -57,7 +35,8 @@ class ApplicationListPage extends React.Component {
|
||||
logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||
enablePassword: true,
|
||||
enableSignUp: true,
|
||||
EnableCodeSignin: false,
|
||||
enableSigninSession: true,
|
||||
enableCodeSignin: false,
|
||||
providers: [],
|
||||
signupItems: [
|
||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||
@ -80,10 +59,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 +68,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 +90,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 (
|
||||
<Link to={`/applications/${text}`}>
|
||||
@ -129,7 +105,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 +115,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 +136,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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -172,6 +150,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;
|
||||
@ -236,8 +215,9 @@ class ApplicationListPage extends React.Component {
|
||||
<Popconfirm
|
||||
title={`Sure to delete application: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteApplication(index)}
|
||||
disabled={record.name === "app-built-in"}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{marginBottom: '10px'}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -246,12 +226,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 (
|
||||
@ -263,21 +241,33 @@ class ApplicationListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={applications === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ApplicationListPage;
|
||||
|
138
web/src/BaseListPage.js
Normal file
138
web/src/BaseListPage.js
Normal file
@ -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 }) => (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
ref={node => {
|
||||
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' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
|
||||
icon={<SearchOutlined />}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
confirm({ closeDropdown: false });
|
||||
this.setState({
|
||||
searchText: selectedKeys[0],
|
||||
searchedColumn: dataIndex,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
|
||||
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 ? (
|
||||
<Highlighter
|
||||
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||
searchWords={[this.state.searchText]}
|
||||
autoEscape
|
||||
textToHighlight={text ? text.toString() : ''}
|
||||
/>
|
||||
) : (
|
||||
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 (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.data)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseListPage;
|
@ -94,7 +94,7 @@ class OrganizationEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.name} onChange={e => {
|
||||
<Input value={this.state.organization.name} disabled={this.state.organization.name === "built-in"} onChange={e => {
|
||||
this.updateOrganizationField('name', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@ -153,7 +153,7 @@ class OrganizationEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}>
|
||||
{
|
||||
['plain', 'salt', 'md5-salt']
|
||||
['plain', 'salt', 'md5-salt', 'bcrypt']
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
|
@ -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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -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 (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
@ -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 (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -210,8 +193,9 @@ class OrganizationListPage extends React.Component {
|
||||
<Popconfirm
|
||||
title={`Sure to delete organization: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteOrganization(index)}
|
||||
disabled={record.name === "built-in"}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{marginBottom: '10px'}} disabled={record.name === "built-in"} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
@ -220,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 (
|
||||
@ -237,21 +219,37 @@ class OrganizationListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={organizations === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default OrganizationListPage;
|
||||
|
@ -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,12 +198,13 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
})}>
|
||||
{
|
||||
this.getProviderTypeOptions(this.state.provider).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
|
||||
Setting.getProviderTypeOptions(this.state.provider.category).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{this.state.provider.type === "WeCom" || this.state.provider.type === "WeChat" ? (
|
||||
{
|
||||
this.state.provider.type !== "WeCom" ? null : (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
@ -272,7 +219,8 @@ class ProviderEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel()}
|
||||
@ -293,6 +241,32 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.type !== "WeChat" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId2} onChange={e => {
|
||||
this.updateProviderField('clientId2', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret2} onChange={e => {
|
||||
this.updateProviderField('clientSecret2', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
{this.state.provider.category === "Storage" ? (
|
||||
<div>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
|
@ -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 (
|
||||
<Link to={`/providers/${text}`}>
|
||||
@ -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 (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
@ -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 {
|
||||
<Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={providers === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ProviderListPage;
|
||||
|
@ -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 (
|
||||
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/users/${record.organization}/${record.user}`}>
|
||||
@ -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")}
|
||||
</div>
|
||||
)}
|
||||
loading={records === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default RecordListPage;
|
||||
|
@ -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 (
|
||||
<Link to={`/providers/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/applications/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/users/${record.owner}/${record.user}`}>
|
||||
@ -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 {
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
loading={resources === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ResourceListPage;
|
||||
|
@ -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;
|
||||
@ -558,3 +612,7 @@ export function getApplicationOrgName(application) {
|
||||
export function getRandomName() {
|
||||
return Math.random().toString(36).slice(-6);
|
||||
}
|
||||
|
||||
export function getRandomNumber() {
|
||||
return Math.random().toString(10).slice(-11);
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ class SignupTable extends React.Component {
|
||||
{id: 'Display name', name: 'Display name'},
|
||||
{id: 'Affiliation', name: 'Affiliation'},
|
||||
{id: 'Country/Region', name: 'Country/Region'},
|
||||
{id: 'ID card', name: 'ID card'},
|
||||
{id: 'Email', name: 'Email'},
|
||||
{id: 'Password', name: 'Password'},
|
||||
{id: 'Confirm password', name: 'Confirm password'},
|
||||
|
@ -158,6 +158,24 @@ class SyncerEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.databaseType} onChange={(value => {this.updateSyncerField('databaseType', value);})}>
|
||||
{
|
||||
[
|
||||
{id: 'mysql', name: 'MySQL'},
|
||||
{id: 'postgres', name: 'PostgreSQL'},
|
||||
{id: 'mssql', name: 'SQL Server'},
|
||||
{id: 'oracle', name: 'Oracle'},
|
||||
{id: 'sqlite3', name: 'Sqlite 3'},
|
||||
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
||||
@ -232,7 +250,7 @@ class SyncerEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Is enabled"), i18next.t("syncer:Is enabled - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.syncer.isEnabled} onChange={checked => {
|
||||
|
@ -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();
|
||||
@ -58,6 +36,7 @@ class SyncerListPage extends React.Component {
|
||||
port: 3306,
|
||||
user: "root",
|
||||
password: "123456",
|
||||
databaseType: "mysql",
|
||||
database: "dbName",
|
||||
table: "tableName",
|
||||
tableColumns: [],
|
||||
@ -73,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}`);
|
||||
}
|
||||
)
|
||||
@ -86,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},
|
||||
});
|
||||
}
|
||||
)
|
||||
@ -107,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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -122,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 (
|
||||
<Link to={`/syncers/${text}`}>
|
||||
@ -136,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);
|
||||
}
|
||||
@ -146,63 +123,80 @@ 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"),
|
||||
dataIndex: 'databaseType',
|
||||
key: 'databaseType',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Database"),
|
||||
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"),
|
||||
title: i18next.t("general:Is enabled"),
|
||||
dataIndex: 'isEnabled',
|
||||
key: 'isEnabled',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.isEnabled - b.isEnabled,
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -232,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 (
|
||||
@ -249,21 +241,37 @@ class SyncerListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addSyncer.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={syncers === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default SyncerListPage;
|
||||
|
@ -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 (
|
||||
<Link to={`/tokens/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/applications/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/users/${record.organization}/${text}`}>
|
||||
@ -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 {
|
||||
<Button type="primary" size="small" onClick={this.addToken.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={tokens === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default TokenListPage;
|
||||
|
@ -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 {
|
||||
@ -70,8 +50,8 @@ class UserListPage extends React.Component {
|
||||
passwordSalt: "",
|
||||
displayName: `New User - ${randomName}`,
|
||||
avatar: "https://casbin.org/img/casbin.svg",
|
||||
email: "user@example.com",
|
||||
phone: "12345678",
|
||||
email: `${randomName}@example.com`,
|
||||
phone: Setting.getRandomNumber(),
|
||||
address: [],
|
||||
affiliation: "Example Inc.",
|
||||
tag: "staff",
|
||||
@ -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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/applications/${text}`}>
|
||||
@ -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 (
|
||||
<Link to={`/users/${record.owner}/${text}`}>
|
||||
@ -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 (
|
||||
<a href={`mailto:${text}`}>
|
||||
@ -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 (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -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 (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -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 (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -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 (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
@ -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 {
|
||||
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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;
|
||||
|
@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select} from 'antd';
|
||||
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import WebhookHeaderTable from "./WebhookHeaderTable";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@ -114,6 +115,23 @@ class WebhookEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Method"), i18next.t("webhook:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.webhook.method} onChange={(value => {this.updateWebhookField('method', value);})}>
|
||||
{
|
||||
[
|
||||
{id: 'POST', name: 'POST'},
|
||||
{id: 'GET', name: 'GET'},
|
||||
{id: 'PUT', name: 'PUT'},
|
||||
{id: 'DELETE', name: 'DELETE'},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
|
||||
@ -129,6 +147,18 @@ class WebhookEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Headers"), i18next.t("webhook:Headers - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<WebhookHeaderTable
|
||||
title={i18next.t("webhook:Headers")}
|
||||
table={this.state.webhook.headers}
|
||||
onUpdateTable={(value) => { this.updateWebhookField('headers', value)}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("webhook:Events"), i18next.t("webhook:Events - Tooltip"))} :
|
||||
@ -151,6 +181,16 @@ class WebhookEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.webhook.isEnabled} onChange={checked => {
|
||||
this.updateWebhookField('isEnabled', checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
138
web/src/WebhookHeaderTable.js
Normal file
138
web/src/WebhookHeaderTable.js
Normal file
@ -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 {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
|
||||
import {Button, Col, Input, Row, Table, Tooltip} from 'antd';
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class WebhookHeaderTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
};
|
||||
}
|
||||
|
||||
updateTable(table) {
|
||||
this.props.onUpdateTable(table);
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
let row = {name: `header-${table.length}`, value: `value-${table.length}`};
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, row);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
upRow(table, i) {
|
||||
table = Setting.swapRow(table, i - 1, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
downRow(table, i) {
|
||||
table = Setting.swapRow(table, i, i + 1);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("webhook:Name"),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '250px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, 'name', e.target.value);
|
||||
}} />
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("webhook:Value"),
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, 'value', e.target.value);
|
||||
}} />
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: 'action',
|
||||
width: '100px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.props.table)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default WebhookHeaderTable;
|
@ -14,48 +14,27 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Popconfirm, Table} from 'antd';
|
||||
import {Button, Popconfirm, Switch, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
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();
|
||||
return {
|
||||
owner: "admin", // this.props.account.webhookname,
|
||||
name: `webhook_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
url: "https://example.com/callback",
|
||||
contentType: "application/json",
|
||||
events: ["signup", "login", "logout", "update-user"],
|
||||
organization: "built-in",
|
||||
url: "https://example.com/callback",
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
headers: [],
|
||||
events: ["signup", "login", "logout", "update-user"],
|
||||
isEnabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +43,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 +52,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 +73,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 (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
@ -113,7 +89,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 (
|
||||
<Link to={`/webhooks/${text}`}>
|
||||
@ -127,7 +104,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 +114,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 (
|
||||
<a target="_blank" rel="noreferrer" href={text}>
|
||||
@ -148,23 +126,49 @@ class WebhookListPage extends React.Component {
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("webhook:Method"),
|
||||
dataIndex: 'method',
|
||||
key: 'method',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps('method'),
|
||||
},
|
||||
{
|
||||
title: i18next.t("webhook:Content type"),
|
||||
dataIndex: 'contentType',
|
||||
key: 'contentType',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.contentType.localeCompare(b.contentType),
|
||||
width: '200px',
|
||||
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);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Is enabled"),
|
||||
dataIndex: 'isEnabled',
|
||||
key: 'isEnabled',
|
||||
width: '120px',
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
@ -188,12 +192,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 +207,37 @@ class WebhookListPage extends React.Component {
|
||||
<Button type="primary" size="small" onClick={this.addWebhook.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={webhooks === null}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default WebhookListPage;
|
||||
|
@ -176,7 +176,9 @@ class ForgetPage extends React.Component {
|
||||
{Setting.getMaskedPhone(this.state.phone)}
|
||||
</Option>
|
||||
);
|
||||
} else if (this.state.email !== "") {
|
||||
}
|
||||
|
||||
if (this.state.email !== "") {
|
||||
options.push(
|
||||
<Option key={"email"} value={"email"}>
|
||||
{Setting.getMaskedEmail(this.state.email)}
|
||||
|
@ -33,8 +33,8 @@ const authInfo = {
|
||||
WeChat: {
|
||||
scope: "snsapi_login",
|
||||
endpoint: "https://open.weixin.qq.com/connect/qrconnect",
|
||||
slientScope: "snsapi_userinfo",
|
||||
silentEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
mpScope: "snsapi_userinfo",
|
||||
mpEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
},
|
||||
Facebook: {
|
||||
scope: "email,public_profile",
|
||||
@ -200,12 +200,10 @@ export function getAuthUrl(application, provider, method) {
|
||||
} else if (provider.type === "QQ") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "WeChat") {
|
||||
if (provider.method === "Silent") {
|
||||
return `${authInfo[provider.type].silentEndpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].slientScope}&response_type=code#wechat_redirect`;
|
||||
} else if (provider.method === "Normal") {
|
||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}#wechat_redirect`;
|
||||
if (navigator.userAgent.includes("MicroMessenger")) {
|
||||
return `${authInfo[provider.type].mpEndpoint}?appid=${provider.clientId2}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].mpScope}&response_type=code#wechat_redirect`;
|
||||
} else {
|
||||
return `https://error:not-supported-provider-method:${provider.method}`;
|
||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}#wechat_redirect`;
|
||||
}
|
||||
} else if (provider.type === "Facebook") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||
|
@ -225,6 +225,28 @@ class SignupPage extends React.Component {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)
|
||||
} else if (signupItem.name === "ID card") {
|
||||
return (
|
||||
<Form.Item
|
||||
name="idCard"
|
||||
key="idCard"
|
||||
label={i18next.t("user:ID card")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please input your ID card number!"),
|
||||
whitespace: true,
|
||||
},
|
||||
{
|
||||
required: required,
|
||||
pattern: new RegExp(/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9X]$/, "g"),
|
||||
message: i18next.t("signup:Please input the correct ID card number!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)
|
||||
} else if (signupItem.name === "Country/Region") {
|
||||
return (
|
||||
<Form.Item
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -30,14 +30,21 @@ export const CountDownInput = (props) => {
|
||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||
const [checkType, setCheckType] = React.useState("");
|
||||
const [checkId, setCheckId] = React.useState("");
|
||||
const [buttonDisabled, setButtonDisabled] = React.useState(false);
|
||||
const [buttonLeftTime, setButtonLeftTime] = React.useState(0);
|
||||
const [buttonLoading, setButtonLoading] = React.useState(false);
|
||||
|
||||
const countDown = (leftTime) => {
|
||||
if (leftTime === 0) {
|
||||
setButtonDisabled(false);
|
||||
return;
|
||||
const handleCountDown = (leftTime = 60) => {
|
||||
let leftTimeSecond = leftTime
|
||||
setButtonLeftTime(leftTimeSecond)
|
||||
const countDown = () => {
|
||||
leftTimeSecond--;
|
||||
setButtonLeftTime(leftTimeSecond)
|
||||
if (leftTimeSecond === 0) {
|
||||
return;
|
||||
}
|
||||
setTimeout(countDown, 1000);
|
||||
}
|
||||
setTimeout(() => countDown(leftTime - 1), 1000);
|
||||
setTimeout(countDown, 1000);
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
@ -50,11 +57,12 @@ export const CountDownInput = (props) => {
|
||||
Util.showMessage("error", i18next.t("login:Invalid Email or phone"))
|
||||
return;
|
||||
}
|
||||
setButtonLoading(true)
|
||||
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
||||
setKey("");
|
||||
setButtonLoading(false)
|
||||
if (res) {
|
||||
setButtonDisabled(true)
|
||||
countDown(60);
|
||||
handleCountDown(60);
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -94,7 +102,7 @@ export const CountDownInput = (props) => {
|
||||
}}
|
||||
/>
|
||||
<Row>
|
||||
<Input autoFocus value={key} prefix={<SafetyOutlined />} placeholder={i18next.t("general:Captcha")} onPressEnter={handleOk} onChange={e => setKey(e.target.value)} />
|
||||
<Input autoFocus value={key} prefix={<SafetyOutlined/>} placeholder={i18next.t("general:Captcha")} onPressEnter={handleOk} onChange={e => setKey(e.target.value)}/>
|
||||
</Row>
|
||||
</Col>
|
||||
)
|
||||
@ -110,14 +118,12 @@ export const CountDownInput = (props) => {
|
||||
<Search
|
||||
addonBefore={textBefore}
|
||||
disabled={disabled}
|
||||
prefix={<SafetyOutlined />}
|
||||
prefix={<SafetyOutlined/>}
|
||||
placeholder={i18next.t("code:Enter your code")}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
enterButton={
|
||||
<Button type={"primary"} disabled={disabled || buttonDisabled}>
|
||||
<div style={{fontSize: 14}}>
|
||||
{i18next.t("code:Send Code")}
|
||||
</div>
|
||||
<Button style={{fontSize: 14}} type={"primary"} disabled={disabled || buttonLeftTime > 0} loading={buttonLoading}>
|
||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
||||
</Button>
|
||||
}
|
||||
onSearch={loadHumanCheck}
|
||||
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "random string",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "App's image tag",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Enable signup - Tooltip",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items - Tooltip",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Home - Tooltip",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "ID - Tooltip",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "Logo - Tooltip",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Category - Tooltip",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Client ID - Tooltip",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Client secret - Tooltip",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Domain - Tooltip",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "random string",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "App's image tag",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "random string",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "App's image tag",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "random string",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "App's image tag",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "Edit Application",
|
||||
"Enable code signin": "Enable code signin",
|
||||
"Enable code signin - Tooltip": "Enable code signin - Tooltip",
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Signin session": "Signin session",
|
||||
"Signup items": "Signup items",
|
||||
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
|
||||
"Test prompt page..": "Test prompt page..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||
"Please input your verification code!": "Please input your verification code!",
|
||||
"Send Code": "Send Code",
|
||||
"Sending Code": "Sending Code",
|
||||
"Submit and complete": "Submit and complete"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "Application homepage",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "random string",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs - Tooltip",
|
||||
"Logo - Tooltip": "App's image tag",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Unique string-style identifier",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "Please accept the agreement!",
|
||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||
"Please confirm your password!": "Please confirm your password!",
|
||||
"Please input the correct ID card number!": "Please input the correct ID card number!",
|
||||
"Please input your Email!": "Please input your Email!",
|
||||
"Please input your ID card number!": "Please input your ID card number!",
|
||||
"Please input your address!": "Please input your address!",
|
||||
"Please input your affiliation!": "Please input your affiliation!",
|
||||
"Please input your display name!": "Please input your display name!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "Column type",
|
||||
"Database": "Database",
|
||||
"Database - Tooltip": "Database - Tooltip",
|
||||
"Database type": "Database type",
|
||||
"Database type - Tooltip": "Database type - Tooltip",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Is enabled": "Is enabled",
|
||||
"Is enabled - Tooltip": "Is enabled - Tooltip",
|
||||
"Is hashed": "Is hashed",
|
||||
"Sync interval": "Sync interval",
|
||||
"Sync interval - Tooltip": "Sync interval - Tooltip",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "Empty input!",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage - Tooltip",
|
||||
"ID card": "ID card",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "Edit Webhook",
|
||||
"Events": "Events",
|
||||
"Events - Tooltip": "Events - Tooltip",
|
||||
"Headers": "Headers",
|
||||
"Headers - Tooltip": "Headers - Tooltip",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Method - Tooltip",
|
||||
"Name": "Name",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL - Tooltip"
|
||||
"URL - Tooltip": "URL - Tooltip",
|
||||
"Value": "Value"
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"Edit Application": "修改应用",
|
||||
"Enable code signin": "启用验证码登录",
|
||||
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
|
||||
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
|
||||
"Enable signup": "启用注册",
|
||||
"Enable signup - Tooltip": "是否允许用户注册",
|
||||
"File uploaded successfully": "文件上传成功",
|
||||
@ -21,6 +22,7 @@
|
||||
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
||||
"Refresh token expire": "Refresh Token过期时间",
|
||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||
"Signin session": "保持登录会话",
|
||||
"Signup items": "注册项",
|
||||
"Signup items - Tooltip": "注册用户注册时需要填写的项目",
|
||||
"Test prompt page..": "测试提醒页面..",
|
||||
@ -40,6 +42,7 @@
|
||||
"Please input your phone verification code!": "请输入您的手机验证码!",
|
||||
"Please input your verification code!": "请输入您的验证码!",
|
||||
"Send Code": "发送验证码",
|
||||
"Sending Code": "发送中",
|
||||
"Submit and complete": "完成提交"
|
||||
},
|
||||
"forget": {
|
||||
@ -86,6 +89,8 @@
|
||||
"Home - Tooltip": "应用的首页",
|
||||
"ID": "ID",
|
||||
"ID - Tooltip": "唯一的随机字符串",
|
||||
"Is enabled": "已启用",
|
||||
"Is enabled - Tooltip": "是否启用",
|
||||
"LDAPs": "LDAPs",
|
||||
"LDAPs - Tooltip": "LDAPs",
|
||||
"Logo - Tooltip": "应用程序向外展示的图标",
|
||||
@ -211,8 +216,12 @@
|
||||
"Category - Tooltip": "分类",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Client ID",
|
||||
"Client ID 2": "Client ID 2",
|
||||
"Client ID 2 - Tooltip": "Client ID 2 - Tooltip",
|
||||
"Client secret": "Client secret",
|
||||
"Client secret - Tooltip": "Client secret",
|
||||
"Client secret 2": "Client secret 2",
|
||||
"Client secret 2 - Tooltip": "Client secret 2 - Tooltip",
|
||||
"Copy": "Copy",
|
||||
"Domain": "域名",
|
||||
"Domain - Tooltip": "存储节点自定义域名",
|
||||
@ -232,7 +241,7 @@
|
||||
"Link copied to clipboard successfully": "Link copied to clipboard successfully",
|
||||
"Metadata": "Metadata",
|
||||
"Metadata - Tooltip": "Metadata - Tooltip",
|
||||
"Method": "方式",
|
||||
"Method": "方法",
|
||||
"Method - Tooltip": "登录行为,二维码或者静默授权登录",
|
||||
"Name": "名称",
|
||||
"Parse": "Parse",
|
||||
@ -300,7 +309,9 @@
|
||||
"Please accept the agreement!": "请先同意用户协议!",
|
||||
"Please click the below button to sign in": "请点击下方按钮登录",
|
||||
"Please confirm your password!": "请再次确认您的密码!",
|
||||
"Please input the correct ID card number!": "请输入正确的身份证号!",
|
||||
"Please input your Email!": "请输入您的电子邮箱!",
|
||||
"Please input your ID card number!": "请输入您的身份证号!",
|
||||
"Please input your address!": "请输入您的地址!",
|
||||
"Please input your affiliation!": "请输入您所在的工作单位!",
|
||||
"Please input your display name!": "请输入您的显示名称!",
|
||||
@ -327,9 +338,9 @@
|
||||
"Column type": "列类型",
|
||||
"Database": "数据库",
|
||||
"Database - Tooltip": "数据库名称",
|
||||
"Database type": "数据库类型",
|
||||
"Database type - Tooltip": "数据库类型",
|
||||
"Edit Syncer": "修改同步器",
|
||||
"Is enabled": "已启用",
|
||||
"Is enabled - Tooltip": "是否启用",
|
||||
"Is hashed": "是否参与哈希计算",
|
||||
"Sync interval": "同步间隔",
|
||||
"Sync interval - Tooltip": "单位为分钟",
|
||||
@ -366,6 +377,7 @@
|
||||
"Empty input!": "输入为空!",
|
||||
"Homepage": "个人主页",
|
||||
"Homepage - Tooltip": "个人主页链接",
|
||||
"ID card": "身份证号",
|
||||
"Input your email": "请输入邮箱",
|
||||
"Input your phone number": "输入手机号",
|
||||
"Is admin": "是管理员",
|
||||
@ -410,7 +422,13 @@
|
||||
"Edit Webhook": "修改Webhook",
|
||||
"Events": "事件",
|
||||
"Events - Tooltip": "事件",
|
||||
"Headers": "协议头",
|
||||
"Headers - Tooltip": "HTTP协议头(键值对)",
|
||||
"Method": "方法",
|
||||
"Method - Tooltip": "HTTP方法",
|
||||
"Name": "名称",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL"
|
||||
"URL - Tooltip": "URL",
|
||||
"Value": "值"
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
Reference in New Issue
Block a user