mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 00:50:28 +08:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
d498bc60ce | |||
7bbe1e38c1 | |||
f465fc6ce0 | |||
c952c2f2f4 | |||
86ae97d1e5 | |||
6ea73e3eca | |||
a71a190db5 | |||
da69d94445 | |||
b8b915abe1 | |||
5d1548e989 | |||
a0dc6e06cd | |||
ae130788ec | |||
f075d0fd74 | |||
65d4946042 | |||
26acece8af | |||
48a0c8473f | |||
082ae3c91e | |||
1ee2ff1d30 | |||
c0d9969013 | |||
1bdee13150 | |||
d668022af0 | |||
e227875c2b | |||
e473de3162 | |||
c5ef841d3f | |||
d46288b591 | |||
b968bf033c | |||
eca2527bc0 | |||
ef836acfe9 | |||
a51f0d7c08 | |||
e3c36beaf4 | |||
19dce838d1 | |||
b41d8652f0 | |||
e705eecffe | |||
2bb2c36f22 |
5
.github/workflows/sync.yml
vendored
5
.github/workflows/sync.yml
vendored
@ -29,6 +29,11 @@ jobs:
|
||||
crowdin_branch_name: l10n_branch
|
||||
config: './web/crowdin.yml'
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: '463556'
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: crowdin backend action
|
||||
uses: crowdin/github-action@1.4.8
|
||||
with:
|
||||
|
@ -44,14 +44,12 @@
|
||||
|
||||
## Online demo
|
||||
|
||||
- International: https://door.casdoor.org (read-only)
|
||||
- Asian mirror: https://door.casdoor.com (read-only)
|
||||
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
|
||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
|
||||
|
||||
## Documentation
|
||||
|
||||
- International: https://casdoor.org
|
||||
- Asian mirror: https://casdoor.cn
|
||||
https://casdoor.org
|
||||
|
||||
## Install
|
||||
|
||||
|
@ -161,7 +161,7 @@ func (c *ApiController) Signup() {
|
||||
username = id
|
||||
}
|
||||
|
||||
initScore, err := getInitScore()
|
||||
initScore, err := getInitScore(organization)
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
|
@ -170,7 +170,7 @@ func (c *ApiController) GetApplicationLogin() {
|
||||
}
|
||||
|
||||
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" {
|
||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" || providerType == "Line" {
|
||||
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
||||
} else {
|
||||
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
||||
@ -265,6 +265,10 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
return
|
||||
}
|
||||
if !application.EnablePassword {
|
||||
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckToEnableCaptcha(application) {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
|
||||
@ -412,7 +416,7 @@ func (c *ApiController) Login() {
|
||||
|
||||
properties := map[string]string{}
|
||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||
initScore, err := getInitScore()
|
||||
initScore, err := getInitScore(organization)
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("auth:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
|
@ -32,8 +32,8 @@ func (c *ApiController) GetCasbinAdapters() {
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetCasbinAdapters(owner)
|
||||
c.ServeJSON()
|
||||
adapters := object.GetCasbinAdapters(owner)
|
||||
c.ResponseOk(adapters)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
|
||||
@ -44,8 +44,8 @@ func (c *ApiController) GetCasbinAdapters() {
|
||||
|
||||
func (c *ApiController) GetCasbinAdapter() {
|
||||
id := c.Input().Get("id")
|
||||
c.Data["json"] = object.GetCasbinAdapter(id)
|
||||
c.ServeJSON()
|
||||
adapter := object.GetCasbinAdapter(id)
|
||||
c.ResponseOk(adapter)
|
||||
}
|
||||
|
||||
func (c *ApiController) UpdateCasbinAdapter() {
|
||||
@ -96,8 +96,7 @@ func (c *ApiController) SyncPolicies() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = policies
|
||||
c.ServeJSON()
|
||||
c.ResponseOk(policies)
|
||||
}
|
||||
|
||||
func (c *ApiController) UpdatePolicy() {
|
||||
|
@ -261,7 +261,7 @@ func (c *ApiController) TokenLogout() {
|
||||
flag, application := object.DeleteTokenByAccessToken(token)
|
||||
redirectUri := c.Input().Get("post_logout_redirect_uri")
|
||||
state := c.Input().Get("state")
|
||||
if application != nil && object.CheckRedirectUriValid(application, redirectUri) {
|
||||
if application != nil && application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
|
||||
return
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func (c *ApiController) SetTokenErrorHttpStatus() {
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("util:Please login first"), "util:Please login first")
|
||||
c.ResponseError(c.T("util:Please login first"), "Please login first")
|
||||
return "", false
|
||||
}
|
||||
return userId, true
|
||||
@ -119,8 +119,12 @@ func (c *ApiController) RequireAdmin() (string, bool) {
|
||||
return user.Owner, true
|
||||
}
|
||||
|
||||
func getInitScore() (int, error) {
|
||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
||||
func getInitScore(organization *object.Organization) (int, error) {
|
||||
if organization != nil {
|
||||
return organization.InitScore, nil
|
||||
} else {
|
||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
|
@ -47,6 +47,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
||||
method := c.Ctx.Request.Form.Get("method")
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if destType == "" {
|
||||
@ -119,7 +120,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
|
||||
userByEmail := object.GetUserByEmail(organization.Name, dest)
|
||||
if userByEmail == nil {
|
||||
if userByEmail == nil && method != "signup" && method != "reset" {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
@ -136,7 +137,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
|
||||
userByPhone := object.GetUserByPhone(organization.Name, dest)
|
||||
if userByPhone == nil {
|
||||
if userByPhone == nil && method != "signup" && method != "reset" {
|
||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||
return
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "The provider type: %s is not supported",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
|
@ -25,13 +25,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定: %s (%s)",
|
||||
"The application: %s does not exist": "应用 %s 不存在",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider type: %s is not supported": "不支持该类型的提供商: %s",
|
||||
"The provider: %s is not enabled for the application": "提供商: %s 未被启用",
|
||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员",
|
||||
"The user: %s/%s doesn't exist": "用户不存在: %s/%s",
|
||||
"Turing test failed.": "真人验证失败",
|
||||
"Unauthorized operation": "未授权的操作",
|
||||
"Unknown authentication type (not password or provider), form = %s": "未授权的操作"
|
||||
"Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "服务 %s 与 %s 不匹配"
|
||||
|
@ -256,6 +256,8 @@ func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
|
||||
}
|
||||
if data.ErrCode == 60121 {
|
||||
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||
} else if data.ErrCode != 0 {
|
||||
return false, fmt.Errorf(data.ErrMessage)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
return nil
|
||||
}
|
||||
|
||||
var gothList = []string{"Apple", "AzureAD", "Slack", "Steam"}
|
||||
var gothList = []string{"Apple", "AzureAD", "Slack", "Steam", "Line"}
|
||||
|
||||
func isGothSupport(provider string) bool {
|
||||
for _, value := range gothList {
|
||||
|
@ -156,5 +156,187 @@
|
||||
"autoSync": 0,
|
||||
"lastSync": ""
|
||||
}
|
||||
],
|
||||
"models": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"modelText": "",
|
||||
"displayName": ""
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"actions": [
|
||||
""
|
||||
],
|
||||
"displayName": "",
|
||||
"effect": "",
|
||||
"isEnabled": true,
|
||||
"model": "",
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"resourceType": "",
|
||||
"resources": [
|
||||
""
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"users": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"payments": [
|
||||
{
|
||||
"currency": "",
|
||||
"detail": "",
|
||||
"displayName": "",
|
||||
"invoiceRemark": "",
|
||||
"invoiceTaxId": "",
|
||||
"invoiceTitle": "",
|
||||
"invoiceType": "",
|
||||
"invoiceUrl": "",
|
||||
"message": "",
|
||||
"name": "",
|
||||
"organization": "",
|
||||
"owner": "",
|
||||
"payUrl": "",
|
||||
"personEmail": "",
|
||||
"personIdCard": "",
|
||||
"personName": "",
|
||||
"personPhone": "",
|
||||
"price": 0,
|
||||
"productDisplayName": "",
|
||||
"productName": "",
|
||||
"provider": "",
|
||||
"returnUrl": "",
|
||||
"state": "",
|
||||
"tag": "",
|
||||
"type": "",
|
||||
"user": ""
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"currency": "",
|
||||
"detail": "",
|
||||
"displayName": "",
|
||||
"image": "",
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"price": 0,
|
||||
"providers": [
|
||||
""
|
||||
],
|
||||
"quantity": 0,
|
||||
"returnUrl": "",
|
||||
"sold": 0,
|
||||
"state": "",
|
||||
"tag": ""
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"user": "",
|
||||
"provider": "",
|
||||
"application": "",
|
||||
"tag": "",
|
||||
"parent": "",
|
||||
"fileName": "",
|
||||
"fileType": "",
|
||||
"fileFormat": "",
|
||||
"url": "",
|
||||
"description": ""
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"displayName": "",
|
||||
"isEnabled": true,
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"users": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"syncers": [
|
||||
{
|
||||
"affiliationTable": "",
|
||||
"avatarBaseUrl": "",
|
||||
"database": "",
|
||||
"databaseType": "",
|
||||
"errorText": "",
|
||||
"host": "",
|
||||
"isEnabled": true,
|
||||
"name": "",
|
||||
"organization": "",
|
||||
"owner": "",
|
||||
"password": "",
|
||||
"port": 0,
|
||||
"syncInterval": 0,
|
||||
"table": "",
|
||||
"tableColumns": [
|
||||
{
|
||||
"casdoorName": "",
|
||||
"isHashed": true,
|
||||
"name": "",
|
||||
"type": "",
|
||||
"values": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"tablePrimaryKey": "",
|
||||
"type": "",
|
||||
"user": ""
|
||||
}
|
||||
],
|
||||
"tokens": [
|
||||
{
|
||||
"accessToken": "",
|
||||
"application": "",
|
||||
"code": "",
|
||||
"codeChallenge": "",
|
||||
"codeExpireIn": 0,
|
||||
"codeIsUsed": true,
|
||||
"createdTime": "",
|
||||
"expiresIn": 0,
|
||||
"name": "",
|
||||
"organization": "",
|
||||
"owner": "",
|
||||
"refreshToken": "",
|
||||
"scope": "",
|
||||
"tokenType": "",
|
||||
"user": ""
|
||||
}
|
||||
],
|
||||
"webhooks": [
|
||||
{
|
||||
"contentType": "",
|
||||
"events": [
|
||||
""
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"name": "",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"isEnabled": true,
|
||||
"isUserExtended": true,
|
||||
"method": "",
|
||||
"name": "",
|
||||
"organization": "",
|
||||
"owner": "",
|
||||
"url": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -51,6 +50,7 @@ type Application struct {
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
@ -353,52 +353,26 @@ func (application *Application) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
}
|
||||
|
||||
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||
validUri := false
|
||||
for _, tmpUri := range application.RedirectUris {
|
||||
tmpUriRegex := regexp.MustCompile(tmpUri)
|
||||
if tmpUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, tmpUri) {
|
||||
validUri = true
|
||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||
isValid := false
|
||||
for _, targetUri := range application.RedirectUris {
|
||||
targetUriRegex := regexp.MustCompile(targetUri)
|
||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return validUri
|
||||
return isValid
|
||||
}
|
||||
|
||||
func IsAllowOrigin(origin string) bool {
|
||||
allowOrigin := false
|
||||
originUrl, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
application := Application{}
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, tmpRedirectUri := range application.RedirectUris {
|
||||
u1, err := url.Parse(tmpRedirectUri)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
|
||||
allowOrigin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if allowOrigin {
|
||||
break
|
||||
func IsOriginAllowed(origin string) bool {
|
||||
applications := GetApplications("")
|
||||
for _, application := range applications {
|
||||
if application.IsRedirectUriValid(origin) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return allowOrigin
|
||||
return false
|
||||
}
|
||||
|
||||
func getApplicationMap(organization string) map[string]*Application {
|
||||
|
@ -313,8 +313,9 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
|
||||
return true, err
|
||||
}
|
||||
enforcer := getEnforcer(permission)
|
||||
allowed, err = enforcer.Enforce(userId, application.Name, "read")
|
||||
break
|
||||
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
|
||||
return allowed, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowed, err
|
||||
|
@ -59,6 +59,7 @@ func initBuiltInOrganization() bool {
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
|
||||
InitScore: 2000,
|
||||
AccountItems: []*AccountItem{
|
||||
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
|
@ -23,6 +23,15 @@ type InitData struct {
|
||||
Certs []*Cert `json:"certs"`
|
||||
Providers []*Provider `json:"providers"`
|
||||
Ldaps []*Ldap `json:"ldaps"`
|
||||
Models []*Model `json:"models"`
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
Payments []*Payment `json:"payments"`
|
||||
Products []*Product `json:"products"`
|
||||
Resources []*Resource `json:"resources"`
|
||||
Roles []*Role `json:"roles"`
|
||||
Syncers []*Syncer `json:"syncers"`
|
||||
Tokens []*Token `json:"tokens"`
|
||||
Webhooks []*Webhook `json:"webhooks"`
|
||||
}
|
||||
|
||||
func InitFromFile() {
|
||||
@ -46,6 +55,33 @@ func InitFromFile() {
|
||||
for _, ldap := range initData.Ldaps {
|
||||
initDefinedLdap(ldap)
|
||||
}
|
||||
for _, model := range initData.Models {
|
||||
initDefinedModel(model)
|
||||
}
|
||||
for _, permission := range initData.Permissions {
|
||||
initDefinedPermission(permission)
|
||||
}
|
||||
for _, payment := range initData.Payments {
|
||||
initDefinedPayment(payment)
|
||||
}
|
||||
for _, product := range initData.Products {
|
||||
initDefinedProduct(product)
|
||||
}
|
||||
for _, resource := range initData.Resources {
|
||||
initDefinedResource(resource)
|
||||
}
|
||||
for _, role := range initData.Roles {
|
||||
initDefinedRole(role)
|
||||
}
|
||||
for _, syncer := range initData.Syncers {
|
||||
initDefinedSyncer(syncer)
|
||||
}
|
||||
for _, token := range initData.Tokens {
|
||||
initDefinedToken(token)
|
||||
}
|
||||
for _, webhook := range initData.Webhooks {
|
||||
initDefinedWebhook(webhook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +99,15 @@ func readInitDataFromFile(filePath string) *InitData {
|
||||
Certs: []*Cert{},
|
||||
Providers: []*Provider{},
|
||||
Ldaps: []*Ldap{},
|
||||
Models: []*Model{},
|
||||
Permissions: []*Permission{},
|
||||
Payments: []*Payment{},
|
||||
Products: []*Product{},
|
||||
Resources: []*Resource{},
|
||||
Roles: []*Role{},
|
||||
Syncers: []*Syncer{},
|
||||
Tokens: []*Token{},
|
||||
Webhooks: []*Webhook{},
|
||||
}
|
||||
err := util.JsonToStruct(s, data)
|
||||
if err != nil {
|
||||
@ -89,6 +134,41 @@ func readInitDataFromFile(filePath string) *InitData {
|
||||
application.RedirectUris = []string{}
|
||||
}
|
||||
}
|
||||
for _, permission := range data.Permissions {
|
||||
if permission.Actions == nil {
|
||||
permission.Actions = []string{}
|
||||
}
|
||||
if permission.Resources == nil {
|
||||
permission.Resources = []string{}
|
||||
}
|
||||
if permission.Roles == nil {
|
||||
permission.Roles = []string{}
|
||||
}
|
||||
if permission.Users == nil {
|
||||
permission.Users = []string{}
|
||||
}
|
||||
}
|
||||
for _, role := range data.Roles {
|
||||
if role.Roles == nil {
|
||||
role.Roles = []string{}
|
||||
}
|
||||
if role.Users == nil {
|
||||
role.Users = []string{}
|
||||
}
|
||||
}
|
||||
for _, syncer := range data.Syncers {
|
||||
if syncer.TableColumns == nil {
|
||||
syncer.TableColumns = []*TableColumn{}
|
||||
}
|
||||
}
|
||||
for _, webhook := range data.Webhooks {
|
||||
if webhook.Events == nil {
|
||||
webhook.Events = []string{}
|
||||
}
|
||||
if webhook.Headers == nil {
|
||||
webhook.Headers = []*Header{}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@ -174,3 +254,84 @@ func initDefinedProvider(provider *Provider) {
|
||||
}
|
||||
AddProvider(provider)
|
||||
}
|
||||
|
||||
func initDefinedModel(model *Model) {
|
||||
existed := GetModel(model.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
model.CreatedTime = util.GetCurrentTime()
|
||||
AddModel(model)
|
||||
}
|
||||
|
||||
func initDefinedPermission(permission *Permission) {
|
||||
existed := GetPermission(permission.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
permission.CreatedTime = util.GetCurrentTime()
|
||||
AddPermission(permission)
|
||||
}
|
||||
|
||||
func initDefinedPayment(payment *Payment) {
|
||||
existed := GetPayment(payment.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
payment.CreatedTime = util.GetCurrentTime()
|
||||
AddPayment(payment)
|
||||
}
|
||||
|
||||
func initDefinedProduct(product *Product) {
|
||||
existed := GetProduct(product.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
product.CreatedTime = util.GetCurrentTime()
|
||||
AddProduct(product)
|
||||
}
|
||||
|
||||
func initDefinedResource(resource *Resource) {
|
||||
existed := GetResource(resource.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
resource.CreatedTime = util.GetCurrentTime()
|
||||
AddResource(resource)
|
||||
}
|
||||
|
||||
func initDefinedRole(role *Role) {
|
||||
existed := GetRole(role.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
role.CreatedTime = util.GetCurrentTime()
|
||||
AddRole(role)
|
||||
}
|
||||
|
||||
func initDefinedSyncer(syncer *Syncer) {
|
||||
existed := GetSyncer(syncer.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
syncer.CreatedTime = util.GetCurrentTime()
|
||||
AddSyncer(syncer)
|
||||
}
|
||||
|
||||
func initDefinedToken(token *Token) {
|
||||
existed := GetToken(token.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
token.CreatedTime = util.GetCurrentTime()
|
||||
AddToken(token)
|
||||
}
|
||||
|
||||
func initDefinedWebhook(webhook *Webhook) {
|
||||
existed := GetWebhook(webhook.GetId())
|
||||
if existed != nil {
|
||||
return
|
||||
}
|
||||
webhook.CreatedTime = util.GetCurrentTime()
|
||||
AddWebhook(webhook)
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ type Organization struct {
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
|
@ -41,7 +41,7 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
r = sub, obj, act
|
||||
|
||||
[policy_definition]
|
||||
p = sub, obj, act
|
||||
p = sub, obj, act, "", "", permissionId
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
@ -27,15 +27,16 @@ type Product struct {
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Image string `xorm:"varchar(100)" json:"image"`
|
||||
Detail string `xorm:"varchar(255)" json:"detail"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Sold int `json:"sold"`
|
||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
Image string `xorm:"varchar(100)" json:"image"`
|
||||
Detail string `xorm:"varchar(255)" json:"detail"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Sold int `json:"sold"`
|
||||
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
|
||||
@ -213,6 +214,10 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
}
|
||||
|
||||
func ExtendProductWithProviders(product *Product) {
|
||||
if product == nil {
|
||||
return
|
||||
}
|
||||
|
||||
product.ProviderObjs = []*Provider{}
|
||||
|
||||
m := getProviderMap(product.Owner)
|
||||
|
@ -240,8 +240,8 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
}
|
||||
|
||||
// verify samlRequest
|
||||
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
|
||||
return "", "", fmt.Errorf("err: invalid issuer url")
|
||||
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
|
||||
return "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
|
||||
}
|
||||
|
||||
// get certificate string
|
||||
@ -251,6 +251,11 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
// redirect Url (Assertion Consumer Url)
|
||||
if application.SamlReplyUrl != "" {
|
||||
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
|
||||
}
|
||||
|
||||
// build signedResponse
|
||||
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||
randomKeyStore := &X509Key{
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
@ -169,6 +168,10 @@ func GetToken(id string) *Token {
|
||||
return getToken(owner, name)
|
||||
}
|
||||
|
||||
func (token *Token) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
|
||||
}
|
||||
|
||||
func UpdateToken(id string, token *Token) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getToken(owner, name) == nil {
|
||||
@ -249,14 +252,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
||||
return i18n.Translate(lang, "token:Invalid client_id"), nil
|
||||
}
|
||||
|
||||
validUri := false
|
||||
for _, tmpUri := range application.RedirectUris {
|
||||
if strings.Contains(redirectUri, tmpUri) {
|
||||
validUri = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validUri {
|
||||
if !application.IsRedirectUriValid(redirectUri) {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application
|
||||
}
|
||||
|
||||
|
@ -102,6 +102,7 @@ type User struct {
|
||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
||||
Line string `xorm:"line varchar(100)" json:"line"`
|
||||
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||
|
||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||
|
@ -34,7 +34,7 @@ func CorsFilter(ctx *context.Context) {
|
||||
originConf := conf.GetConfigString("origin")
|
||||
|
||||
if origin != "" && originConf != "" && origin != originConf {
|
||||
if object.IsAllowOrigin(origin) {
|
||||
if object.IsOriginAllowed(origin) {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
|
@ -9,7 +9,7 @@
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"antd": "5.0.3",
|
||||
"antd": "5.0.5",
|
||||
"codemirror": "^5.61.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"core-js": "^3.25.0",
|
||||
|
@ -48,12 +48,14 @@ class AdapterEditPage extends React.Component {
|
||||
|
||||
getAdapter() {
|
||||
AdapterBackend.getAdapter(this.state.owner, this.state.adapterName)
|
||||
.then((adapter) => {
|
||||
this.setState({
|
||||
adapter: adapter,
|
||||
});
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
adapter: res.data,
|
||||
});
|
||||
|
||||
this.getModels(adapter.owner);
|
||||
this.getModels(this.state.owner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class AdapterListPage extends BaseListPage {
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/adapters/${text}`}>
|
||||
<Link to={`/adapters/${record.organization}/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
|
241
web/src/App.js
241
web/src/App.js
@ -17,7 +17,7 @@ import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, theme} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
@ -55,26 +55,21 @@ import CustomGithubCorner from "./CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
import * as Auth from "./auth/Auth";
|
||||
import SignupPage from "./auth/SignupPage";
|
||||
import EntryPage from "./EntryPage";
|
||||
import ResultPage from "./auth/ResultPage";
|
||||
import LoginPage from "./auth/LoginPage";
|
||||
import SelfLoginPage from "./auth/SelfLoginPage";
|
||||
import SelfForgetPage from "./auth/SelfForgetPage";
|
||||
import ForgetPage from "./auth/ForgetPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from "./SelectLanguageBox";
|
||||
import i18next from "i18next";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||
import SamlCallback from "./auth/SamlCallback";
|
||||
import CasLogout from "./auth/CasLogout";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
import SystemInfo from "./SystemInfo";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import {withTranslation} from "react-i18next";
|
||||
import SelectThemeBox from "./SelectThemeBox";
|
||||
|
||||
const {Header, Footer, Content} = Layout;
|
||||
|
||||
@ -87,6 +82,8 @@ class App extends Component {
|
||||
account: undefined,
|
||||
uri: null,
|
||||
menuVisible: false,
|
||||
themeAlgorithm: null,
|
||||
logo: null,
|
||||
};
|
||||
|
||||
Setting.initServerUrl();
|
||||
@ -101,6 +98,16 @@ class App extends Component {
|
||||
this.getAccount();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
localStorage.getItem("theme") ?
|
||||
this.setState({"themeAlgorithm": this.getTheme()}) : this.setState({"themeAlgorithm": theme.defaultAlgorithm});
|
||||
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
|
||||
addEventListener("themeChange", (e) => {
|
||||
this.setState({"themeAlgorithm": this.getTheme()});
|
||||
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const uri = location.pathname;
|
||||
@ -190,6 +197,10 @@ class App extends Component {
|
||||
return "";
|
||||
}
|
||||
|
||||
getTheme() {
|
||||
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
|
||||
}
|
||||
|
||||
setLanguage(account) {
|
||||
const language = account?.language;
|
||||
if (language !== "" && language !== i18next.language) {
|
||||
@ -224,7 +235,7 @@ class App extends Component {
|
||||
this.setLanguage(account);
|
||||
} else {
|
||||
if (res.data !== "Please login first") {
|
||||
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +260,7 @@ class App extends Component {
|
||||
account: null,
|
||||
});
|
||||
|
||||
Setting.showMessage("success", "Logged out successfully");
|
||||
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||
const redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
@ -471,54 +482,62 @@ class App extends Component {
|
||||
|
||||
renderRouter() {
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
<ConfigProvider theme={{
|
||||
token: {
|
||||
colorPrimary: "rgb(89,54,213)",
|
||||
colorInfo: "rgb(89,54,213)",
|
||||
},
|
||||
algorithm: this.state.themeAlgorithm,
|
||||
}}>
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -538,30 +557,27 @@ class App extends Component {
|
||||
if (!Setting.isMobile()) {
|
||||
return (
|
||||
<Layout id="parent-area">
|
||||
<Header style={{marginBottom: "3px", paddingInline: 0}}>
|
||||
<Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<div>
|
||||
<Menu
|
||||
// theme="dark"
|
||||
items={this.renderMenu()}
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
|
||||
>
|
||||
</Menu>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||
</div>
|
||||
<Menu
|
||||
items={this.renderMenu()}
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
|
||||
/>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||
</Header>
|
||||
<Content style={{backgroundColor: "#f5f5f5", alignItems: "stretch", display: "flex", flexDirection: "column"}}>
|
||||
<Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
|
||||
<Card className="content-warp-card">
|
||||
{
|
||||
this.renderRouter()
|
||||
@ -576,11 +592,11 @@ class App extends Component {
|
||||
} else {
|
||||
return (
|
||||
<Layout>
|
||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@ -598,12 +614,11 @@ class App extends Component {
|
||||
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
<div style = {{float: "right"}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||
</div>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >{
|
||||
this.renderRouter()}
|
||||
@ -619,74 +634,66 @@ class App extends Component {
|
||||
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
|
||||
|
||||
return (
|
||||
<>
|
||||
<React.Fragment>
|
||||
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
||||
<Footer id="footer" style={
|
||||
{
|
||||
borderTop: "1px solid #e8e8e8",
|
||||
backgroundColor: "white",
|
||||
textAlign: "center",
|
||||
}
|
||||
}>
|
||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a>
|
||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
||||
</Footer>
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
isDoorPages() {
|
||||
return this.isEntryPages() || window.location.pathname.startsWith("/callback");
|
||||
}
|
||||
|
||||
isEntryPages() {
|
||||
return window.location.pathname.startsWith("/signup") ||
|
||||
window.location.pathname.startsWith("/login") ||
|
||||
window.location.pathname.startsWith("/callback") ||
|
||||
window.location.pathname.startsWith("/prompt") ||
|
||||
window.location.pathname.startsWith("/forget") ||
|
||||
window.location.pathname.startsWith("/cas");
|
||||
window.location.pathname.startsWith("/prompt") ||
|
||||
window.location.pathname.startsWith("/cas") ||
|
||||
window.location.pathname.startsWith("/auto-signup");
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
if (this.isDoorPages()) {
|
||||
return (
|
||||
<>
|
||||
<React.Fragment>
|
||||
<Layout id="parent-area">
|
||||
<Content style={{display: "flex", justifyContent: "center"}}>
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
|
||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
|
||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
|
||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo {...props} />)} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
{
|
||||
this.isEntryPages() ?
|
||||
<EntryPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />
|
||||
:
|
||||
<Switch>
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
}
|
||||
</Content>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
</Layout>
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<React.Fragment>
|
||||
<FloatButton.BackTop />
|
||||
<CustomGithubCorner />
|
||||
{
|
||||
this.renderContent()
|
||||
}
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@ -702,6 +709,7 @@ class App extends Component {
|
||||
colorPrimary: "rgb(89,54,213)",
|
||||
colorInfo: "rgb(89,54,213)",
|
||||
},
|
||||
algorithm: this.state.themeAlgorithm,
|
||||
}}>
|
||||
{
|
||||
this.renderPage()
|
||||
@ -723,6 +731,7 @@ class App extends Component {
|
||||
colorPrimary: "rgb(89,54,213)",
|
||||
colorInfo: "rgb(89,54,213)",
|
||||
},
|
||||
algorithm: this.state.themeAlgorithm,
|
||||
}}>
|
||||
{
|
||||
this.renderPage()
|
||||
|
@ -13,14 +13,12 @@
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
@ -41,7 +39,6 @@ img {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.panel-logo {
|
||||
@ -55,7 +52,7 @@ img {
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 5px;
|
||||
width: 45px;
|
||||
height: 65px;
|
||||
height: 100%;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
|
||||
@ -64,9 +61,32 @@ img {
|
||||
}
|
||||
}
|
||||
|
||||
.login-form .language-box {
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
.theme-box {
|
||||
background: url("@{StaticBaseUrl}/img/muti_language.svg");
|
||||
background-size: 25px, 25px;
|
||||
background-position: center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
border-radius: 5px;
|
||||
width: 45px;
|
||||
height: 100%;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.rightDropDown {
|
||||
border-radius: 7px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,3 +148,9 @@ img {
|
||||
background-size: 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.ant-menu-horizontal {
|
||||
border-bottom: none !important;
|
||||
margin-right: 30px;
|
||||
right: 230px;
|
||||
}
|
||||
|
@ -545,6 +545,16 @@ class ApplicationEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:SAML Reply URL"), i18next.t("application:Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.application.samlReplyUrl} onChange={e => {
|
||||
this.updateApplicationField("samlReplyUrl", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
|
||||
@ -716,7 +726,7 @@ class ApplicationEditPage extends React.Component {
|
||||
renderSignupSigninPreview() {
|
||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||
if (!this.state.application.enablePassword) {
|
||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||
}
|
||||
@ -732,15 +742,19 @@ class ApplicationEditPage extends React.Component {
|
||||
{i18next.t("application:Copy signup page URL")}
|
||||
</Button>
|
||||
<br />
|
||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
||||
{
|
||||
this.state.application.enablePassword ? (
|
||||
<SignupPage application={this.state.application} />
|
||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||
<SignupPage application={this.state.application} preview = "auto" />
|
||||
</div>
|
||||
) : (
|
||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} />
|
||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} preview = "auto" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div style={maskStyle} />
|
||||
<div style={{overflow: "auto", ...maskStyle}} />
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={previewGrid}>
|
||||
@ -752,9 +766,11 @@ class ApplicationEditPage extends React.Component {
|
||||
{i18next.t("application:Copy signin page URL")}
|
||||
</Button>
|
||||
<br />
|
||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
|
||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
|
||||
<div style={maskStyle} />
|
||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
|
||||
</div>
|
||||
<div style={{overflow: "auto", ...maskStyle}} />
|
||||
</div>
|
||||
</Col>
|
||||
</React.Fragment>
|
||||
|
@ -197,9 +197,9 @@ class ApplicationListPage extends BaseListPage {
|
||||
<List.Item>
|
||||
<div style={{display: "inline"}}>
|
||||
<Tooltip placement="topLeft" title="Edit">
|
||||
<Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => Setting.goToLinkSoft(this, `/providers/${providerItem.name}`)} />
|
||||
<Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => Setting.goToLinkSoft(this, `/providers/${record.organization}/${providerItem.name}`)} />
|
||||
</Tooltip>
|
||||
<Link to={`/providers/${providerItem.name}`}>
|
||||
<Link to={`/providers/${record.organization}/${providerItem.name}`}>
|
||||
{providerItem.name}
|
||||
</Link>
|
||||
</div>
|
||||
|
84
web/src/EntryPage.js
Normal file
84
web/src/EntryPage.js
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2022 The Casdoor 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 {Redirect, Route, Switch} from "react-router-dom";
|
||||
import {Spin} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as Setting from "./Setting";
|
||||
import SignupPage from "./auth/SignupPage";
|
||||
import SelfLoginPage from "./auth/SelfLoginPage";
|
||||
import LoginPage from "./auth/LoginPage";
|
||||
import SelfForgetPage from "./auth/SelfForgetPage";
|
||||
import ForgetPage from "./auth/ForgetPage";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import CasLogout from "./auth/CasLogout";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
application: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
renderHomeIfLoggedIn(component) {
|
||||
if (this.props.account !== null && this.props.account !== undefined) {
|
||||
return <Redirect to="/" />;
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
renderLoginIfNotLoggedIn(component) {
|
||||
if (this.props.account === null) {
|
||||
sessionStorage.setItem("from", window.location.pathname);
|
||||
return <Redirect to="/login" />;
|
||||
} else if (this.props.account === undefined) {
|
||||
return null;
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const onUpdateApplication = (application) => {
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
};
|
||||
|
||||
return <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
|
||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
|
||||
</Switch>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EntryPage;
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
@ -86,7 +86,6 @@ class OrganizationEditPage extends React.Component {
|
||||
|
||||
updateOrganizationField(key, value) {
|
||||
value = this.parseOrganizationField(key, value);
|
||||
|
||||
const organization = this.state.organization;
|
||||
organization[key] = value;
|
||||
this.setState({
|
||||
@ -280,6 +279,16 @@ class OrganizationEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:InitScore"), i18next.t("organization:The user's initScore - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={4} >
|
||||
<InputNumber value={this.state.organization.initScore} onChange={value => {
|
||||
this.updateOrganizationField("initScore", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :
|
||||
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Descriptions, Spin} from "antd";
|
||||
import {Button, Descriptions, Modal, Spin} from "antd";
|
||||
import {CheckCircleTwoTone} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@ -26,6 +27,7 @@ class ProductBuyPage extends React.Component {
|
||||
productName: props.match?.params.productName,
|
||||
product: null,
|
||||
isPlacingOrder: false,
|
||||
qrCodeModalProvider: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -34,6 +36,10 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
|
||||
getProduct() {
|
||||
if (this.state.productName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProductBackend.getProduct("admin", this.state.productName)
|
||||
.then((product) => {
|
||||
this.setState({
|
||||
@ -75,6 +81,13 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
|
||||
buyProduct(product, provider) {
|
||||
if (provider.clientId.startsWith("http")) {
|
||||
this.setState({
|
||||
qrCodeModalProvider: provider,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isPlacingOrder: true,
|
||||
});
|
||||
@ -97,6 +110,45 @@ class ProductBuyPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderQrCodeModal() {
|
||||
if (this.state.qrCodeModalProvider === undefined || this.state.qrCodeModalProvider === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title={
|
||||
<div>
|
||||
<CheckCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
||||
{" " + i18next.t("product:Please scan the QR code to pay")}
|
||||
</div>
|
||||
}
|
||||
open={this.state.qrCodeModalProvider !== undefined && this.state.qrCodeModalProvider !== null}
|
||||
onOk={() => {
|
||||
Setting.goToLink(this.state.product.returnUrl);
|
||||
}}
|
||||
onCancel={() => {
|
||||
this.setState({
|
||||
qrCodeModalProvider: null,
|
||||
});
|
||||
}}
|
||||
okText={i18next.t("product:I have completed the payment")}
|
||||
cancelText={i18next.t("general:Cancel")}>
|
||||
<p key={this.state.qrCodeModalProvider?.name}>
|
||||
{
|
||||
i18next.t("product:Please provide your username in the remark")
|
||||
}
|
||||
:
|
||||
{
|
||||
Setting.getTag("default", this.props.account.name)
|
||||
}
|
||||
<br />
|
||||
<br />
|
||||
<img src={this.state.qrCodeModalProvider?.clientId} alt={this.state.qrCodeModalProvider?.name} width={"472px"} style={{marginBottom: "20px"}} />
|
||||
</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
getPayButton(provider) {
|
||||
let text = provider.type;
|
||||
if (provider.type === "Alipay") {
|
||||
@ -185,6 +237,9 @@ class ProductBuyPage extends React.Component {
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
{
|
||||
this.renderQrCodeModal()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -153,6 +153,16 @@ class ProductEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Description"), i18next.t("product:Description - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.product.description} onChange={e => {
|
||||
this.updateProductField("description", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} :
|
||||
|
@ -92,6 +92,7 @@ export const ResetModal = (props) => {
|
||||
<CountDownInput
|
||||
textBefore={i18next.t("code:Code You Received")}
|
||||
onChange={setCode}
|
||||
method={"reset"}
|
||||
onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
|
@ -113,7 +113,7 @@ class ResourceListPage extends BaseListPage {
|
||||
...this.getColumnSearchProps("application"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/applications/${text}`}>
|
||||
<Link to={`/applications/${record.organization}/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import {Dropdown, Menu} from "antd";
|
||||
import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
|
||||
function flagIcon(country, alt) {
|
||||
@ -32,16 +32,7 @@ class SelectLanguageBox extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
items = [
|
||||
Setting.getItem("English", "en", flagIcon("US", "English")),
|
||||
Setting.getItem("简体中文", "zh", flagIcon("CN", "简体中文")),
|
||||
Setting.getItem("Español", "es", flagIcon("ES", "Español")),
|
||||
Setting.getItem("Français", "fr", flagIcon("FR", "Français")),
|
||||
Setting.getItem("Deutsch", "de", flagIcon("DE", "Deutsch")),
|
||||
Setting.getItem("日本語", "ja", flagIcon("JP", "日本語")),
|
||||
Setting.getItem("한국어", "ko", flagIcon("KR", "한국어")),
|
||||
Setting.getItem("Русский", "ru", flagIcon("RU", "Русский")),
|
||||
];
|
||||
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||
|
||||
getOrganizationLanguages(languages) {
|
||||
const select = [];
|
||||
@ -53,15 +44,12 @@ class SelectLanguageBox extends React.Component {
|
||||
|
||||
render() {
|
||||
const languageItems = this.getOrganizationLanguages(this.state.languages);
|
||||
const menu = (
|
||||
<Menu items={languageItems} onClick={(e) => {
|
||||
Setting.setLanguage(e.key);
|
||||
}}>
|
||||
</Menu>
|
||||
);
|
||||
const onClick = (e) => {
|
||||
Setting.setLanguage(e.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} >
|
||||
<Dropdown menu={{items: languageItems, onClick}} >
|
||||
<div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} />
|
||||
</Dropdown>
|
||||
);
|
||||
|
@ -42,12 +42,15 @@ class SelectRegionBox extends React.Component {
|
||||
placeholder="Please select country/region"
|
||||
onChange={(value => {this.onChange(value);})}
|
||||
filterOption={(input, option) =>
|
||||
option.label.indexOf(input) >= 0
|
||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
filterSort={(optionA, optionB) =>
|
||||
(optionA?.label ?? "").toLowerCase().localeCompare((optionB?.label ?? "").toLowerCase())
|
||||
}
|
||||
>
|
||||
{
|
||||
Setting.CountryRegionData.map((item, index) => (
|
||||
<Option key={index} value={item.code} label={item.code} >
|
||||
<Option key={index} value={item.code} label={`${item.name} (${item.code})`} >
|
||||
<img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}} />
|
||||
{`${item.name} (${item.code})`}
|
||||
</Option>
|
||||
|
81
web/src/SelectThemeBox.js
Normal file
81
web/src/SelectThemeBox.js
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2021 The Casdoor 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 * as Setting from "./Setting";
|
||||
import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
import i18next from "i18next";
|
||||
|
||||
function themeIcon(themeKey) {
|
||||
return <img width={24} alt={themeKey} src={getLogoURL(themeKey)} />;
|
||||
}
|
||||
|
||||
function getLogoURL(themeKey) {
|
||||
if (themeKey) {
|
||||
return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"];
|
||||
} else {
|
||||
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"];
|
||||
}
|
||||
}
|
||||
|
||||
class SelectThemeBox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
themes: props.theme ?? ["Default", "Dark", "Compact"],
|
||||
icon: null,
|
||||
};
|
||||
}
|
||||
|
||||
items = this.getThemes();
|
||||
|
||||
componentDidMount() {
|
||||
i18next.on("languageChanged", () => {
|
||||
this.items = this.getThemes();
|
||||
});
|
||||
localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")});
|
||||
addEventListener("themeChange", (e) => {
|
||||
this.setState({"icon": getLogoURL()});
|
||||
});
|
||||
}
|
||||
|
||||
getThemes() {
|
||||
return Setting.Themes.map((theme) => Setting.getItem(i18next.t(`general:${theme.label}`), theme.key, themeIcon(theme.key)));
|
||||
}
|
||||
|
||||
getOrganizationThemes(themes) {
|
||||
const select = [];
|
||||
for (const theme of themes) {
|
||||
this.items.map((item, index) => item.key === theme ? select.push(item) : null);
|
||||
}
|
||||
return select;
|
||||
}
|
||||
|
||||
render() {
|
||||
const themeItems = this.getOrganizationThemes(this.state.themes);
|
||||
const onClick = (e) => {
|
||||
Setting.setTheme(e.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown menu={{items: themeItems, onClick}} >
|
||||
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectThemeBox;
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Tag, Tooltip, message} from "antd";
|
||||
import {Tag, Tooltip, message, theme} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
@ -33,6 +33,23 @@ export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
|
||||
export const CountryRegionData = getCountryRegionData();
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "简体中文", key: "zh", country: "CN", alt: "简体中文"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
];
|
||||
|
||||
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme;
|
||||
|
||||
export const Themes = [{label: "Dark", key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`},
|
||||
{label: "Compact", key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`},
|
||||
{label: "Default", key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`},
|
||||
];
|
||||
|
||||
export const OtherProviderInfo = {
|
||||
SMS: {
|
||||
"Aliyun SMS": {
|
||||
@ -251,8 +268,10 @@ export function isValidPersonName(personName) {
|
||||
}
|
||||
|
||||
export function isValidIdCard(idCard) {
|
||||
const idCardRegex = /^[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]$/;
|
||||
return idCardRegex.test(idCard);
|
||||
return idCard !== "";
|
||||
|
||||
// const idCardRegex = /^[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]$/;
|
||||
// return idCardRegex.test(idCard);
|
||||
}
|
||||
|
||||
export function isValidEmail(email) {
|
||||
@ -262,34 +281,40 @@ export function isValidEmail(email) {
|
||||
}
|
||||
|
||||
export function isValidPhone(phone) {
|
||||
if (phone === "") {
|
||||
return false;
|
||||
}
|
||||
return phone !== "";
|
||||
|
||||
// https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||
const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||
return phoneRegex.test(phone);
|
||||
// if (phone === "") {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||
// const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||
// return phoneRegex.test(phone);
|
||||
}
|
||||
|
||||
export function isValidInvoiceTitle(invoiceTitle) {
|
||||
if (invoiceTitle === "") {
|
||||
return false;
|
||||
}
|
||||
return invoiceTitle !== "";
|
||||
|
||||
// https://blog.css8.cn/post/14210975.html
|
||||
const invoiceTitleRegex = /^[()()\u4e00-\u9fa5]{0,50}$/;
|
||||
return invoiceTitleRegex.test(invoiceTitle);
|
||||
// if (invoiceTitle === "") {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // https://blog.css8.cn/post/14210975.html
|
||||
// const invoiceTitleRegex = /^[()()\u4e00-\u9fa5]{0,50}$/;
|
||||
// return invoiceTitleRegex.test(invoiceTitle);
|
||||
}
|
||||
|
||||
export function isValidTaxId(taxId) {
|
||||
// https://www.codetd.com/article/8592083
|
||||
const regArr = [/^[\da-z]{10,15}$/i, /^\d{6}[\da-z]{10,12}$/i, /^[a-z]\d{6}[\da-z]{9,11}$/i, /^[a-z]{2}\d{6}[\da-z]{8,10}$/i, /^\d{14}[\dx][\da-z]{4,5}$/i, /^\d{17}[\dx][\da-z]{1,2}$/i, /^[a-z]\d{14}[\dx][\da-z]{3,4}$/i, /^[a-z]\d{17}[\dx][\da-z]{0,1}$/i, /^[\d]{6}[\da-z]{13,14}$/i];
|
||||
for (let i = 0; i < regArr.length; i++) {
|
||||
if (regArr[i].test(taxId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return taxId !== "";
|
||||
|
||||
// // https://www.codetd.com/article/8592083
|
||||
// const regArr = [/^[\da-z]{10,15}$/i, /^\d{6}[\da-z]{10,12}$/i, /^[a-z]\d{6}[\da-z]{9,11}$/i, /^[a-z]{2}\d{6}[\da-z]{8,10}$/i, /^\d{14}[\dx][\da-z]{4,5}$/i, /^\d{17}[\dx][\da-z]{1,2}$/i, /^[a-z]\d{14}[\dx][\da-z]{3,4}$/i, /^[a-z]\d{17}[\dx][\da-z]{0,1}$/i, /^[\d]{6}[\da-z]{13,14}$/i];
|
||||
// for (let i = 0; i < regArr.length; i++) {
|
||||
// if (regArr[i].test(taxId)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
}
|
||||
|
||||
export function isAffiliationPrompted(application) {
|
||||
@ -544,6 +569,14 @@ export function getAvatarColor(s) {
|
||||
return colorList[hash % 4];
|
||||
}
|
||||
|
||||
export function getLogo(theme) {
|
||||
if (theme === "Dark") {
|
||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
|
||||
} else {
|
||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLanguageText(text) {
|
||||
if (!text.includes("|")) {
|
||||
return text;
|
||||
@ -569,6 +602,11 @@ export function setLanguage(language) {
|
||||
i18next.changeLanguage(language);
|
||||
}
|
||||
|
||||
export function setTheme(themeKey) {
|
||||
localStorage.setItem("theme", themeKey);
|
||||
dispatchEvent(new Event("themeChange"));
|
||||
}
|
||||
|
||||
export function getAcceptLanguage() {
|
||||
if (i18next.language === null || i18next.language === "") {
|
||||
return "en;q=0.9,en;q=0.8";
|
||||
@ -663,6 +701,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Bilibili", name: "Bilibili"},
|
||||
{id: "Okta", name: "Okta"},
|
||||
{id: "Douyin", name: "Douyin"},
|
||||
{id: "Line", name: "Line"},
|
||||
{id: "Custom", name: "Custom"},
|
||||
]
|
||||
);
|
||||
@ -780,6 +819,9 @@ export function renderLoginLink(application, text) {
|
||||
|
||||
export function redirectToLoginPage(application, history) {
|
||||
const loginLink = getLoginLink(application);
|
||||
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
|
||||
window.location.replace(loginLink);
|
||||
}
|
||||
history.push(loginLink);
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,7 @@ import SamlWidget from "./common/SamlWidget";
|
||||
import SelectRegionBox from "./SelectRegionBox";
|
||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./ManagedAccountTable";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
import PropertyTable from "./propertyTable";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -490,13 +486,10 @@ class UserEditPage extends React.Component {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("user:Properties")}:
|
||||
{Setting.getLabel(i18next.t("user:Properties"), i18next.t("user:Properties - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<CodeMirror
|
||||
value={JSON.stringify(this.state.user.properties, null, 4)}
|
||||
options={{mode: "javascript", theme: "material-darker"}}
|
||||
/>
|
||||
<PropertyTable properties={this.state.user.properties} onUpdateTable={(value) => {this.updateUserField("properties", value);}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -62,6 +62,7 @@ class UserListPage extends BaseListPage {
|
||||
isAdmin: (owner === "built-in"),
|
||||
isGlobalAdmin: (owner === "built-in"),
|
||||
IsForbidden: false,
|
||||
score: this.state.organization.initScore,
|
||||
isDeleted: false,
|
||||
properties: {},
|
||||
signupApplication: "app-built-in",
|
||||
|
@ -178,7 +178,7 @@ class AuthCallback extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{textAlign: "center"}}>
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
(this.state.msg === null) ? (
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Spin} from "antd";
|
||||
import {Card, Spin} from "antd";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as Setting from "../Setting";
|
||||
@ -39,7 +39,8 @@ class CasLogout extends React.Component {
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", "Logged out successfully");
|
||||
this.props.clearAccount();
|
||||
this.props.onUpdateAccount(null);
|
||||
this.onUpdateApplication(null);
|
||||
const redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
@ -49,6 +50,7 @@ class CasLogout extends React.Component {
|
||||
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
||||
}
|
||||
} else {
|
||||
this.onUpdateApplication(null);
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
@ -57,11 +59,13 @@ class CasLogout extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{textAlign: "center"}}>
|
||||
{
|
||||
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
|
||||
}
|
||||
</div>
|
||||
<Card>
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
|
||||
}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -34,12 +34,7 @@ class ForgetPage extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
account: props.account,
|
||||
applicationName:
|
||||
props.applicationName !== undefined
|
||||
? props.applicationName
|
||||
: props.match === undefined
|
||||
? null
|
||||
: props.match.params.applicationName,
|
||||
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
||||
application: null,
|
||||
msg: null,
|
||||
userId: "",
|
||||
@ -57,34 +52,36 @@ class ForgetPage extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.state.applicationName !== undefined) {
|
||||
this.getApplication();
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
|
||||
componentDidMount() {
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (this.state.applicationName !== undefined) {
|
||||
this.getApplication();
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
if (this.state.applicationName === null) {
|
||||
if (this.state.applicationName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName).then(
|
||||
(application) => {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
if (this.props.application !== undefined) {
|
||||
return this.props.application;
|
||||
} else {
|
||||
return this.state.application;
|
||||
}
|
||||
return this.props.application ?? this.state.application;
|
||||
}
|
||||
|
||||
onUpdateApplication(application) {
|
||||
this.props.onUpdateApplication(application);
|
||||
}
|
||||
|
||||
onFormFinish(name, info, forms) {
|
||||
@ -143,7 +140,7 @@ class ForgetPage extends React.Component {
|
||||
username: this.state.username,
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("emailCode"),
|
||||
phonePrefix: this.state.application?.organizationObj.phonePrefix,
|
||||
phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
|
||||
type: "login",
|
||||
}, oAuthParams).then(res => {
|
||||
if (res.status === "ok") {
|
||||
@ -166,10 +163,10 @@ class ForgetPage extends React.Component {
|
||||
|
||||
onFinish(values) {
|
||||
values.username = this.state.username;
|
||||
values.userOwner = this.state.application?.organizationObj.name;
|
||||
values.userOwner = this.getApplicationObj()?.organizationObj.name;
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.redirectToLoginPage(this.state.application, this.props.history);
|
||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
@ -355,13 +352,15 @@ class ForgetPage extends React.Component {
|
||||
{this.state.verifyType === "email" ? (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]}
|
||||
method={"forget"}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
|
||||
application={application}
|
||||
/>
|
||||
) : (
|
||||
<CountDownInput
|
||||
disabled={this.state.username === "" || this.state.verifyType === ""}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]}
|
||||
method={"forget"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
|
||||
application={application}
|
||||
/>
|
||||
)}
|
||||
@ -490,7 +489,7 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
||||
<React.Fragment>
|
||||
<CustomGithubCorner />
|
||||
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
|
||||
<Row>
|
||||
@ -548,7 +547,7 @@ class ForgetPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
32
web/src/auth/LineLoginButton.js
Normal file
32
web/src/auth/LineLoginButton.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2021 The Casdoor 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 {createButton} from "react-social-login-buttons";
|
||||
import {StaticBaseUrl} from "../Setting";
|
||||
|
||||
function Icon({width = 24, height = 24, color}) {
|
||||
return <img src={`${StaticBaseUrl}/buttons/line.svg`} alt="Sign in with Line" style={{width: 24, height: 24}} />;
|
||||
}
|
||||
|
||||
const config = {
|
||||
text: "Sign in with Line",
|
||||
icon: Icon,
|
||||
iconFormat: name => `fa fa-${name}`,
|
||||
style: {background: "#ffffff", color: "#000000"},
|
||||
activeStyle: {background: "#ededee"},
|
||||
};
|
||||
|
||||
const LineLoginButton = createButton(config);
|
||||
|
||||
export default LineLoginButton;
|
@ -29,6 +29,7 @@ import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import {CountDownInput} from "../common/CountDownInput";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import {CaptchaModal} from "../common/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -49,6 +50,9 @@ class LoginPage extends React.Component {
|
||||
enableCaptchaModal: false,
|
||||
openCaptchaModal: false,
|
||||
verifyCaptcha: undefined,
|
||||
samlResponse: "",
|
||||
relayState: "",
|
||||
redirectUrl: "",
|
||||
};
|
||||
|
||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||
@ -57,16 +61,22 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.state.type === "login" || this.state.type === "cas") {
|
||||
this.getApplication();
|
||||
} else if (this.state.type === "code") {
|
||||
this.getApplicationLogin();
|
||||
} else if (this.state.type === "saml") {
|
||||
this.getSamlApplication();
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||
componentDidMount() {
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (this.state.type === "login" || this.state.type === "cas") {
|
||||
this.getApplication();
|
||||
} else if (this.state.type === "code") {
|
||||
this.getApplicationLogin();
|
||||
} else if (this.state.type === "saml") {
|
||||
this.getSamlApplication();
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
Setting.Countries.forEach((country) => {
|
||||
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
@ -86,11 +96,13 @@ class LoginPage extends React.Component {
|
||||
AuthBackend.getApplicationLogin(oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.onUpdateApplication(res.data);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
});
|
||||
} else {
|
||||
// Setting.showMessage("error", res.msg);
|
||||
this.onUpdateApplication(null);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
msg: res.msg,
|
||||
@ -107,6 +119,7 @@ class LoginPage extends React.Component {
|
||||
if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
@ -115,11 +128,13 @@ class LoginPage extends React.Component {
|
||||
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.onUpdateApplication(res.data);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
applicationName: res.data.name,
|
||||
});
|
||||
} else {
|
||||
this.onUpdateApplication(null);
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
@ -132,25 +147,25 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
if (this.props.application !== undefined) {
|
||||
return this.props.application;
|
||||
} else {
|
||||
return this.state.application;
|
||||
}
|
||||
return this.props.application ?? this.state.application;
|
||||
}
|
||||
|
||||
onUpdateAccount(account) {
|
||||
this.props.onUpdateAccount(account);
|
||||
}
|
||||
|
||||
onUpdateApplication(application) {
|
||||
this.props.onUpdateApplication(application);
|
||||
}
|
||||
|
||||
parseOffset(offset) {
|
||||
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
|
||||
return "0 auto";
|
||||
@ -178,10 +193,11 @@ class LoginPage extends React.Component {
|
||||
|
||||
if (values["samlRequest"] !== null && values["samlRequest"] !== "" && values["samlRequest"] !== undefined) {
|
||||
values["type"] = "saml";
|
||||
values["relayState"] = oAuthParams.relayState;
|
||||
}
|
||||
|
||||
if (this.state.application.organization !== null && this.state.application.organization !== undefined) {
|
||||
values["organization"] = this.state.application.organization;
|
||||
if (this.getApplicationObj()?.organization) {
|
||||
values["organization"] = this.getApplicationObj().organization;
|
||||
}
|
||||
}
|
||||
postCodeLoginAction(res) {
|
||||
@ -293,7 +309,7 @@ class LoginPage extends React.Component {
|
||||
const responseType = values["type"];
|
||||
|
||||
if (responseType === "login") {
|
||||
Setting.showMessage("success", "Logged in successfully");
|
||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||
|
||||
const link = Setting.getFromLink();
|
||||
Setting.goToLink(link);
|
||||
@ -306,7 +322,15 @@ class LoginPage extends React.Component {
|
||||
} else if (responseType === "saml") {
|
||||
const SAMLResponse = res.data;
|
||||
const redirectUri = res.data2;
|
||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
if (this.state.application.assertionConsumerUrl !== "") {
|
||||
this.setState({
|
||||
samlResponse: res.data,
|
||||
redirectUrl: res.data2,
|
||||
relayState: oAuthParams.relayState,
|
||||
});
|
||||
} else {
|
||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.setState({openCaptchaModal: false});
|
||||
@ -554,12 +578,12 @@ class LoginPage extends React.Component {
|
||||
<span style={{float: "right"}}>
|
||||
{
|
||||
!application.enableSignUp ? null : (
|
||||
<>
|
||||
<React.Fragment>
|
||||
{i18next.t("login:No account?")}
|
||||
{
|
||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||
}
|
||||
</>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
@ -655,7 +679,7 @@ class LoginPage extends React.Component {
|
||||
const rawId = assertion.rawId;
|
||||
const sig = assertion.response.signature;
|
||||
const userHandle = assertion.response.userHandle;
|
||||
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish${AuthBackend.oAuthParamsToQuery(oAuthParams)}`, {
|
||||
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish?responseType=${values["type"]}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
@ -719,6 +743,7 @@ class LoginPage extends React.Component {
|
||||
>
|
||||
<CountDownInput
|
||||
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
|
||||
method={"login"}
|
||||
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
@ -754,6 +779,10 @@ class LoginPage extends React.Component {
|
||||
return Util.renderMessageLarge(this, this.state.msg);
|
||||
}
|
||||
|
||||
if (this.state.samlResponse !== "") {
|
||||
return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
|
||||
}
|
||||
|
||||
if (application.signinHtml !== "") {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: application.signinHtml}} />
|
||||
@ -764,16 +793,16 @@ class LoginPage extends React.Component {
|
||||
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||
return (
|
||||
<div style={{textAlign: "center"}}>
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
||||
<React.Fragment>
|
||||
<CustomGithubCorner />
|
||||
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
|
||||
<div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
<div className="login-panel">
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
@ -803,7 +832,7 @@ class LoginPage extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Result, Row} from "antd";
|
||||
import {Button, Card, Col, Result, Row} from "antd";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
@ -30,7 +30,7 @@ class PromptPage extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
type: props.type,
|
||||
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName),
|
||||
applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
|
||||
application: null,
|
||||
user: null,
|
||||
};
|
||||
@ -38,7 +38,9 @@ class PromptPage extends React.Component {
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getUser();
|
||||
this.getApplication();
|
||||
if (this.getApplicationObj() === null) {
|
||||
this.getApplication();
|
||||
}
|
||||
}
|
||||
|
||||
getUser() {
|
||||
@ -59,6 +61,7 @@ class PromptPage extends React.Component {
|
||||
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
@ -66,11 +69,11 @@ class PromptPage extends React.Component {
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
if (this.props.application !== undefined) {
|
||||
return this.props.application;
|
||||
} else {
|
||||
return this.state.application;
|
||||
}
|
||||
return this.props.application ?? this.state.application;
|
||||
}
|
||||
|
||||
onUpdateApplication(application) {
|
||||
this.props.onUpdateApplication(application);
|
||||
}
|
||||
|
||||
parseUserField(key, value) {
|
||||
@ -122,7 +125,7 @@ class PromptPage extends React.Component {
|
||||
|
||||
renderContent(application) {
|
||||
return (
|
||||
<div style={{width: "400px"}}>
|
||||
<div style={{width: "500px"}}>
|
||||
{
|
||||
this.renderAffiliation(application)
|
||||
}
|
||||
@ -247,9 +250,9 @@ class PromptPage extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center"}}>
|
||||
<div style={{display: "flex", flex: "1", justifyContent: "center"}}>
|
||||
<Card>
|
||||
<div style={{marginTop: "30px", marginBottom: "30px", textAlign: "center"}}>
|
||||
{
|
||||
Setting.renderHelmet(application)
|
||||
}
|
||||
@ -259,16 +262,12 @@ class PromptPage extends React.Component {
|
||||
{
|
||||
this.renderContent(application)
|
||||
}
|
||||
<Row style={{margin: 10}}>
|
||||
<Col span={18}>
|
||||
</Col>
|
||||
</Row>
|
||||
<div style={{marginTop: "50px"}}>
|
||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,10 @@ const authInfo = {
|
||||
Bilibili: {
|
||||
endpoint: "https://passport.bilibili.com/register/pc_oauth2.html",
|
||||
},
|
||||
Line: {
|
||||
scope: "profile%20openid%20email",
|
||||
endpoint: "https://access.line.me/oauth2/v2.1/authorize",
|
||||
},
|
||||
};
|
||||
|
||||
export function getProviderUrl(provider) {
|
||||
@ -256,5 +260,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "Bilibili") {
|
||||
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`;
|
||||
} else if (provider.type === "Line") {
|
||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import SteamLoginButton from "./SteamLoginButton";
|
||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||
import OktaLoginButton from "./OktaLoginButton";
|
||||
import DouyinLoginButton from "./DouyinLoginButton";
|
||||
import LineLoginButton from "./LineLoginButton";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import {getEvent} from "./Util";
|
||||
import {Modal} from "antd";
|
||||
@ -93,6 +94,8 @@ function getSigninButton(type) {
|
||||
return <OktaLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Douyin") {
|
||||
return <DouyinLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Line") {
|
||||
return <LineLoginButton text={text} align={"center"} />;
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@ -95,7 +95,7 @@ class SamlCallback extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{textAlign: "center"}}>
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
(this.state.msg === null) ? (
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
||||
|
@ -22,7 +22,6 @@ class SelfForgetPage extends React.Component {
|
||||
<ForgetPage
|
||||
type={"forgotPassword"}
|
||||
applicationName={authConfig.appName}
|
||||
account={this.props.account}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ import {authConfig} from "./Auth";
|
||||
class SelfLoginPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} account={this.props.account} {...this.props} />
|
||||
<LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class SignupPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
applicationName: props.match?.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName,
|
||||
applicationName: props.match.params?.applicationName ?? authConfig.appName,
|
||||
application: null,
|
||||
email: "",
|
||||
phone: "",
|
||||
@ -91,10 +91,12 @@ class SignupPage extends React.Component {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
}
|
||||
|
||||
if (applicationName !== undefined) {
|
||||
this.getApplication(applicationName);
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (applicationName !== undefined) {
|
||||
this.getApplication(applicationName);
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +107,7 @@ class SignupPage extends React.Component {
|
||||
|
||||
ApplicationBackend.getApplication("admin", applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
@ -128,11 +131,7 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
if (this.props.application !== undefined) {
|
||||
return this.props.application;
|
||||
} else {
|
||||
return this.state.application;
|
||||
}
|
||||
return this.props.application ?? this.state.application;
|
||||
}
|
||||
|
||||
getTermsofuseContent(url) {
|
||||
@ -149,6 +148,10 @@ class SignupPage extends React.Component {
|
||||
this.props.onUpdateAccount(account);
|
||||
}
|
||||
|
||||
onUpdateApplication(application) {
|
||||
this.props.onUpdateApplication(application);
|
||||
}
|
||||
|
||||
parseOffset(offset) {
|
||||
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
|
||||
return "0 auto";
|
||||
@ -373,6 +376,7 @@ class SignupPage extends React.Component {
|
||||
>
|
||||
<CountDownInput
|
||||
disabled={!this.state.validEmail}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
@ -426,6 +430,7 @@ class SignupPage extends React.Component {
|
||||
>
|
||||
<CountDownInput
|
||||
disabled={!this.state.validPhone}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
/>
|
||||
@ -630,9 +635,9 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
||||
<React.Fragment>
|
||||
<CustomGithubCorner />
|
||||
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
|
||||
<div className="login-content" style={{margin: this.props.preview ?? this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
<div className="login-panel" >
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
@ -655,7 +660,7 @@ class SignupPage extends React.Component {
|
||||
{
|
||||
this.renderModal()
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +109,12 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function sendCode(checkType, checkId, checkKey, dest, type, applicationId, checkUser) {
|
||||
export function sendCode(checkType, checkId, checkKey, method, dest, type, applicationId, checkUser) {
|
||||
const formData = new FormData();
|
||||
formData.append("checkType", checkType);
|
||||
formData.append("checkId", checkId);
|
||||
formData.append("checkKey", checkKey);
|
||||
formData.append("method", method);
|
||||
formData.append("dest", dest);
|
||||
formData.append("type", type);
|
||||
formData.append("applicationId", applicationId);
|
||||
|
@ -125,5 +125,5 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh
|
||||
}
|
||||
}, [captchaType, subType, siteKey, clientSecret, clientId2, clientSecret2]);
|
||||
|
||||
return <div id="captcha"></div>;
|
||||
return <div id="captcha" />;
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ import {CaptchaWidget} from "./CaptchaWidget";
|
||||
const {Search} = Input;
|
||||
|
||||
export const CountDownInput = (props) => {
|
||||
const {disabled, textBefore, onChange, onButtonClickArgs, application} = props;
|
||||
const {disabled, textBefore, onChange, onButtonClickArgs, application, method} = props;
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [key, setKey] = React.useState("");
|
||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||
@ -53,7 +53,7 @@ export const CountDownInput = (props) => {
|
||||
const handleOk = () => {
|
||||
setVisible(false);
|
||||
setButtonLoading(true);
|
||||
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => {
|
||||
UserBackend.sendCode(checkType, checkId, key, method, ...onButtonClickArgs).then(res => {
|
||||
setKey("");
|
||||
setButtonLoading(false);
|
||||
if (res) {
|
||||
@ -70,7 +70,7 @@ export const CountDownInput = (props) => {
|
||||
const loadCaptcha = () => {
|
||||
UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
|
||||
if (res.type === "none") {
|
||||
UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => {
|
||||
UserBackend.sendCode("none", "", "", method, ...onButtonClickArgs).then(res => {
|
||||
if (res) {
|
||||
handleCountDown(60);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ class OAuthWidget extends React.Component {
|
||||
</Col>
|
||||
<Col span={24 - this.props.labelSpan} >
|
||||
<img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
|
||||
<span style={{width: this.props.labelSpan === 3 ? "300px" : "130px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}>
|
||||
<span style={{width: this.props.labelSpan === 3 ? "300px" : "200px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}>
|
||||
{
|
||||
linkedValue === "" ? (
|
||||
"(empty)"
|
||||
@ -164,7 +164,7 @@ class OAuthWidget extends React.Component {
|
||||
</span>
|
||||
{
|
||||
linkedValue === "" ? (
|
||||
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "link")}>
|
||||
<a key={provider.displayName} href={user.id !== account.id ? null : Provider.getAuthUrl(application, provider, "link")}>
|
||||
<Button style={{marginLeft: "20px", width: "80px"}} type="primary" disabled={user.id !== account.id}>{i18next.t("user:Link")}</Button>
|
||||
</a>
|
||||
) : (
|
||||
|
@ -91,7 +91,8 @@ class PolicyTable extends React.Component {
|
||||
AdapterBackend.syncPolicies(this.props.owner, this.props.name)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({policyLists: res});
|
||||
this.setState({policyLists: res.data});
|
||||
Setting.showMessage("success", i18next.t("adapter:Sync policies successfully"));
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`);
|
||||
}
|
||||
@ -292,14 +293,15 @@ class PolicyTable extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<>
|
||||
<Button type="primary" onClick={() => {this.synPolicies();}}>
|
||||
{i18next.t("adapter:Sync")}
|
||||
</Button>
|
||||
{
|
||||
this.renderTable(this.state.policyLists)
|
||||
}
|
||||
</>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}>
|
||||
{i18next.t("adapter:Sync")}
|
||||
</Button>
|
||||
{
|
||||
this.renderTable(this.state.policyLists)
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
45
web/src/common/RedirectForm.js
Normal file
45
web/src/common/RedirectForm.js
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2022 The Casdoor 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, {useEffect} from "react";
|
||||
import i18next from "i18next";
|
||||
|
||||
export const RedirectForm = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
document.getElementById("saml").submit();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p>{i18next.t("login:Redirecting, please wait.")}</p>
|
||||
<form id="saml" method="post" action={props.redirectUrl}>
|
||||
<input
|
||||
type="hidden"
|
||||
name="SAMLResponse"
|
||||
id="samlResponse"
|
||||
value={props.samlResponse}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="RelayState"
|
||||
id="relayState"
|
||||
value={props.relayState}
|
||||
/>
|
||||
</form>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedirectForm;
|
@ -28,7 +28,7 @@ code {
|
||||
|
||||
.logo {
|
||||
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
|
||||
background-size: 130px, 27px;
|
||||
background-size: 130px, 27px !important;
|
||||
width: 130px;
|
||||
height: 27px;
|
||||
margin: 17px 0 16px 15px;
|
||||
@ -45,7 +45,3 @@ code {
|
||||
.ant-list-sm .ant-list-item {
|
||||
padding: 2px !important;
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
background-color: white !important;
|
||||
}
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip",
|
||||
"Enable signup": "Anmeldung aktivieren",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "Datei erfolgreich hochgeladen",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Passwort AN",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||
"Redirect URL": "Weiterleitungs-URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "Umleitungs-URLs",
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Aktualisierungs-Token läuft ab",
|
||||
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "Client-IP",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "Erstellte Zeit",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "Standard Avatar",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "Weiter mit",
|
||||
"Email or phone": "E-Mail oder Telefon",
|
||||
"Forgot password?": "Passwort vergessen?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Logging out...",
|
||||
"No account?": "Kein Konto?",
|
||||
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
|
||||
"Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!",
|
||||
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Anmelden",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "Mit {type} anmelden",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "Standard Avatar",
|
||||
"Edit Organization": "Organisation bearbeiten",
|
||||
"Favicon": "Févicon",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website-URL",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Detail",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Edit Product",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Image",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "New Product",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "Paypal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Ist globaler Admin",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Standort",
|
||||
"Location - Tooltip": "Standort - Tooltip",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "Passwort setzen",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Eigenschaften",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Neu erneut eingeben",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Telefon zurücksetzen...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "Link aufheben",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Foto hochladen",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "Passwort eingeben"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Enable signup - Tooltip",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Password ON",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "Please select a HTML file",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||
"Redirect URL": "Redirect URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "Redirect URLs",
|
||||
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "Client IP",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "Created time",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "Default avatar",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "Continue with",
|
||||
"Email or phone": "Email or phone",
|
||||
"Forgot password?": "Forgot password?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Logging out...",
|
||||
"No account?": "No account?",
|
||||
"Or sign in with another account": "Or sign in with another account",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
|
||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "Sign in with {type}",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "Default avatar",
|
||||
"Edit Organization": "Edit Organization",
|
||||
"Favicon": "Favicon",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Detail",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Edit Product",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Image",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "New Product",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "Paypal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Is forbidden - Tooltip",
|
||||
"Is global admin": "Is global admin",
|
||||
"Is global admin - Tooltip": "Is global admin - Tooltip",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "Location - Tooltip",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "Password Set",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Properties",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Re-enter New",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Reset Phone...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "Unlink",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Upload a photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "input password"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Activer la session de connexion - infobulle",
|
||||
"Enable signup": "Activer l'inscription",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "Fichier téléchargé avec succès",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Mot de passe activé",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "Veuillez sélectionner un fichier HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||
"Redirect URL": "URL de redirection",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "URL de redirection",
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Expiration du jeton d'actualisation",
|
||||
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "IP du client",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "Date de création",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "Avatar par défaut",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "Continuer avec",
|
||||
"Email or phone": "Courriel ou téléphone",
|
||||
"Forgot password?": "Mot de passe oublié ?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Logging out...",
|
||||
"No account?": "Pas de compte ?",
|
||||
"Or sign in with another account": "Ou connectez-vous avec un autre compte",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "Veuillez saisir votre mot de passe !",
|
||||
"Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !",
|
||||
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Se connecter",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "Se connecter avec {type}",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "Avatar par défaut",
|
||||
"Edit Organization": "Modifier l'organisation",
|
||||
"Favicon": "Favicon",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "Suppression de soft - infobulle",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "URL du site web",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Detail",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Edit Product",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Image",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "New Product",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "Paypal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Est un administrateur global",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Lier",
|
||||
"Location": "Localisation",
|
||||
"Location - Tooltip": "Localisation - Infobulle",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "Mot de passe défini",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Propriétés",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Nouvelle entrée",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Réinitialiser le téléphone...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "Délier",
|
||||
"Upload (.xlsx)": "Télécharger (.xlsx)",
|
||||
"Upload a photo": "Télécharger une photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "saisir le mot de passe"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "サインアップを有効にする",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "ファイルが正常にアップロードされました",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "パスワードON",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "HTMLファイルを選択してください",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||
"Redirect URL": "リダイレクトURL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "リダイレクトURL",
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "トークンの更新の期限が切れます",
|
||||
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "クライアント IP",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "作成日時",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "デフォルトのアバター",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "次で続ける",
|
||||
"Email or phone": "Eメールまたは電話番号",
|
||||
"Forgot password?": "パスワードを忘れましたか?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Logging out...",
|
||||
"No account?": "アカウントがありませんか?",
|
||||
"Or sign in with another account": "または別のアカウントでサインイン",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "パスワードを入力してください!",
|
||||
"Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください!",
|
||||
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "サインイン",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "{type} でサインイン",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "デフォルトのアバター",
|
||||
"Edit Organization": "組織を編集",
|
||||
"Favicon": "ファビコン",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Detail",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Edit Product",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Image",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "New Product",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "Paypal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "グローバル管理者",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "リンク",
|
||||
"Location": "場所",
|
||||
"Location - Tooltip": "場所 → ツールチップ",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "パスワード設定",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "プロパティー",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "新しい入力",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "電話番号をリセット...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "リンクを解除",
|
||||
"Upload (.xlsx)": "アップロード (.xlsx)",
|
||||
"Upload a photo": "写真をアップロード",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "パスワードを入力"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Enable signin session - Tooltip",
|
||||
"Enable signup": "Enable signup",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Password ON",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "Please select a HTML file",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
|
||||
"Redirect URL": "Redirect URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "Redirect URLs",
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "Client IP",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "Created time",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "Default avatar",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "Continue with",
|
||||
"Email or phone": "Email or phone",
|
||||
"Forgot password?": "Forgot password?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Logging out...",
|
||||
"No account?": "No account?",
|
||||
"Or sign in with another account": "Or sign in with another account",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
|
||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "Sign in with {type}",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "Default avatar",
|
||||
"Edit Organization": "Edit Organization",
|
||||
"Favicon": "Favicon",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Detail",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Edit Product",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Image",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "New Product",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "Paypal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Price",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Is global admin",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "Location - Tooltip",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "Password Set",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Properties",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Re-enter New",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Reset Phone...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "Unlink",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Upload a photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "input password"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "Edit Adapter",
|
||||
"Failed to sync policies: ": "Failed to sync policies: ",
|
||||
"Failed to sync policies": "Failed to sync policies",
|
||||
"New Adapter": "New Adapter",
|
||||
"Policies": "Policies",
|
||||
"Policies - Tooltip": "Policies - Tooltip",
|
||||
"Repeated policy rules": "Repeated policy rules",
|
||||
"Sync": "Sync"
|
||||
"Sync": "Sync",
|
||||
"Sync policies successfully": "Sync policies successfully"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "Включить сеанс входа - Подсказка",
|
||||
"Enable signup": "Включить регистрацию",
|
||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "Файл успешно загружен",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "Виды грантов",
|
||||
"Grant types - Tooltip": "Виды грантов - Подсказка",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "Новое приложение",
|
||||
"None": "None",
|
||||
"Password ON": "Пароль ВКЛ",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "Пожалуйста, выберите HTML-файл",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Ссылка на страницу успешно скопирована в буфер обмена, пожалуйста, вставьте ее в окно инкогнито или другой браузер",
|
||||
"Redirect URL": "URL перенаправления",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
|
||||
"Redirect URLs": "Перенаправление URL",
|
||||
"Redirect URLs - Tooltip": "List of redirect addresses after successful login",
|
||||
"Refresh token expire": "Срок действия обновления токена истекает",
|
||||
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
|
||||
"Right": "Right",
|
||||
"Rule": "правило",
|
||||
"SAML Reply URL": "SAML Reply URL",
|
||||
"SAML metadata": "Метаданные SAML",
|
||||
"SAML metadata - Tooltip": "Метаданные SAML - Подсказка",
|
||||
"SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
||||
"Client IP": "IP клиента",
|
||||
"Close": "Close",
|
||||
"Compact": "Compact",
|
||||
"Created time": "Время создания",
|
||||
"Dark": "Dark",
|
||||
"Default": "Default",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application - Tooltip",
|
||||
"Default avatar": "Аватар по умолчанию",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "Продолжить с",
|
||||
"Email or phone": "Электронная почта или телефон",
|
||||
"Forgot password?": "Забыли пароль?",
|
||||
"Loading": "Loading",
|
||||
"Logging out...": "Выход...",
|
||||
"No account?": "Нет учетной записи?",
|
||||
"Or sign in with another account": "Или войти с помощью другой учетной записи",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "Пожалуйста, введите ваш пароль!",
|
||||
"Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
|
||||
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Войти",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
"Sign in with {type}": "Войти с помощью {type}",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "Аватар по умолчанию",
|
||||
"Edit Organization": "Изменить организацию",
|
||||
"Favicon": "Иконка",
|
||||
"InitScore": "InitScore",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "URL сайта",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "CNY",
|
||||
"Currency": "Currency",
|
||||
"Currency - Tooltip": "Currency - Tooltip",
|
||||
"Description": "Description",
|
||||
"Description - Tooltip": "Description - Tooltip",
|
||||
"Detail": "Сведения",
|
||||
"Detail - Tooltip": "Detail - Tooltip",
|
||||
"Edit Product": "Редактирование продукта",
|
||||
"I have completed the payment": "I have completed the payment",
|
||||
"Image": "Изображение",
|
||||
"Image - Tooltip": "Image - Tooltip",
|
||||
"New Product": "Новый продукт",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "Payment providers - Tooltip",
|
||||
"Paypal": "PayPal",
|
||||
"Placing order...": "Placing order...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "Please scan the QR code to pay",
|
||||
"Price": "Цена",
|
||||
"Price - Tooltip": "Price - Tooltip",
|
||||
"Quantity": "Quantity",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Глобальный администратор",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Ссылка",
|
||||
"Location": "Местоположение",
|
||||
"Location - Tooltip": "Расположение - Подсказка",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "Пароль установлен",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Свойства",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Введите еще раз",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Сбросить телефон...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "Отвязать",
|
||||
"Upload (.xlsx)": "Загрузить (.xlsx)",
|
||||
"Upload a photo": "Загрузить фото",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "пароль для ввода"
|
||||
},
|
||||
|
@ -7,12 +7,13 @@
|
||||
},
|
||||
"adapter": {
|
||||
"Edit Adapter": "编辑适配器",
|
||||
"Failed to sync policies: ": "同步策略失败: ",
|
||||
"Failed to sync policies": "同步策略失败",
|
||||
"New Adapter": "添加适配器",
|
||||
"Policies": "策略",
|
||||
"Policies - Tooltip": "策略",
|
||||
"Repeated policy rules": "重复的策略",
|
||||
"Sync": "同步"
|
||||
"Sync": "同步",
|
||||
"Sync policies successfully": "同步策略成功"
|
||||
},
|
||||
"application": {
|
||||
"Always": "始终开启",
|
||||
@ -36,6 +37,7 @@
|
||||
"Enable signin session - Tooltip": "从应用登录Casdoor后,Casdoor是否保持会话",
|
||||
"Enable signup": "启用注册",
|
||||
"Enable signup - Tooltip": "是否允许用户注册",
|
||||
"Failed to sign in": "登录失败",
|
||||
"File uploaded successfully": "文件上传成功",
|
||||
"Form CSS": "表单CSS",
|
||||
"Form CSS - Edit": "编辑表单CSS",
|
||||
@ -45,6 +47,8 @@
|
||||
"Grant types": "OAuth授权类型",
|
||||
"Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types",
|
||||
"Left": "居左",
|
||||
"Logged in successfully": "登录成功",
|
||||
"Logged out successfully": "登出成功",
|
||||
"New Application": "添加应用",
|
||||
"None": "关闭",
|
||||
"Password ON": "开启密码",
|
||||
@ -54,12 +58,14 @@
|
||||
"Please select a HTML file": "请选择一个HTML文件",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "提醒页面URL已成功复制到剪贴板,请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
|
||||
"Redirect URL": "重定向 URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "回复 URL (断言使用者服务 URL, 使用POST请求返回响应) - Tooltip",
|
||||
"Redirect URLs": "重定向 URLs",
|
||||
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
|
||||
"Refresh token expire": "Refresh Token过期",
|
||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||
"Right": "居右",
|
||||
"Rule": "规则",
|
||||
"SAML Reply URL": "SAML回复 URL",
|
||||
"SAML metadata": "SAML元数据",
|
||||
"SAML metadata - Tooltip": "SAML协议的元数据(Metadata)信息",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
|
||||
@ -150,7 +156,10 @@
|
||||
"Click to Upload": "点击上传",
|
||||
"Client IP": "客户端IP",
|
||||
"Close": "关闭",
|
||||
"Compact": "紧凑",
|
||||
"Created time": "创建时间",
|
||||
"Dark": "黑暗",
|
||||
"Default": "默认",
|
||||
"Default application": "默认应用",
|
||||
"Default application - Tooltip": "默认应用",
|
||||
"Default avatar": "默认头像",
|
||||
@ -293,6 +302,7 @@
|
||||
"Continue with": "使用以下账号继续",
|
||||
"Email or phone": "Email或手机号",
|
||||
"Forgot password?": "忘记密码?",
|
||||
"Loading": "加载中",
|
||||
"Logging out...": "正在退出登录...",
|
||||
"No account?": "没有账号?",
|
||||
"Or sign in with another account": "或者,登录其他账号",
|
||||
@ -302,6 +312,7 @@
|
||||
"Please input your password!": "请输入您的密码!",
|
||||
"Please input your password, at least 6 characters!": "请输入您的密码,不少于6位",
|
||||
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号!",
|
||||
"Redirecting, please wait.": "正在跳转, 请稍等.",
|
||||
"Sign In": "登录",
|
||||
"Sign in with WebAuthn": "WebAuthn登录",
|
||||
"Sign in with {type}": "{type}登录",
|
||||
@ -326,6 +337,7 @@
|
||||
"Default avatar": "默认头像",
|
||||
"Edit Organization": "编辑组织",
|
||||
"Favicon": "图标",
|
||||
"InitScore": "初始积分",
|
||||
"Is profile public": "用户个人页公开",
|
||||
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
||||
"Modify rule": "修改规则",
|
||||
@ -334,6 +346,7 @@
|
||||
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
|
||||
"Tags": "标签集合",
|
||||
"Tags - Tooltip": "可供用户选择的标签的集合",
|
||||
"The user's initScore - Tooltip": "用户的初始积分",
|
||||
"View rule": "查看规则",
|
||||
"Visible": "是否可见",
|
||||
"Website URL": "网页地址",
|
||||
@ -426,9 +439,12 @@
|
||||
"CNY": "人民币",
|
||||
"Currency": "币种",
|
||||
"Currency - Tooltip": "币种 - 工具提示",
|
||||
"Description": "描述",
|
||||
"Description - Tooltip": "描述 - 工具提示",
|
||||
"Detail": "详情",
|
||||
"Detail - Tooltip": "详情 - 工具提示",
|
||||
"Edit Product": "编辑商品",
|
||||
"I have completed the payment": "支付完成",
|
||||
"Image": "图片",
|
||||
"Image - Tooltip": "图片 - 工具提示",
|
||||
"New Product": "添加商品",
|
||||
@ -437,6 +453,8 @@
|
||||
"Payment providers - Tooltip": "支付提供商 - 工具提示",
|
||||
"Paypal": "PayPal(贝宝)",
|
||||
"Placing order...": "正在下单...",
|
||||
"Please provide your username in the remark": "Please provide your username in the remark",
|
||||
"Please scan the QR code to pay": "请扫描二维码支付",
|
||||
"Price": "价格",
|
||||
"Price - Tooltip": "价格 - 工具提示",
|
||||
"Quantity": "库存",
|
||||
@ -708,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "账户是否已被禁用",
|
||||
"Is global admin": "是全局管理员",
|
||||
"Is global admin - Tooltip": "是应用程序管理员",
|
||||
"Keys": "键",
|
||||
"Link": "绑定",
|
||||
"Location": "城市",
|
||||
"Location - Tooltip": "居住地址所在的城市",
|
||||
@ -723,6 +742,7 @@
|
||||
"Password Set": "密码已设置",
|
||||
"Please select avatar from resources": "从资源中选择...",
|
||||
"Properties": "属性",
|
||||
"Properties - Tooltip": "属性",
|
||||
"Re-enter New": "重复新密码",
|
||||
"Reset Email...": "重置邮箱...",
|
||||
"Reset Phone...": "重置手机号...",
|
||||
@ -738,6 +758,7 @@
|
||||
"Unlink": "解绑",
|
||||
"Upload (.xlsx)": "上传(.xlsx)",
|
||||
"Upload a photo": "上传头像",
|
||||
"Values": "值",
|
||||
"WebAuthn credentials": "WebAuthn凭据",
|
||||
"input password": "输入密码"
|
||||
},
|
||||
|
131
web/src/propertyTable.js
Normal file
131
web/src/propertyTable.js
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2022 The Casdoor 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, Table} from "antd";
|
||||
import i18next from "i18next";
|
||||
import {DeleteOutlined} from "@ant-design/icons";
|
||||
import * as Setting from "./Setting";
|
||||
|
||||
class PropertyTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
properties: [],
|
||||
count: Object.entries(this.props.properties).length,
|
||||
};
|
||||
// transfer the Object to object[]
|
||||
Object.entries(this.props.properties).map((item, index) => {
|
||||
this.state.properties.push({key: index, name: item[0], value: item[1]});
|
||||
});
|
||||
}
|
||||
|
||||
page = 1;
|
||||
|
||||
updateTable(table) {
|
||||
this.setState({properties: table});
|
||||
const properties = {};
|
||||
table.map((item) => {
|
||||
properties[item.name] = item.value;
|
||||
});
|
||||
this.props.onUpdateTable(properties);
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {key: this.state.count, name: "", value: ""};
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
table = Setting.addRow(table, row);
|
||||
this.setState({count: this.state.count + 1});
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, index) {
|
||||
table = Setting.deleteRow(table, this.getIndex(index));
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
getIndex(index) {
|
||||
// Parameter is the row index in table, need to calculate the index in dataSource. 10 is the pageSize.
|
||||
return index + (this.page - 1) * 10;
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[this.getIndex(index)][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("user:Keys"),
|
||||
dataIndex: "name",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "name", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("user:Values"),
|
||||
dataIndex: "value",
|
||||
width: "200px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "value", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "operation",
|
||||
width: "20px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table title={() => (
|
||||
<div>
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
pagination={{onChange: page => {this.page = page;}}}
|
||||
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
this.renderTable(this.state.properties)
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PropertyTable;
|
@ -2096,6 +2096,13 @@
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@rc-component/mini-decimal@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/mini-decimal/-/mini-decimal-1.0.1.tgz#e5dbc20a6a5b0e234d279bc71ce730ab865d3910"
|
||||
integrity sha512-9N8nRk0oKj1qJzANKl+n9eNSMUGsZtjwNuDCiZ/KA+dt1fE3zq5x2XxclRcAbOIXnZcJ53ozP2Pa60gyELXagA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.0"
|
||||
|
||||
"@rc-component/portal@^1.0.0-6", "@rc-component/portal@^1.0.0-8", "@rc-component/portal@^1.0.0-9", "@rc-component/portal@^1.0.2":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.0.3.tgz#3aa2c229a7a20ac2412d864e8977e6377973416e"
|
||||
@ -3134,10 +3141,10 @@ ansi-styles@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
|
||||
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
|
||||
|
||||
antd@5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.0.3.tgz#2cdaffe1afdb5c2f0f3325b2d06d508ec6ddf85a"
|
||||
integrity sha512-Gqkba0earlR5H6gfT4nsyV3W9rL1Up1+clEXsa1+9Jem/geC2phBImpjWgVjqOjH3L5Oi8SHe0NeYBagDxwP5g==
|
||||
antd@5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.0.5.tgz#16f0ade8b2d2ea9f7bd47a8e0af81884dc504a7d"
|
||||
integrity sha512-8jWUjZ65urNHZPg9/Ywa9V0PlNfqjhewKgSPF4nraN9X5v434lDJkRBQGN7meNixQ6aM2B/JhXPm9UaJ/tAQmA==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^6.0.0"
|
||||
"@ant-design/cssinjs" "^1.0.0"
|
||||
@ -3159,7 +3166,7 @@ antd@5.0.3:
|
||||
rc-field-form "~1.27.0"
|
||||
rc-image "~5.12.0"
|
||||
rc-input "~0.1.4"
|
||||
rc-input-number "~7.3.9"
|
||||
rc-input-number "~7.4.0"
|
||||
rc-mentions "~1.13.1"
|
||||
rc-menu "~9.8.0"
|
||||
rc-motion "^2.6.1"
|
||||
@ -3175,15 +3182,15 @@ antd@5.0.3:
|
||||
rc-steps "~6.0.0-alpha.2"
|
||||
rc-switch "~4.0.0"
|
||||
rc-table "~7.26.0"
|
||||
rc-tabs "~12.4.1"
|
||||
rc-tabs "~12.4.2"
|
||||
rc-textarea "~0.4.5"
|
||||
rc-tooltip "~5.2.0"
|
||||
rc-tree "~5.7.0"
|
||||
rc-tree-select "~5.5.4"
|
||||
rc-trigger "^5.2.10"
|
||||
rc-upload "~4.3.0"
|
||||
rc-util "^5.22.5"
|
||||
scroll-into-view-if-needed "^2.2.25"
|
||||
rc-util "^5.25.2"
|
||||
scroll-into-view-if-needed "^3.0.3"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
anymatch@^3.0.3, anymatch@~3.1.2:
|
||||
@ -4011,10 +4018,10 @@ compression@^1.7.4:
|
||||
safe-buffer "5.1.2"
|
||||
vary "~1.1.2"
|
||||
|
||||
compute-scroll-into-view@^1.0.17:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
|
||||
integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
|
||||
compute-scroll-into-view@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-2.0.2.tgz#ac5cc71ca833884866e581a82d8558a6ed7ee877"
|
||||
integrity sha512-W+4Iti92hktsTtNPNeRM1vE0JdqCBk5qIabRafpr5pGrQhQ+xzCv0NGnFzTCKmW4yGLm9Aovbw8YNxloe2z9tQ==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
@ -8934,12 +8941,13 @@ rc-image@~5.12.0:
|
||||
rc-dialog "~9.0.0"
|
||||
rc-util "^5.0.6"
|
||||
|
||||
rc-input-number@~7.3.9:
|
||||
version "7.3.11"
|
||||
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.3.11.tgz#c7089705a220e1a59ba974fabf89693e00dd2442"
|
||||
integrity sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==
|
||||
rc-input-number@~7.4.0:
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.4.0.tgz#b8b4ffa8bbc04198e79ce8b9611756d046d128ec"
|
||||
integrity sha512-r/Oub/sPYbzqLNUOHnnc9sbCu78a81KX+RCbRwmpvB4W6nptUySbdWS5KHV4Hak5CAE1LAd+wWm5JjvZizG1FA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
"@rc-component/mini-decimal" "^1.0.1"
|
||||
classnames "^2.2.5"
|
||||
rc-util "^5.23.0"
|
||||
|
||||
@ -9148,10 +9156,10 @@ rc-table@~7.26.0:
|
||||
rc-util "^5.22.5"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-tabs@~12.4.1:
|
||||
version "12.4.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.4.1.tgz#a45aa7560ae4e2a91426e74a2e76566f5c8ec9cc"
|
||||
integrity sha512-yViBZypldDnPffk3IPTarplF1RAv8VQDDnOt9sHDU7pjCnqE72csCU+7kjbLPtPpYniIMQJYyWxh/lsBUcagSA==
|
||||
rc-tabs@~12.4.2:
|
||||
version "12.4.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.4.2.tgz#487a1b3f8d8cf0bfc121224013dab00d4a8e0532"
|
||||
integrity sha512-FFlGwuTjQUznWzJtyhmHc6KAp5lRQFxKUv9Aj1UtsOYe2e7WGmuzcrd+/LQchuPe0VjhaZPdGkmFGcqGqNO6ow==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
classnames "2.x"
|
||||
@ -9269,6 +9277,15 @@ rc-util@^5.21.2, rc-util@^5.23.0:
|
||||
react-is "^16.12.0"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-util@^5.25.2:
|
||||
version "5.25.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.25.2.tgz#09fd3ce88da7d2149427d51e40a84e3527f5a263"
|
||||
integrity sha512-OyCO675K/rh4zG3e+LYaHw54WQFEYGV9ibkGawQxqCvf0G0PzUrLQjgZ6SfoHORdbEKN7eQMFn3hHQyA/P8Y5Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
react-is "^16.12.0"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8:
|
||||
version "3.4.8"
|
||||
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.8.tgz#c24c10c6940546b7e2a5e9809402c6716adfd26c"
|
||||
@ -9901,12 +9918,12 @@ schema-utils@^4.0.0:
|
||||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
scroll-into-view-if-needed@^2.2.25:
|
||||
version "2.2.29"
|
||||
resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz#551791a84b7e2287706511f8c68161e4990ab885"
|
||||
integrity sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==
|
||||
scroll-into-view-if-needed@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.3.tgz#57256bef78f3c3c288070d2aaa63cf547aa11e70"
|
||||
integrity sha512-QoCH0lVw0tbA7Rl6sToH7e1tO3n95Oi6JgBgC8hEpNNZUC91MfasJ/4E1ZdbzGueNDZ+Y7ObfRaelKUgTyPbJA==
|
||||
dependencies:
|
||||
compute-scroll-into-view "^1.0.17"
|
||||
compute-scroll-into-view "^2.0.2"
|
||||
|
||||
select-hose@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
Reference in New Issue
Block a user