Compare commits

...

33 Commits

Author SHA1 Message Date
d498bc60ce feat: edit user properties (#1435) 2022-12-31 15:27:53 +08:00
7bbe1e38c1 fix: fix translate error (#1432)
* fix:fix translate error

* Delete TelegramLoginButton.js

* Update data.json

* Update data.json

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-30 12:10:18 +08:00
f465fc6ce0 feat: support changing theme in antd 5 (#1430)
* feat: add global theme change function

* feat: add icons

* feat: in app theme changer

* feat: use antd built-in themes

* fix: multiple styling problem

* fix: theme init from localstorage

* feat: dark mode footer

* feat: casdoor logo color theme

* feat: select theme box icon adaptive to theme

* fix: menu bar style

* fix: language box style

* feat: translation

* feat: update translation of select theme box without reloading

* fix: mobile view

* fix: better structured select theme box

* feat: add compact icon

* fix: redundant theme fetch

* fix: redundant theme fetch

* fix: various styling problems
2022-12-29 22:30:37 +08:00
c952c2f2f4 feat: fix login with password bug when feature is disabled (#1428) 2022-12-27 14:46:57 +08:00
86ae97d1e5 feat: fix the bug that spin is always showing when response error (#1424) 2022-12-24 17:55:36 +08:00
6ea73e3eca fix: show background image in preview (#1425) 2022-12-24 17:47:05 +08:00
a71a190db5 feat: fix bug in redirectToLoginPage() (#1422) 2022-12-24 01:10:02 +08:00
da69d94445 feat: fix the bug that spin in oauth is always showing (#1421) 2022-12-23 15:06:51 +08:00
b8b915abe1 feat: check AccessPermission in multiple permissions (#1420) 2022-12-23 14:06:02 +08:00
5d1548e989 feat: fix absolute URL redirection (#1419)
* fix: redirect to absolute url

* fix: original jump
2022-12-23 11:05:15 +08:00
a0dc6e06cd feat: add EntryPage for login, signup pages to fix background flashing issue (#1416)
* feat: fix flush in login Pages

* fix: code format

* fix: improve code

* Update App.js

* Update EntryPage.js

* fix: optimize api request

* Update App.js

* Update App.js

* fix: fix css

* fix: css and getApllicationObj

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-22 23:39:02 +08:00
ae130788ec feat: add Line support as OAuth 3rd-party login (#1413) 2022-12-21 02:25:58 +08:00
f075d0fd74 Refactor out application.IsRedirectUriValid() 2022-12-21 00:35:33 +08:00
65d4946042 feat: add valid key for creating token (#1411) 2022-12-20 22:05:00 +08:00
Liu
26acece8af feat: add all other missing objects to init_data (#1407)
* Add all other missing objects to init_data.json

* Format golang code

* feat: add all other missing objects to init_data

* feat: add all other missing objects to init_data
2022-12-18 01:49:42 +08:00
48a0c8473f Improve README 2022-12-18 01:41:12 +08:00
082ae3c91e fix: fix undefined owner bug in AdapterEditPage (#1406) 2022-12-17 21:21:39 +08:00
1ee2ff1d30 feat: now dingtalk OAuth returns all error messages to frontend (#1405) 2022-12-17 21:10:20 +08:00
c0d9969013 Add description to product 2022-12-16 23:35:30 +08:00
1bdee13150 Fix bug in renderQrCodeModal() 2022-12-16 23:28:43 +08:00
d668022af0 feat: fix length of policy and [policy_define] in model inconsistent (#1400) 2022-12-15 20:42:55 +08:00
e227875c2b feat: add post methed for saml response (#1399) 2022-12-13 22:32:45 +08:00
e473de3162 feat: fix sign in error via webauthn (#1398)
* fix: fix sign in error via webauthn

* fix review problems
2022-12-13 16:57:42 +08:00
c5ef841d3f Disable isValidIdCard() 2022-12-12 01:07:31 +08:00
d46288b591 Add renderQrCodeModal() 2022-12-12 00:42:45 +08:00
b968bf033c fix: case insensitive country name and country abbreviation search in region selection (#1394) 2022-12-11 18:14:25 +08:00
eca2527bc0 feat: fix bug in signup and reset phone and email (#1396)
* fix: fix bug in signup and reset phone and email

* delete useless addition
2022-12-11 15:52:36 +08:00
ef836acfe9 fix: login page flag icon preload (#1393) 2022-12-11 11:22:58 +08:00
a51f0d7c08 feat: init score in organization (#1388)
* feat: init score in organization

* Update OrganizationEditPage.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-10 22:27:12 +08:00
e3c36beaf4 fix: the link button with disabled style but can click (#1390) 2022-12-10 22:14:20 +08:00
19dce838d1 fix: fix invalid url in applications page (#1389) 2022-12-10 22:06:21 +08:00
b41d8652f0 feat: fix showing wrong error messages (#1385) 2022-12-09 15:11:13 +08:00
e705eecffe feat: response with status in casbin_adapter.go (#1384)
* fix: response standardized information with status in `casbin_adapter.go`

* fix: remove redundant statements
2022-12-08 10:22:59 +08:00
76 changed files with 1515 additions and 437 deletions

View File

@ -44,14 +44,12 @@
## Online demo ## Online demo
- International: https://door.casdoor.org (read-only) - Read-only site: https://door.casdoor.com (any modification operation will fail)
- Asian mirror: https://door.casdoor.com (read-only) - Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
## Documentation ## Documentation
- International: https://casdoor.org https://casdoor.org
- Asian mirror: https://casdoor.cn
## Install ## Install

View File

@ -161,7 +161,7 @@ func (c *ApiController) Signup() {
username = id username = id
} }
initScore, err := getInitScore() initScore, err := getInitScore(organization)
if err != nil { if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error()) c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return return

View File

@ -170,7 +170,7 @@ func (c *ApiController) GetApplicationLogin() {
} }
func setHttpClient(idProvider idp.IdProvider, providerType string) { 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) idProvider.SetHttpClient(proxy.ProxyHttpClient)
} else { } else {
idProvider.SetHttpClient(proxy.DefaultHttpClient) 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)) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
return 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) { if object.CheckToEnableCaptcha(application) {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret) isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
@ -412,7 +416,7 @@ func (c *ApiController) Login() {
properties := map[string]string{} properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2) properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
initScore, err := getInitScore() initScore, err := getInitScore(organization)
if err != nil { if err != nil {
c.ResponseError(fmt.Errorf(c.T("auth:Get init score failed, error: %w"), err).Error()) c.ResponseError(fmt.Errorf(c.T("auth:Get init score failed, error: %w"), err).Error())
return return

View File

@ -32,8 +32,8 @@ func (c *ApiController) GetCasbinAdapters() {
sortField := c.Input().Get("sortField") sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" { if limit == "" || page == "" {
c.Data["json"] = object.GetCasbinAdapters(owner) adapters := object.GetCasbinAdapters(owner)
c.ServeJSON() c.ResponseOk(adapters)
} else { } else {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value))) paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
@ -44,8 +44,8 @@ func (c *ApiController) GetCasbinAdapters() {
func (c *ApiController) GetCasbinAdapter() { func (c *ApiController) GetCasbinAdapter() {
id := c.Input().Get("id") id := c.Input().Get("id")
c.Data["json"] = object.GetCasbinAdapter(id) adapter := object.GetCasbinAdapter(id)
c.ServeJSON() c.ResponseOk(adapter)
} }
func (c *ApiController) UpdateCasbinAdapter() { func (c *ApiController) UpdateCasbinAdapter() {
@ -96,8 +96,7 @@ func (c *ApiController) SyncPolicies() {
return return
} }
c.Data["json"] = policies c.ResponseOk(policies)
c.ServeJSON()
} }
func (c *ApiController) UpdatePolicy() { func (c *ApiController) UpdatePolicy() {

View File

@ -261,7 +261,7 @@ func (c *ApiController) TokenLogout() {
flag, application := object.DeleteTokenByAccessToken(token) flag, application := object.DeleteTokenByAccessToken(token)
redirectUri := c.Input().Get("post_logout_redirect_uri") redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state") 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) c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
return return
} }

View File

@ -84,7 +84,7 @@ func (c *ApiController) SetTokenErrorHttpStatus() {
func (c *ApiController) RequireSignedIn() (string, bool) { func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername() userId := c.GetSessionUsername()
if userId == "" { 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 "", false
} }
return userId, true return userId, true
@ -119,8 +119,12 @@ func (c *ApiController) RequireAdmin() (string, bool) {
return user.Owner, true return user.Owner, true
} }
func getInitScore() (int, error) { func getInitScore(organization *object.Organization) (int, error) {
return strconv.Atoi(conf.GetConfigString("initScore")) 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) { func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {

View File

@ -47,6 +47,7 @@ func (c *ApiController) SendVerificationCode() {
checkKey := c.Ctx.Request.Form.Get("checkKey") checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser") checkUser := c.Ctx.Request.Form.Get("checkUser")
applicationId := c.Ctx.Request.Form.Get("applicationId") applicationId := c.Ctx.Request.Form.Get("applicationId")
method := c.Ctx.Request.Form.Get("method")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" { if destType == "" {
@ -119,7 +120,7 @@ func (c *ApiController) SendVerificationCode() {
} }
userByEmail := object.GetUserByEmail(organization.Name, dest) 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")) c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return return
} }
@ -136,7 +137,7 @@ func (c *ApiController) SendVerificationCode() {
} }
userByPhone := object.GetUserByPhone(organization.Name, dest) 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")) c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return return
} }

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 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 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", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -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) 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 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 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 type: %s is not supported": "不支持该类型的提供商: %s",
"The provider: %s is not enabled for the application": "提供商: %s 未被启用", "The provider: %s is not enabled for the application": "提供商: %s 未被启用",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员", "The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员",
"The user: %s/%s doesn't exist": "用户不存在: %s/%s", "The user: %s/%s doesn't exist": "用户不存在: %s/%s",
"Turing test failed.": "真人验证失败", "Turing test failed.": "真人验证失败",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未授权的操作" "Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "服务 %s 与 %s 不匹配" "Service %s and %s do not match": "服务 %s 与 %s 不匹配"

View File

@ -256,6 +256,8 @@ func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
} }
if data.ErrCode == 60121 { if data.ErrCode == 60121 {
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong") 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 return true, nil
} }

View File

@ -98,7 +98,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return nil return nil
} }
var gothList = []string{"Apple", "AzureAD", "Slack", "Steam"} var gothList = []string{"Apple", "AzureAD", "Slack", "Steam", "Line"}
func isGothSupport(provider string) bool { func isGothSupport(provider string) bool {
for _, value := range gothList { for _, value := range gothList {

View File

@ -156,5 +156,187 @@
"autoSync": 0, "autoSync": 0,
"lastSync": "" "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": ""
}
] ]
} }

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"net/url"
"regexp" "regexp"
"strings" "strings"
@ -51,6 +50,7 @@ type Application struct {
EnableCodeSignin bool `json:"enableCodeSignin"` EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"` EnableSamlCompress bool `json:"enableSamlCompress"`
EnableWebAuthn bool `json:"enableWebAuthn"` EnableWebAuthn bool `json:"enableWebAuthn"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"` Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` 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) return fmt.Sprintf("%s/%s", application.Owner, application.Name)
} }
func CheckRedirectUriValid(application *Application, redirectUri string) bool { func (application *Application) IsRedirectUriValid(redirectUri string) bool {
validUri := false isValid := false
for _, tmpUri := range application.RedirectUris { for _, targetUri := range application.RedirectUris {
tmpUriRegex := regexp.MustCompile(tmpUri) targetUriRegex := regexp.MustCompile(targetUri)
if tmpUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, tmpUri) { if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
validUri = true isValid = true
break break
} }
} }
return validUri return isValid
} }
func IsAllowOrigin(origin string) bool { func IsOriginAllowed(origin string) bool {
allowOrigin := false applications := GetApplications("")
originUrl, err := url.Parse(origin) for _, application := range applications {
if err != nil { if application.IsRedirectUriValid(origin) {
return false return true
}
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
} }
} }
return false
return allowOrigin
} }
func getApplicationMap(organization string) map[string]*Application { func getApplicationMap(organization string) map[string]*Application {

View File

@ -313,8 +313,9 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return true, err return true, err
} }
enforcer := getEnforcer(permission) enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read") if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
break return allowed, err
}
} }
} }
return allowed, err return allowed, err

View File

@ -59,6 +59,7 @@ func initBuiltInOrganization() bool {
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"}, Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
InitScore: 2000,
AccountItems: []*AccountItem{ AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"}, {Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},

View File

@ -23,6 +23,15 @@ type InitData struct {
Certs []*Cert `json:"certs"` Certs []*Cert `json:"certs"`
Providers []*Provider `json:"providers"` Providers []*Provider `json:"providers"`
Ldaps []*Ldap `json:"ldaps"` 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() { func InitFromFile() {
@ -46,6 +55,33 @@ func InitFromFile() {
for _, ldap := range initData.Ldaps { for _, ldap := range initData.Ldaps {
initDefinedLdap(ldap) 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{}, Certs: []*Cert{},
Providers: []*Provider{}, Providers: []*Provider{},
Ldaps: []*Ldap{}, 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) err := util.JsonToStruct(s, data)
if err != nil { if err != nil {
@ -89,6 +134,41 @@ func readInitDataFromFile(filePath string) *InitData {
application.RedirectUris = []string{} 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 return data
} }
@ -174,3 +254,84 @@ func initDefinedProvider(provider *Provider) {
} }
AddProvider(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)
}

View File

@ -47,6 +47,7 @@ type Organization struct {
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"` Languages []string `xorm:"varchar(255)" json:"languages"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
InitScore int `json:"initScore"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"` IsProfilePublic bool `json:"isProfilePublic"`

View File

@ -41,7 +41,7 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
r = sub, obj, act r = sub, obj, act
[policy_definition] [policy_definition]
p = sub, obj, act p = sub, obj, act, "", "", permissionId
[role_definition] [role_definition]
g = _, _ g = _, _

View File

@ -27,15 +27,16 @@ type Product struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Image string `xorm:"varchar(100)" json:"image"` Image string `xorm:"varchar(100)" json:"image"`
Detail string `xorm:"varchar(255)" json:"detail"` Detail string `xorm:"varchar(255)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"` Description string `xorm:"varchar(100)" json:"description"`
Currency string `xorm:"varchar(100)" json:"currency"` Tag string `xorm:"varchar(100)" json:"tag"`
Price float64 `json:"price"` Currency string `xorm:"varchar(100)" json:"currency"`
Quantity int `json:"quantity"` Price float64 `json:"price"`
Sold int `json:"sold"` Quantity int `json:"quantity"`
Providers []string `xorm:"varchar(100)" json:"providers"` Sold int `json:"sold"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"` Providers []string `xorm:"varchar(100)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"` 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) { func ExtendProductWithProviders(product *Product) {
if product == nil {
return
}
product.ProviderObjs = []*Provider{} product.ProviderObjs = []*Provider{}
m := getProviderMap(product.Owner) m := getProviderMap(product.Owner)

View File

@ -240,8 +240,8 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} }
// verify samlRequest // verify samlRequest
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid { if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
return "", "", fmt.Errorf("err: invalid issuer url") return "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
} }
// get certificate string // get certificate string
@ -251,6 +251,11 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
// redirect Url (Assertion Consumer Url)
if application.SamlReplyUrl != "" {
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
}
// build signedResponse // build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris) samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{

View File

@ -18,7 +18,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
@ -169,6 +168,10 @@ func GetToken(id string) *Token {
return getToken(owner, name) 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 { func UpdateToken(id string, token *Token) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getToken(owner, name) == nil { 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 return i18n.Translate(lang, "token:Invalid client_id"), nil
} }
validUri := false if !application.IsRedirectUriValid(redirectUri) {
for _, tmpUri := range application.RedirectUris {
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break
}
}
if !validUri {
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application
} }

View File

@ -102,6 +102,7 @@ type User struct {
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"` Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"` Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"` Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Line string `xorm:"line varchar(100)" json:"line"`
Custom string `xorm:"custom varchar(100)" json:"custom"` Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`

View File

@ -34,7 +34,7 @@ func CorsFilter(ctx *context.Context) {
originConf := conf.GetConfigString("origin") originConf := conf.GetConfigString("origin")
if origin != "" && originConf != "" && origin != originConf { if origin != "" && originConf != "" && origin != originConf {
if object.IsAllowOrigin(origin) { if object.IsOriginAllowed(origin) {
ctx.Output.Header(headerAllowOrigin, origin) ctx.Output.Header(headerAllowOrigin, origin)
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS") ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization") ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")

View File

@ -9,7 +9,7 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"antd": "5.0.3", "antd": "5.0.5",
"codemirror": "^5.61.1", "codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0", "core-js": "^3.25.0",

View File

@ -48,12 +48,14 @@ class AdapterEditPage extends React.Component {
getAdapter() { getAdapter() {
AdapterBackend.getAdapter(this.state.owner, this.state.adapterName) AdapterBackend.getAdapter(this.state.owner, this.state.adapterName)
.then((adapter) => { .then((res) => {
this.setState({ if (res.status === "ok") {
adapter: adapter, this.setState({
}); adapter: res.data,
});
this.getModels(adapter.owner); this.getModels(this.state.owner);
}
}); });
} }

View File

@ -87,7 +87,7 @@ class AdapterListPage extends BaseListPage {
...this.getColumnSearchProps("name"), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/adapters/${text}`}> <Link to={`/adapters/${record.organization}/${text}`}>
{text} {text}
</Link> </Link>
); );

View File

@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; 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 {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage"; import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage"; import OrganizationEditPage from "./OrganizationEditPage";
@ -55,26 +55,21 @@ import CustomGithubCorner from "./CustomGithubCorner";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import * as Auth from "./auth/Auth"; import * as Auth from "./auth/Auth";
import SignupPage from "./auth/SignupPage"; import EntryPage from "./EntryPage";
import ResultPage from "./auth/ResultPage"; 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 * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from "./SelectLanguageBox"; import SelectLanguageBox from "./SelectLanguageBox";
import i18next from "i18next"; import i18next from "i18next";
import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from "./auth/SamlCallback"; import SamlCallback from "./auth/SamlCallback";
import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage"; import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage"; import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo"; import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage"; import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage"; import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import SelectThemeBox from "./SelectThemeBox";
const {Header, Footer, Content} = Layout; const {Header, Footer, Content} = Layout;
@ -87,6 +82,8 @@ class App extends Component {
account: undefined, account: undefined,
uri: null, uri: null,
menuVisible: false, menuVisible: false,
themeAlgorithm: null,
logo: null,
}; };
Setting.initServerUrl(); Setting.initServerUrl();
@ -101,6 +98,16 @@ class App extends Component {
this.getAccount(); 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() { componentDidUpdate() {
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const uri = location.pathname; const uri = location.pathname;
@ -190,6 +197,10 @@ class App extends Component {
return ""; return "";
} }
getTheme() {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
}
setLanguage(account) { setLanguage(account) {
const language = account?.language; const language = account?.language;
if (language !== "" && language !== i18next.language) { if (language !== "" && language !== i18next.language) {
@ -224,7 +235,7 @@ class App extends Component {
this.setLanguage(account); this.setLanguage(account);
} else { } else {
if (res.data !== "Please login first") { 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, account: null,
}); });
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", i18next.t("application:Logged out successfully"));
const redirectUri = res.data2; const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
@ -471,54 +482,62 @@ class App extends Component {
renderRouter() { renderRouter() {
return ( return (
<div> <ConfigProvider theme={{
<Switch> token: {
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorPrimary: "rgb(89,54,213)",
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorInfo: "rgb(89,54,213)",
<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} />)} /> algorithm: this.state.themeAlgorithm,
<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} />)} /> <div>
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Switch>
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} /> <Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} /> <Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} /> <Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} /> <Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} /> <Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} /> <Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} /> <Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} /> <Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/} <Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} /> <Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} /> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} /> <Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} /> <Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} /> {/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} /> <Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} /> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} /> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} /> <Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage 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.")} <Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} /> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
</Switch> <Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
</div> <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()) { if (!Setting.isMobile()) {
return ( return (
<Layout id="parent-area"> <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 : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
<div> <Menu
<Menu items={this.renderMenu()}
// theme="dark" mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
items={this.renderMenu()} selectedKeys={[`${this.state.selectedMenuKey}`]}
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
selectedKeys={[`${this.state.selectedMenuKey}`]} />
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}} {
> this.renderAccount()
</Menu> }
{ {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
this.renderAccount() {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{backgroundColor: "#f5f5f5", alignItems: "stretch", display: "flex", flexDirection: "column"}}> <Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
<Card className="content-warp-card"> <Card className="content-warp-card">
{ {
this.renderRouter() this.renderRouter()
@ -576,11 +592,11 @@ class App extends Component {
} else { } else {
return ( return (
<Layout> <Layout>
<Header style={{padding: "0", marginBottom: "3px"}}> <Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
@ -598,12 +614,11 @@ class App extends Component {
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text"> <Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")} {i18next.t("general:Menu")}
</Button> </Button>
<div style = {{float: "right"}}> {
{ this.renderAccount()
this.renderAccount() }
} {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />} {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{display: "flex", flexDirection: "column"}} >{ <Content style={{display: "flex", flexDirection: "column"}} >{
this.renderRouter()} this.renderRouter()}
@ -619,74 +634,66 @@ class App extends Component {
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ // https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return ( return (
<> <React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />} {!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
<Footer id="footer" style={ <Footer id="footer" style={
{ {
borderTop: "1px solid #e8e8e8",
backgroundColor: "white",
textAlign: "center", 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> </Footer>
</> </React.Fragment>
); );
} }
isDoorPages() { isDoorPages() {
return this.isEntryPages() || window.location.pathname.startsWith("/callback");
}
isEntryPages() {
return window.location.pathname.startsWith("/signup") || return window.location.pathname.startsWith("/signup") ||
window.location.pathname.startsWith("/login") || window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/callback") ||
window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/forget") || 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() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<> <React.Fragment>
<Layout id="parent-area"> <Layout id="parent-area">
<Content style={{display: "flex", justifyContent: "center"}}> <Content style={{display: "flex", justifyContent: "center"}}>
<Switch> {
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} /> this.isEntryPages() ?
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} /> <EntryPage account={this.state.account} 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} />)} /> <Switch>
<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="/callback" component={AuthCallback} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
<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);}} />} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} /> </Switch>
<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>
</Content> </Content>
{ {
this.renderFooter() this.renderFooter()
} }
</Layout> </Layout>
</> </React.Fragment>
); );
} }
return ( return (
<> <React.Fragment>
<FloatButton.BackTop /> <FloatButton.BackTop />
<CustomGithubCorner /> <CustomGithubCorner />
{ {
this.renderContent() this.renderContent()
} }
</> </React.Fragment>
); );
} }
@ -702,6 +709,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()
@ -723,6 +731,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()

View File

@ -13,14 +13,12 @@
} }
.App-header { .App-header {
background-color: #282c34;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white;
} }
.App-link { .App-link {
@ -41,7 +39,6 @@ img {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5;
} }
.panel-logo { .panel-logo {
@ -55,7 +52,7 @@ img {
background-repeat: no-repeat; background-repeat: no-repeat;
border-radius: 5px; border-radius: 5px;
width: 45px; width: 45px;
height: 65px; height: 100%;
float: right; float: right;
cursor: pointer; 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 { .rightDropDown {
border-radius: 7px;
&:hover { &:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
color: black;
} }
} }
@ -128,3 +148,9 @@ img {
background-size: 100% 100%; background-size: 100% 100%;
background-attachment: fixed; background-attachment: fixed;
} }
.ant-menu-horizontal {
border-bottom: none !important;
margin-right: 30px;
right: 230px;
}

View File

@ -545,6 +545,16 @@ class ApplicationEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </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"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} : {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() { renderSignupSigninPreview() {
let signUpUrl = `/signup/${this.state.application.name}`; 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 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) { if (!this.state.application.enablePassword) {
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize"); 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")} {i18next.t("application:Copy signup page URL")}
</Button> </Button>
<br /> <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 ? ( 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> </div>
</Col> </Col>
<Col span={previewGrid}> <Col span={previewGrid}>
@ -752,9 +766,11 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signin page URL")} {i18next.t("application:Copy signin page URL")}
</Button> </Button>
<br /> <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"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} /> <div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<div style={maskStyle} /> <LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
</div>
<div style={{overflow: "auto", ...maskStyle}} />
</div> </div>
</Col> </Col>
</React.Fragment> </React.Fragment>

View File

@ -197,9 +197,9 @@ class ApplicationListPage extends BaseListPage {
<List.Item> <List.Item>
<div style={{display: "inline"}}> <div style={{display: "inline"}}>
<Tooltip placement="topLeft" title="Edit"> <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> </Tooltip>
<Link to={`/providers/${providerItem.name}`}> <Link to={`/providers/${record.organization}/${providerItem.name}`}>
{providerItem.name} {providerItem.name}
</Link> </Link>
</div> </div>

84
web/src/EntryPage.js Normal file
View 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;

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; 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 OrganizationBackend from "./backend/OrganizationBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as LdapBackend from "./backend/LdapBackend"; import * as LdapBackend from "./backend/LdapBackend";
@ -86,7 +86,6 @@ class OrganizationEditPage extends React.Component {
updateOrganizationField(key, value) { updateOrganizationField(key, value) {
value = this.parseOrganizationField(key, value); value = this.parseOrganizationField(key, value);
const organization = this.state.organization; const organization = this.state.organization;
organization[key] = value; organization[key] = value;
this.setState({ this.setState({
@ -280,6 +279,16 @@ class OrganizationEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </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"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :

View File

@ -13,7 +13,8 @@
// limitations under the License. // limitations under the License.
import React from "react"; 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 i18next from "i18next";
import * as ProductBackend from "./backend/ProductBackend"; import * as ProductBackend from "./backend/ProductBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -26,6 +27,7 @@ class ProductBuyPage extends React.Component {
productName: props.match?.params.productName, productName: props.match?.params.productName,
product: null, product: null,
isPlacingOrder: false, isPlacingOrder: false,
qrCodeModalProvider: null,
}; };
} }
@ -34,6 +36,10 @@ class ProductBuyPage extends React.Component {
} }
getProduct() { getProduct() {
if (this.state.productName === undefined) {
return;
}
ProductBackend.getProduct("admin", this.state.productName) ProductBackend.getProduct("admin", this.state.productName)
.then((product) => { .then((product) => {
this.setState({ this.setState({
@ -75,6 +81,13 @@ class ProductBuyPage extends React.Component {
} }
buyProduct(product, provider) { buyProduct(product, provider) {
if (provider.clientId.startsWith("http")) {
this.setState({
qrCodeModalProvider: provider,
});
return;
}
this.setState({ this.setState({
isPlacingOrder: true, 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")
}
:&nbsp;&nbsp;
{
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) { getPayButton(provider) {
let text = provider.type; let text = provider.type;
if (provider.type === "Alipay") { if (provider.type === "Alipay") {
@ -185,6 +237,9 @@ class ProductBuyPage extends React.Component {
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Spin> </Spin>
{
this.renderQrCodeModal()
}
</div> </div>
); );
} }

View File

@ -153,6 +153,16 @@ class ProductEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} : {Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} :

View File

@ -92,6 +92,7 @@ export const ResetModal = (props) => {
<CountDownInput <CountDownInput
textBefore={i18next.t("code:Code You Received")} textBefore={i18next.t("code:Code You Received")}
onChange={setCode} onChange={setCode}
method={"reset"}
onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]} onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]}
application={application} application={application}
/> />

View File

@ -113,7 +113,7 @@ class ResourceListPage extends BaseListPage {
...this.getColumnSearchProps("application"), ...this.getColumnSearchProps("application"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/applications/${text}`}> <Link to={`/applications/${record.organization}/${text}`}>
{text} {text}
</Link> </Link>
); );

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Dropdown, Menu} from "antd"; import {Dropdown} from "antd";
import "./App.less"; import "./App.less";
function flagIcon(country, alt) { function flagIcon(country, alt) {
@ -32,16 +32,7 @@ class SelectLanguageBox extends React.Component {
}; };
} }
items = [ items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
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", "Русский")),
];
getOrganizationLanguages(languages) { getOrganizationLanguages(languages) {
const select = []; const select = [];
@ -53,15 +44,12 @@ class SelectLanguageBox extends React.Component {
render() { render() {
const languageItems = this.getOrganizationLanguages(this.state.languages); const languageItems = this.getOrganizationLanguages(this.state.languages);
const menu = ( const onClick = (e) => {
<Menu items={languageItems} onClick={(e) => { Setting.setLanguage(e.key);
Setting.setLanguage(e.key); };
}}>
</Menu>
);
return ( return (
<Dropdown overlay={menu} > <Dropdown menu={{items: languageItems, onClick}} >
<div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} /> <div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} />
</Dropdown> </Dropdown>
); );

View File

@ -42,12 +42,15 @@ class SelectRegionBox extends React.Component {
placeholder="Please select country/region" placeholder="Please select country/region"
onChange={(value => {this.onChange(value);})} onChange={(value => {this.onChange(value);})}
filterOption={(input, option) => 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) => ( 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}} /> <img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}} />
{`${item.name} (${item.code})`} {`${item.name} (${item.code})`}
</Option> </Option>

81
web/src/SelectThemeBox.js Normal file
View 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;

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; 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 {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n"; import "./i18n";
@ -33,6 +33,23 @@ export const StaticBaseUrl = "https://cdn.casbin.org";
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html // https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
export const CountryRegionData = getCountryRegionData(); 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 = { export const OtherProviderInfo = {
SMS: { SMS: {
"Aliyun SMS": { "Aliyun SMS": {
@ -251,8 +268,10 @@ export function isValidPersonName(personName) {
} }
export function isValidIdCard(idCard) { 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 idCard !== "";
return idCardRegex.test(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) { export function isValidEmail(email) {
@ -262,34 +281,40 @@ export function isValidEmail(email) {
} }
export function isValidPhone(phone) { export function isValidPhone(phone) {
if (phone === "") { return phone !== "";
return false;
}
// https://learnku.com/articles/31543, `^s*$` filter empty email individually. // if (phone === "") {
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 false;
return phoneRegex.test(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);
} }
export function isValidInvoiceTitle(invoiceTitle) { export function isValidInvoiceTitle(invoiceTitle) {
if (invoiceTitle === "") { return invoiceTitle !== "";
return false;
}
// https://blog.css8.cn/post/14210975.html // if (invoiceTitle === "") {
const invoiceTitleRegex = /^[()\u4e00-\u9fa5]{0,50}$/; // return false;
return invoiceTitleRegex.test(invoiceTitle); // }
//
// // https://blog.css8.cn/post/14210975.html
// const invoiceTitleRegex = /^[()\u4e00-\u9fa5]{0,50}$/;
// return invoiceTitleRegex.test(invoiceTitle);
} }
export function isValidTaxId(taxId) { export function isValidTaxId(taxId) {
// https://www.codetd.com/article/8592083 return taxId !== "";
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++) { // // https://www.codetd.com/article/8592083
if (regArr[i].test(taxId)) { // 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];
return true; // for (let i = 0; i < regArr.length; i++) {
} // if (regArr[i].test(taxId)) {
} // return true;
return false; // }
// }
// return false;
} }
export function isAffiliationPrompted(application) { export function isAffiliationPrompted(application) {
@ -544,6 +569,14 @@ export function getAvatarColor(s) {
return colorList[hash % 4]; 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) { export function getLanguageText(text) {
if (!text.includes("|")) { if (!text.includes("|")) {
return text; return text;
@ -569,6 +602,11 @@ export function setLanguage(language) {
i18next.changeLanguage(language); i18next.changeLanguage(language);
} }
export function setTheme(themeKey) {
localStorage.setItem("theme", themeKey);
dispatchEvent(new Event("themeChange"));
}
export function getAcceptLanguage() { export function getAcceptLanguage() {
if (i18next.language === null || i18next.language === "") { if (i18next.language === null || i18next.language === "") {
return "en;q=0.9,en;q=0.8"; return "en;q=0.9,en;q=0.8";
@ -663,6 +701,7 @@ export function getProviderTypeOptions(category) {
{id: "Bilibili", name: "Bilibili"}, {id: "Bilibili", name: "Bilibili"},
{id: "Okta", name: "Okta"}, {id: "Okta", name: "Okta"},
{id: "Douyin", name: "Douyin"}, {id: "Douyin", name: "Douyin"},
{id: "Line", name: "Line"},
{id: "Custom", name: "Custom"}, {id: "Custom", name: "Custom"},
] ]
); );
@ -780,6 +819,9 @@ export function renderLoginLink(application, text) {
export function redirectToLoginPage(application, history) { export function redirectToLoginPage(application, history) {
const loginLink = getLoginLink(application); const loginLink = getLoginLink(application);
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
window.location.replace(loginLink);
}
history.push(loginLink); history.push(loginLink);
} }

View File

@ -28,11 +28,7 @@ import SamlWidget from "./common/SamlWidget";
import SelectRegionBox from "./SelectRegionBox"; import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable"; import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import ManagedAccountTable from "./ManagedAccountTable"; import ManagedAccountTable from "./ManagedAccountTable";
import PropertyTable from "./propertyTable";
import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const {Option} = Select; const {Option} = Select;
@ -490,13 +486,10 @@ class UserEditPage extends React.Component {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <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>
<Col span={22} > <Col span={22} >
<CodeMirror <PropertyTable properties={this.state.user.properties} onUpdateTable={(value) => {this.updateUserField("properties", value);}} />
value={JSON.stringify(this.state.user.properties, null, 4)}
options={{mode: "javascript", theme: "material-darker"}}
/>
</Col> </Col>
</Row> </Row>
); );

View File

@ -62,6 +62,7 @@ class UserListPage extends BaseListPage {
isAdmin: (owner === "built-in"), isAdmin: (owner === "built-in"),
isGlobalAdmin: (owner === "built-in"), isGlobalAdmin: (owner === "built-in"),
IsForbidden: false, IsForbidden: false,
score: this.state.organization.initScore,
isDeleted: false, isDeleted: false,
properties: {}, properties: {},
signupApplication: "app-built-in", signupApplication: "app-built-in",

View File

@ -178,7 +178,7 @@ class AuthCallback extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Spin} from "antd"; import {Card, Spin} from "antd";
import {withRouter} from "react-router-dom"; import {withRouter} from "react-router-dom";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
@ -39,7 +39,8 @@ class CasLogout extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", "Logged out successfully");
this.props.clearAccount(); this.props.onUpdateAccount(null);
this.onUpdateApplication(null);
const redirectUri = res.data2; const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
@ -49,6 +50,7 @@ class CasLogout extends React.Component {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`); Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
} }
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
} }
}); });
@ -57,11 +59,13 @@ class CasLogout extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <Card>
{ <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} /> {
} <Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
</div> }
</div>
</Card>
); );
} }
} }

View File

@ -34,12 +34,7 @@ class ForgetPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
account: props.account, account: props.account,
applicationName: applicationName: props.applicationName ?? props.match.params?.applicationName,
props.applicationName !== undefined
? props.applicationName
: props.match === undefined
? null
: props.match.params.applicationName,
application: null, application: null,
msg: null, msg: null,
userId: "", userId: "",
@ -57,34 +52,36 @@ class ForgetPage extends React.Component {
}; };
} }
UNSAFE_componentWillMount() { componentDidMount() {
if (this.state.applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(); if (this.state.applicationName !== undefined) {
} else { this.getApplication();
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type); } else {
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
}
} }
} }
getApplication() { getApplication() {
if (this.state.applicationName === null) { if (this.state.applicationName === undefined) {
return; return;
} }
ApplicationBackend.getApplication("admin", this.state.applicationName).then( ApplicationBackend.getApplication("admin", this.state.applicationName)
(application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
onFormFinish(name, info, forms) { onFormFinish(name, info, forms) {
@ -143,7 +140,7 @@ class ForgetPage extends React.Component {
username: this.state.username, username: this.state.username,
name: this.state.name, name: this.state.name,
code: forms.step2.getFieldValue("emailCode"), code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix, phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
type: "login", type: "login",
}, oAuthParams).then(res => { }, oAuthParams).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
@ -166,10 +163,10 @@ class ForgetPage extends React.Component {
onFinish(values) { onFinish(values) {
values.username = this.state.username; 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 => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.redirectToLoginPage(this.state.application, this.props.history); Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }
@ -355,13 +352,15 @@ class ForgetPage extends React.Component {
{this.state.verifyType === "email" ? ( {this.state.verifyType === "email" ? (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} 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} application={application}
/> />
) : ( ) : (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} 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} application={application}
/> />
)} )}
@ -490,7 +489,7 @@ class ForgetPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}> <div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
<Row> <Row>
@ -548,7 +547,7 @@ class ForgetPage extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

View 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;

View File

@ -29,6 +29,7 @@ import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput"; import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {CaptchaModal} from "../common/CaptchaModal"; import {CaptchaModal} from "../common/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
class LoginPage extends React.Component { class LoginPage extends React.Component {
constructor(props) { constructor(props) {
@ -49,6 +50,9 @@ class LoginPage extends React.Component {
enableCaptchaModal: false, enableCaptchaModal: false,
openCaptchaModal: false, openCaptchaModal: false,
verifyCaptcha: undefined, verifyCaptcha: undefined,
samlResponse: "",
relayState: "",
redirectUrl: "",
}; };
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) { if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@ -57,16 +61,22 @@ class LoginPage extends React.Component {
} }
} }
UNSAFE_componentWillMount() { componentDidMount() {
if (this.state.type === "login" || this.state.type === "cas") { if (this.getApplicationObj() === null) {
this.getApplication(); if (this.state.type === "login" || this.state.type === "cas") {
} else if (this.state.type === "code") { this.getApplication();
this.getApplicationLogin(); } else if (this.state.type === "code") {
} else if (this.state.type === "saml") { this.getApplicationLogin();
this.getSamlApplication(); } else if (this.state.type === "saml") {
} else { this.getSamlApplication();
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`); } 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) { componentDidUpdate(prevProps, prevState, snapshot) {
@ -86,11 +96,13 @@ class LoginPage extends React.Component {
AuthBackend.getApplicationLogin(oAuthParams) AuthBackend.getApplicationLogin(oAuthParams)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
}); });
} else { } else {
// Setting.showMessage("error", res.msg); // Setting.showMessage("error", res.msg);
this.onUpdateApplication(null);
this.setState({ this.setState({
application: res.data, application: res.data,
msg: res.msg, msg: res.msg,
@ -107,6 +119,7 @@ class LoginPage extends React.Component {
if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") { if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -115,11 +128,13 @@ class LoginPage extends React.Component {
OrganizationBackend.getDefaultApplication("admin", this.state.owner) OrganizationBackend.getDefaultApplication("admin", this.state.owner)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
applicationName: res.data.name, applicationName: res.data.name,
}); });
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}); });
@ -132,25 +147,25 @@ class LoginPage extends React.Component {
} }
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName) ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
onUpdateAccount(account) { onUpdateAccount(account) {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -178,10 +193,11 @@ class LoginPage extends React.Component {
if (values["samlRequest"] !== null && values["samlRequest"] !== "" && values["samlRequest"] !== undefined) { if (values["samlRequest"] !== null && values["samlRequest"] !== "" && values["samlRequest"] !== undefined) {
values["type"] = "saml"; values["type"] = "saml";
values["relayState"] = oAuthParams.relayState;
} }
if (this.state.application.organization !== null && this.state.application.organization !== undefined) { if (this.getApplicationObj()?.organization) {
values["organization"] = this.state.application.organization; values["organization"] = this.getApplicationObj().organization;
} }
} }
postCodeLoginAction(res) { postCodeLoginAction(res) {
@ -293,7 +309,7 @@ class LoginPage extends React.Component {
const responseType = values["type"]; const responseType = values["type"];
if (responseType === "login") { if (responseType === "login") {
Setting.showMessage("success", "Logged in successfully"); Setting.showMessage("success", i18next.t("application:Logged in successfully"));
const link = Setting.getFromLink(); const link = Setting.getFromLink();
Setting.goToLink(link); Setting.goToLink(link);
@ -306,7 +322,15 @@ class LoginPage extends React.Component {
} else if (responseType === "saml") { } else if (responseType === "saml") {
const SAMLResponse = res.data; const SAMLResponse = res.data;
const redirectUri = res.data2; 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 { } else {
this.setState({openCaptchaModal: false}); this.setState({openCaptchaModal: false});
@ -554,12 +578,12 @@ class LoginPage extends React.Component {
<span style={{float: "right"}}> <span style={{float: "right"}}>
{ {
!application.enableSignUp ? null : ( !application.enableSignUp ? null : (
<> <React.Fragment>
{i18next.t("login:No account?")}&nbsp; {i18next.t("login:No account?")}&nbsp;
{ {
Setting.renderSignupLink(application, i18next.t("login:sign up now")) Setting.renderSignupLink(application, i18next.t("login:sign up now"))
} }
</> </React.Fragment>
) )
} }
</span> </span>
@ -655,7 +679,7 @@ class LoginPage extends React.Component {
const rawId = assertion.rawId; const rawId = assertion.rawId;
const sig = assertion.response.signature; const sig = assertion.response.signature;
const userHandle = assertion.response.userHandle; 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", method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify({ body: JSON.stringify({
@ -719,6 +743,7 @@ class LoginPage extends React.Component {
> >
<CountDownInput <CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone} disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
method={"login"}
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
application={application} application={application}
/> />
@ -754,6 +779,10 @@ class LoginPage extends React.Component {
return Util.renderMessageLarge(this, this.state.msg); 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 !== "") { if (application.signinHtml !== "") {
return ( return (
<div dangerouslySetInnerHTML={{__html: application.signinHtml}} /> <div dangerouslySetInnerHTML={{__html: application.signinHtml}} />
@ -764,16 +793,16 @@ class LoginPage extends React.Component {
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) { if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup")); Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return ( 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%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
</div> </div>
); );
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <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}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel"> <div className="login-panel">
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
@ -803,7 +832,7 @@ class LoginPage extends React.Component {
</div> </div>
</div> </div>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; 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 ApplicationBackend from "../backend/ApplicationBackend";
import * as UserBackend from "../backend/UserBackend"; import * as UserBackend from "../backend/UserBackend";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
@ -30,7 +30,7 @@ class PromptPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
type: props.type, 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, application: null,
user: null, user: null,
}; };
@ -38,7 +38,9 @@ class PromptPage extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getUser(); this.getUser();
this.getApplication(); if (this.getApplicationObj() === null) {
this.getApplication();
}
} }
getUser() { getUser() {
@ -59,6 +61,7 @@ class PromptPage extends React.Component {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -66,11 +69,11 @@ class PromptPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
parseUserField(key, value) { parseUserField(key, value) {
@ -122,7 +125,7 @@ class PromptPage extends React.Component {
renderContent(application) { renderContent(application) {
return ( return (
<div style={{width: "400px"}}> <div style={{width: "500px"}}>
{ {
this.renderAffiliation(application) this.renderAffiliation(application)
} }
@ -247,9 +250,9 @@ class PromptPage extends React.Component {
} }
return ( return (
<Row> <div style={{display: "flex", flex: "1", justifyContent: "center"}}>
<Col span={24} style={{display: "flex", justifyContent: "center"}}> <Card>
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center"}}> <div style={{marginTop: "30px", marginBottom: "30px", textAlign: "center"}}>
{ {
Setting.renderHelmet(application) Setting.renderHelmet(application)
} }
@ -259,16 +262,12 @@ class PromptPage extends React.Component {
{ {
this.renderContent(application) this.renderContent(application)
} }
<Row style={{margin: 10}}>
<Col span={18}>
</Col>
</Row>
<div style={{marginTop: "50px"}}> <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> <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>
</div> </div>
</Col> </Card>
</Row> </div>
); );
} }
} }

View File

@ -121,6 +121,10 @@ const authInfo = {
Bilibili: { Bilibili: {
endpoint: "https://passport.bilibili.com/register/pc_oauth2.html", 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) { 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}`; return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
} else if (provider.type === "Bilibili") { } else if (provider.type === "Bilibili") {
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`; 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}`;
} }
} }

View File

@ -39,6 +39,7 @@ import SteamLoginButton from "./SteamLoginButton";
import BilibiliLoginButton from "./BilibiliLoginButton"; import BilibiliLoginButton from "./BilibiliLoginButton";
import OktaLoginButton from "./OktaLoginButton"; import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton"; import DouyinLoginButton from "./DouyinLoginButton";
import LineLoginButton from "./LineLoginButton";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import {getEvent} from "./Util"; import {getEvent} from "./Util";
import {Modal} from "antd"; import {Modal} from "antd";
@ -93,6 +94,8 @@ function getSigninButton(type) {
return <OktaLoginButton text={text} align={"center"} />; return <OktaLoginButton text={text} align={"center"} />;
} else if (type === "Douyin") { } else if (type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />; return <DouyinLoginButton text={text} align={"center"} />;
} else if (type === "Line") {
return <LineLoginButton text={text} align={"center"} />;
} }
return text; return text;

View File

@ -95,7 +95,7 @@ class SamlCallback extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -22,7 +22,6 @@ class SelfForgetPage extends React.Component {
<ForgetPage <ForgetPage
type={"forgotPassword"} type={"forgotPassword"}
applicationName={authConfig.appName} applicationName={authConfig.appName}
account={this.props.account}
{...this.props} {...this.props}
/> />
); );

View File

@ -19,7 +19,7 @@ import {authConfig} from "./Auth";
class SelfLoginPage extends React.Component { class SelfLoginPage extends React.Component {
render() { render() {
return ( 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} />
); );
} }
} }

View File

@ -65,7 +65,7 @@ class SignupPage extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
applicationName: props.match?.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName, applicationName: props.match.params?.applicationName ?? authConfig.appName,
application: null, application: null,
email: "", email: "",
phone: "", phone: "",
@ -91,10 +91,12 @@ class SignupPage extends React.Component {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
} }
if (applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(applicationName); if (applicationName !== undefined) {
} else { this.getApplication(applicationName);
Setting.showMessage("error", `Unknown application name: ${applicationName}`); } else {
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
}
} }
} }
@ -105,6 +107,7 @@ class SignupPage extends React.Component {
ApplicationBackend.getApplication("admin", applicationName) ApplicationBackend.getApplication("admin", applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -128,11 +131,7 @@ class SignupPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
getTermsofuseContent(url) { getTermsofuseContent(url) {
@ -149,6 +148,10 @@ class SignupPage extends React.Component {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -373,6 +376,7 @@ class SignupPage extends React.Component {
> >
<CountDownInput <CountDownInput
disabled={!this.state.validEmail} disabled={!this.state.validEmail}
method={"signup"}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(application)]}
application={application} application={application}
/> />
@ -426,6 +430,7 @@ class SignupPage extends React.Component {
> >
<CountDownInput <CountDownInput
disabled={!this.state.validPhone} disabled={!this.state.validPhone}
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application} application={application}
/> />
@ -630,9 +635,9 @@ class SignupPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <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}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel" > <div className="login-panel" >
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
@ -655,7 +660,7 @@ class SignupPage extends React.Component {
{ {
this.renderModal() this.renderModal()
} }
</div> </React.Fragment>
); );
} }
} }

View File

@ -109,11 +109,12 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
}).then(res => res.json()); }).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(); const formData = new FormData();
formData.append("checkType", checkType); formData.append("checkType", checkType);
formData.append("checkId", checkId); formData.append("checkId", checkId);
formData.append("checkKey", checkKey); formData.append("checkKey", checkKey);
formData.append("method", method);
formData.append("dest", dest); formData.append("dest", dest);
formData.append("type", type); formData.append("type", type);
formData.append("applicationId", applicationId); formData.append("applicationId", applicationId);

View File

@ -125,5 +125,5 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh
} }
}, [captchaType, subType, siteKey, clientSecret, clientId2, clientSecret2]); }, [captchaType, subType, siteKey, clientSecret, clientId2, clientSecret2]);
return <div id="captcha"></div>; return <div id="captcha" />;
}; };

View File

@ -22,7 +22,7 @@ import {CaptchaWidget} from "./CaptchaWidget";
const {Search} = Input; const {Search} = Input;
export const CountDownInput = (props) => { 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 [visible, setVisible] = React.useState(false);
const [key, setKey] = React.useState(""); const [key, setKey] = React.useState("");
const [captchaImg, setCaptchaImg] = React.useState(""); const [captchaImg, setCaptchaImg] = React.useState("");
@ -53,7 +53,7 @@ export const CountDownInput = (props) => {
const handleOk = () => { const handleOk = () => {
setVisible(false); setVisible(false);
setButtonLoading(true); setButtonLoading(true);
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => { UserBackend.sendCode(checkType, checkId, key, method, ...onButtonClickArgs).then(res => {
setKey(""); setKey("");
setButtonLoading(false); setButtonLoading(false);
if (res) { if (res) {
@ -70,7 +70,7 @@ export const CountDownInput = (props) => {
const loadCaptcha = () => { const loadCaptcha = () => {
UserBackend.getCaptcha(application.owner, application.name, false).then(res => { UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
if (res.type === "none") { if (res.type === "none") {
UserBackend.sendCode("none", "", "", ...onButtonClickArgs).then(res => { UserBackend.sendCode("none", "", "", method, ...onButtonClickArgs).then(res => {
if (res) { if (res) {
handleCountDown(60); handleCountDown(60);
} }

View File

@ -147,7 +147,7 @@ class OAuthWidget extends React.Component {
</Col> </Col>
<Col span={24 - this.props.labelSpan} > <Col span={24 - this.props.labelSpan} >
<img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" /> <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 === "" ? ( linkedValue === "" ? (
"(empty)" "(empty)"
@ -164,7 +164,7 @@ class OAuthWidget extends React.Component {
</span> </span>
{ {
linkedValue === "" ? ( 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> <Button style={{marginLeft: "20px", width: "80px"}} type="primary" disabled={user.id !== account.id}>{i18next.t("user:Link")}</Button>
</a> </a>
) : ( ) : (

View File

@ -91,7 +91,8 @@ class PolicyTable extends React.Component {
AdapterBackend.syncPolicies(this.props.owner, this.props.name) AdapterBackend.syncPolicies(this.props.owner, this.props.name)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({policyLists: res}); this.setState({policyLists: res.data});
Setting.showMessage("success", i18next.t("adapter:Sync policies successfully"));
} else { } else {
Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`);
} }
@ -292,14 +293,15 @@ class PolicyTable extends React.Component {
} }
render() { render() {
return (<> return (
<Button type="primary" onClick={() => {this.synPolicies();}}> <React.Fragment>
{i18next.t("adapter:Sync")} <Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}>
</Button> {i18next.t("adapter:Sync")}
{ </Button>
this.renderTable(this.state.policyLists) {
} this.renderTable(this.state.policyLists)
</> }
</React.Fragment>
); );
} }
} }

View 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;

View File

@ -28,7 +28,7 @@ code {
.logo { .logo {
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png"); background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
background-size: 130px, 27px; background-size: 130px, 27px !important;
width: 130px; width: 130px;
height: 27px; height: 27px;
margin: 17px 0 16px 15px; margin: 17px 0 16px 15px;
@ -45,7 +45,3 @@ code {
.ant-list-sm .ant-list-item { .ant-list-sm .ant-list-item {
padding: 2px !important; padding: 2px !important;
} }
.ant-layout-header {
background-color: white !important;
}

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "Always": "Always",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip", "Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip",
"Enable signup": "Anmeldung aktivieren", "Enable signup": "Anmeldung aktivieren",
"Enable signup - Tooltip": "Whether to allow users to sign up", "Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "Datei erfolgreich hochgeladen", "File uploaded successfully": "Datei erfolgreich hochgeladen",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Grant types", "Grant types": "Grant types",
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "New Application", "New Application": "New Application",
"None": "None", "None": "None",
"Password ON": "Passwort AN", "Password ON": "Passwort AN",
@ -54,12 +58,14 @@
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei", "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", "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": "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": "Umleitungs-URLs",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Aktualisierungs-Token läuft ab", "Refresh token expire": "Aktualisierungs-Token läuft ab",
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip", "Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule", "Rule": "Rule",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -150,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client-IP", "Client IP": "Client-IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Erstellte Zeit", "Created time": "Erstellte Zeit",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
@ -293,6 +302,7 @@
"Continue with": "Weiter mit", "Continue with": "Weiter mit",
"Email or phone": "E-Mail oder Telefon", "Email or phone": "E-Mail oder Telefon",
"Forgot password?": "Passwort vergessen?", "Forgot password?": "Passwort vergessen?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Kein Konto?", "No account?": "Kein Konto?",
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an", "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!": "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 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!", "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": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Mit {type} anmelden", "Sign in with {type}": "Mit {type} anmelden",
@ -326,6 +337,7 @@
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
"Edit Organization": "Organisation bearbeiten", "Edit Organization": "Organisation bearbeiten",
"Favicon": "Févicon", "Favicon": "Févicon",
"InitScore": "InitScore",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip", "Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "Website-URL", "Website URL": "Website-URL",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Edit Product", "Edit Product": "Edit Product",
"I have completed the payment": "I have completed the payment",
"Image": "Image", "Image": "Image",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "New Product", "New Product": "New Product",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...", "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": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Ist globaler Admin", "Is global admin": "Ist globaler Admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Standort", "Location": "Standort",
"Location - Tooltip": "Standort - Tooltip", "Location - Tooltip": "Standort - Tooltip",
@ -723,6 +742,7 @@
"Password Set": "Passwort setzen", "Password Set": "Passwort setzen",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Eigenschaften", "Properties": "Eigenschaften",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Neu erneut eingeben", "Re-enter New": "Neu erneut eingeben",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Telefon zurücksetzen...", "Reset Phone...": "Telefon zurücksetzen...",
@ -738,6 +758,7 @@
"Unlink": "Link aufheben", "Unlink": "Link aufheben",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Foto hochladen", "Upload a photo": "Foto hochladen",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "Passwort eingeben" "input password": "Passwort eingeben"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "Always": "Always",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Enable signup", "Enable signup": "Enable signup",
"Enable signup - Tooltip": "Enable signup - Tooltip", "Enable signup - Tooltip": "Enable signup - Tooltip",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully", "File uploaded successfully": "File uploaded successfully",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Grant types", "Grant types": "Grant types",
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "New Application", "New Application": "New Application",
"None": "None", "None": "None",
"Password ON": "Password ON", "Password ON": "Password ON",
@ -54,12 +58,14 @@
"Please select a HTML file": "Please select a HTML file", "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", "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": "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": "Redirect URLs",
"Redirect URLs - Tooltip": "Redirect URLs - Tooltip", "Redirect URLs - Tooltip": "Redirect URLs - Tooltip",
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule", "Rule": "Rule",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -150,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -293,6 +302,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another 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!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "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!", "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": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",
@ -326,6 +337,7 @@
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",
"Favicon": "Favicon", "Favicon": "Favicon",
"InitScore": "InitScore",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "Soft deletion - Tooltip", "Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Edit Product", "Edit Product": "Edit Product",
"I have completed the payment": "I have completed the payment",
"Image": "Image", "Image": "Image",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "New Product", "New Product": "New Product",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...", "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": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Is forbidden - Tooltip", "Is forbidden - Tooltip": "Is forbidden - Tooltip",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is global admin - Tooltip", "Is global admin - Tooltip": "Is global admin - Tooltip",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -723,6 +742,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -738,6 +758,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "Always": "Always",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "Activer la session de connexion - infobulle", "Enable signin session - Tooltip": "Activer la session de connexion - infobulle",
"Enable signup": "Activer l'inscription", "Enable signup": "Activer l'inscription",
"Enable signup - Tooltip": "Whether to allow users to sign up", "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", "File uploaded successfully": "Fichier téléchargé avec succès",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Grant types", "Grant types": "Grant types",
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "New Application", "New Application": "New Application",
"None": "None", "None": "None",
"Password ON": "Mot de passe activé", "Password ON": "Mot de passe activé",
@ -54,12 +58,14 @@
"Please select a HTML file": "Veuillez sélectionner un fichier HTML", "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", "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": "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": "URL de redirection",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Expiration du jeton d'actualisation", "Refresh token expire": "Expiration du jeton d'actualisation",
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle", "Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
"Right": "Right", "Right": "Right",
"Rule": "Rule", "Rule": "Rule",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -150,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "IP du client", "Client IP": "IP du client",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Date de création", "Created time": "Date de création",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
@ -293,6 +302,7 @@
"Continue with": "Continuer avec", "Continue with": "Continuer avec",
"Email or phone": "Courriel ou téléphone", "Email or phone": "Courriel ou téléphone",
"Forgot password?": "Mot de passe oublié ?", "Forgot password?": "Mot de passe oublié ?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Pas de compte ?", "No account?": "Pas de compte ?",
"Or sign in with another account": "Ou connectez-vous avec un autre 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!": "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 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!", "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": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Se connecter avec {type}", "Sign in with {type}": "Se connecter avec {type}",
@ -326,6 +337,7 @@
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
"Edit Organization": "Modifier l'organisation", "Edit Organization": "Modifier l'organisation",
"Favicon": "Favicon", "Favicon": "Favicon",
"InitScore": "InitScore",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "Suppression de soft - infobulle", "Soft deletion - Tooltip": "Suppression de soft - infobulle",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "URL du site web", "Website URL": "URL du site web",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Edit Product", "Edit Product": "Edit Product",
"I have completed the payment": "I have completed the payment",
"Image": "Image", "Image": "Image",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "New Product", "New Product": "New Product",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...", "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": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Est un administrateur global", "Is global admin": "Est un administrateur global",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Lier", "Link": "Lier",
"Location": "Localisation", "Location": "Localisation",
"Location - Tooltip": "Localisation - Infobulle", "Location - Tooltip": "Localisation - Infobulle",
@ -723,6 +742,7 @@
"Password Set": "Mot de passe défini", "Password Set": "Mot de passe défini",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Propriétés", "Properties": "Propriétés",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Nouvelle entrée", "Re-enter New": "Nouvelle entrée",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Réinitialiser le téléphone...", "Reset Phone...": "Réinitialiser le téléphone...",
@ -738,6 +758,7 @@
"Unlink": "Délier", "Unlink": "Délier",
"Upload (.xlsx)": "Télécharger (.xlsx)", "Upload (.xlsx)": "Télécharger (.xlsx)",
"Upload a photo": "Télécharger une photo", "Upload a photo": "Télécharger une photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "saisir le mot de passe" "input password": "saisir le mot de passe"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "Always": "Always",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "サインアップを有効にする", "Enable signup": "サインアップを有効にする",
"Enable signup - Tooltip": "Whether to allow users to sign up", "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": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Grant types", "Grant types": "Grant types",
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "New Application", "New Application": "New Application",
"None": "None", "None": "None",
"Password ON": "パスワードON", "Password ON": "パスワードON",
@ -54,12 +58,14 @@
"Please select a HTML file": "HTMLファイルを選択してください", "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", "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": "リダイレクトURL",
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
"Redirect URLs": "リダイレクトURL", "Redirect URLs": "リダイレクトURL",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "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", "Right": "Right",
"Rule": "Rule", "Rule": "Rule",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -150,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "クライアント IP", "Client IP": "クライアント IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "作成日時", "Created time": "作成日時",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
@ -293,6 +302,7 @@
"Continue with": "次で続ける", "Continue with": "次で続ける",
"Email or phone": "Eメールまたは電話番号", "Email or phone": "Eメールまたは電話番号",
"Forgot password?": "パスワードを忘れましたか?", "Forgot password?": "パスワードを忘れましたか?",
"Loading": "Loading",
"Logging out...": "Logging out...", "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!": "6文字以上でパスワードを入力してください", "Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください",
"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 WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "{type} でサインイン", "Sign in with {type}": "{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": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ", "Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Edit Product", "Edit Product": "Edit Product",
"I have completed the payment": "I have completed the payment",
"Image": "Image", "Image": "Image",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "New Product", "New Product": "New Product",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...", "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": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "グローバル管理者", "Is global admin": "グローバル管理者",
"Is global admin - Tooltip": "Is the application global administrator", "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", "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 Email...": "Reset Email...",
"Reset Phone...": "電話番号をリセット...", "Reset Phone...": "電話番号をリセット...",
@ -738,6 +758,7 @@
"Unlink": "リンクを解除", "Unlink": "リンクを解除",
"Upload (.xlsx)": "アップロード (.xlsx)", "Upload (.xlsx)": "アップロード (.xlsx)",
"Upload a photo": "写真をアップロード", "Upload a photo": "写真をアップロード",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "パスワードを入力" "input password": "パスワードを入力"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "Always": "Always",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
"Enable signup": "Enable signup", "Enable signup": "Enable signup",
"Enable signup - Tooltip": "Whether to allow users to sign up", "Enable signup - Tooltip": "Whether to allow users to sign up",
"Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully", "File uploaded successfully": "File uploaded successfully",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Grant types", "Grant types": "Grant types",
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "New Application", "New Application": "New Application",
"None": "None", "None": "None",
"Password ON": "Password ON", "Password ON": "Password ON",
@ -54,12 +58,14 @@
"Please select a HTML file": "Please select a HTML file", "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", "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": "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": "Redirect URLs",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "Redirect URLs - Tooltip": "List of redirect addresses after successful login",
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule", "Rule": "Rule",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -150,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -293,6 +302,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another 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!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "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!", "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": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",
@ -326,6 +337,7 @@
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",
"Favicon": "Favicon", "Favicon": "Favicon",
"InitScore": "InitScore",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "Soft deletion - Tooltip", "Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Detail", "Detail": "Detail",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Edit Product", "Edit Product": "Edit Product",
"I have completed the payment": "I have completed the payment",
"Image": "Image", "Image": "Image",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "New Product", "New Product": "New Product",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...", "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": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -723,6 +742,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -738,6 +758,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "Edit 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", "New Adapter": "New Adapter",
"Policies": "Policies", "Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip", "Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules", "Repeated policy rules": "Repeated policy rules",
"Sync": "Sync" "Sync": "Sync",
"Sync policies successfully": "Sync policies successfully"
}, },
"application": { "application": {
"Always": "Always", "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", "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": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
@ -45,6 +47,8 @@
"Grant types": "Виды грантов", "Grant types": "Виды грантов",
"Grant types - Tooltip": "Виды грантов - Подсказка", "Grant types - Tooltip": "Виды грантов - Подсказка",
"Left": "Left", "Left": "Left",
"Logged in successfully": "Logged in successfully",
"Logged out successfully": "Logged out successfully",
"New Application": "Новое приложение", "New Application": "Новое приложение",
"None": "None", "None": "None",
"Password ON": "Пароль ВКЛ", "Password ON": "Пароль ВКЛ",
@ -54,12 +58,14 @@
"Please select a HTML file": "Пожалуйста, выберите HTML-файл", "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": "URL перенаправления",
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip",
"Redirect URLs": "Перенаправление URL", "Redirect URLs": "Перенаправление URL",
"Redirect URLs - Tooltip": "List of redirect addresses after successful login", "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", "Right": "Right",
"Rule": "правило", "Rule": "правило",
"SAML Reply URL": "SAML Reply URL",
"SAML metadata": "Метаданные SAML", "SAML metadata": "Метаданные SAML",
"SAML metadata - Tooltip": "Метаданные SAML - Подсказка", "SAML metadata - Tooltip": "Метаданные SAML - Подсказка",
"SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена", "SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена",
@ -150,7 +156,10 @@
"Click to Upload": "Нажмите здесь, чтобы загрузить", "Click to Upload": "Нажмите здесь, чтобы загрузить",
"Client IP": "IP клиента", "Client IP": "IP клиента",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Время создания", "Created time": "Время создания",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "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!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!", "Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
"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 WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Войти с помощью {type}", "Sign in with {type}": "Войти с помощью {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": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule", "Modify rule": "Modify rule",
@ -334,6 +346,7 @@
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка", "Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"The user's initScore - Tooltip": "The user's initScore - Tooltip",
"View rule": "View rule", "View rule": "View rule",
"Visible": "Visible", "Visible": "Visible",
"Website URL": "URL сайта", "Website URL": "URL сайта",
@ -426,9 +439,12 @@
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Description": "Description",
"Description - Tooltip": "Description - Tooltip",
"Detail": "Сведения", "Detail": "Сведения",
"Detail - Tooltip": "Detail - Tooltip", "Detail - Tooltip": "Detail - Tooltip",
"Edit Product": "Редактирование продукта", "Edit Product": "Редактирование продукта",
"I have completed the payment": "I have completed the payment",
"Image": "Изображение", "Image": "Изображение",
"Image - Tooltip": "Image - Tooltip", "Image - Tooltip": "Image - Tooltip",
"New Product": "Новый продукт", "New Product": "Новый продукт",
@ -437,6 +453,8 @@
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "PayPal", "Paypal": "PayPal",
"Placing order...": "Placing order...", "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", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",
@ -708,6 +726,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Глобальный администратор", "Is global admin": "Глобальный администратор",
"Is global admin - Tooltip": "Is the application global administrator", "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", "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 Email...": "Reset Email...",
"Reset Phone...": "Сбросить телефон...", "Reset Phone...": "Сбросить телефон...",
@ -738,6 +758,7 @@
"Unlink": "Отвязать", "Unlink": "Отвязать",
"Upload (.xlsx)": "Загрузить (.xlsx)", "Upload (.xlsx)": "Загрузить (.xlsx)",
"Upload a photo": "Загрузить фото", "Upload a photo": "Загрузить фото",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "пароль для ввода" "input password": "пароль для ввода"
}, },

View File

@ -7,12 +7,13 @@
}, },
"adapter": { "adapter": {
"Edit Adapter": "编辑适配器", "Edit Adapter": "编辑适配器",
"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 policies successfully": "同步策略成功"
}, },
"application": { "application": {
"Always": "始终开启", "Always": "始终开启",
@ -36,6 +37,7 @@
"Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话", "Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话",
"Enable signup": "启用注册", "Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册", "Enable signup - Tooltip": "是否允许用户注册",
"Failed to sign in": "登录失败",
"File uploaded successfully": "文件上传成功", "File uploaded successfully": "文件上传成功",
"Form CSS": "表单CSS", "Form CSS": "表单CSS",
"Form CSS - Edit": "编辑表单CSS", "Form CSS - Edit": "编辑表单CSS",
@ -45,6 +47,8 @@
"Grant types": "OAuth授权类型", "Grant types": "OAuth授权类型",
"Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types", "Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types",
"Left": "居左", "Left": "居左",
"Logged in successfully": "登录成功",
"Logged out successfully": "登出成功",
"New Application": "添加应用", "New Application": "添加应用",
"None": "关闭", "None": "关闭",
"Password ON": "开启密码", "Password ON": "开启密码",
@ -54,12 +58,14 @@
"Please select a HTML file": "请选择一个HTML文件", "Please select a HTML file": "请选择一个HTML文件",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "提醒页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问", "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "提醒页面URL已成功复制到剪贴板请粘贴到当前浏览器的隐身模式窗口或另一个浏览器访问",
"Redirect URL": "重定向 URL", "Redirect URL": "重定向 URL",
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "回复 URL (断言使用者服务 URL, 使用POST请求返回响应) - Tooltip",
"Redirect URLs": "重定向 URLs", "Redirect URLs": "重定向 URLs",
"Redirect URLs - Tooltip": "登录成功后重定向地址列表", "Redirect URLs - Tooltip": "登录成功后重定向地址列表",
"Refresh token expire": "Refresh Token过期", "Refresh token expire": "Refresh Token过期",
"Refresh token expire - Tooltip": "Refresh Token过期时间", "Refresh token expire - Tooltip": "Refresh Token过期时间",
"Right": "居右", "Right": "居右",
"Rule": "规则", "Rule": "规则",
"SAML Reply URL": "SAML回复 URL",
"SAML metadata": "SAML元数据", "SAML metadata": "SAML元数据",
"SAML metadata - Tooltip": "SAML协议的元数据Metadata信息", "SAML metadata - Tooltip": "SAML协议的元数据Metadata信息",
"SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板", "SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
@ -150,7 +156,10 @@
"Click to Upload": "点击上传", "Click to Upload": "点击上传",
"Client IP": "客户端IP", "Client IP": "客户端IP",
"Close": "关闭", "Close": "关闭",
"Compact": "紧凑",
"Created time": "创建时间", "Created time": "创建时间",
"Dark": "黑暗",
"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或手机号", "Email or phone": "Email或手机号",
"Forgot password?": "忘记密码?", "Forgot password?": "忘记密码?",
"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!": "请输入您的密码不少于6位", "Please input your password, at least 6 characters!": "请输入您的密码不少于6位",
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号", "Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Redirecting, please wait.": "正在跳转, 请稍等.",
"Sign In": "登录", "Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录", "Sign in with WebAuthn": "WebAuthn登录",
"Sign in with {type}": "{type}登录", "Sign in with {type}": "{type}登录",
@ -326,6 +337,7 @@
"Default avatar": "默认头像", "Default avatar": "默认头像",
"Edit Organization": "编辑组织", "Edit Organization": "编辑组织",
"Favicon": "图标", "Favicon": "图标",
"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": "用户的初始积分",
"View rule": "查看规则", "View rule": "查看规则",
"Visible": "是否可见", "Visible": "是否可见",
"Website URL": "网页地址", "Website URL": "网页地址",
@ -426,9 +439,12 @@
"CNY": "人民币", "CNY": "人民币",
"Currency": "币种", "Currency": "币种",
"Currency - Tooltip": "币种 - 工具提示", "Currency - Tooltip": "币种 - 工具提示",
"Description": "描述",
"Description - Tooltip": "描述 - 工具提示",
"Detail": "详情", "Detail": "详情",
"Detail - Tooltip": "详情 - 工具提示", "Detail - Tooltip": "详情 - 工具提示",
"Edit Product": "编辑商品", "Edit Product": "编辑商品",
"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贝宝", "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": "请扫描二维码支付",
"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": "键",
"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": "属性",
"Re-enter New": "重复新密码", "Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...", "Reset Email...": "重置邮箱...",
"Reset Phone...": "重置手机号...", "Reset Phone...": "重置手机号...",
@ -738,6 +758,7 @@
"Unlink": "解绑", "Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx", "Upload (.xlsx)": "上传(.xlsx",
"Upload a photo": "上传头像", "Upload a photo": "上传头像",
"Values": "值",
"WebAuthn credentials": "WebAuthn凭据", "WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码" "input password": "输入密码"
}, },

131
web/src/propertyTable.js Normal file
View 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;

View File

@ -2096,6 +2096,13 @@
schema-utils "^3.0.0" schema-utils "^3.0.0"
source-map "^0.7.3" 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": "@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" version "1.0.3"
resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.0.3.tgz#3aa2c229a7a20ac2412d864e8977e6377973416e" 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" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
antd@5.0.3: antd@5.0.5:
version "5.0.3" version "5.0.5"
resolved "https://registry.yarnpkg.com/antd/-/antd-5.0.3.tgz#2cdaffe1afdb5c2f0f3325b2d06d508ec6ddf85a" resolved "https://registry.yarnpkg.com/antd/-/antd-5.0.5.tgz#16f0ade8b2d2ea9f7bd47a8e0af81884dc504a7d"
integrity sha512-Gqkba0earlR5H6gfT4nsyV3W9rL1Up1+clEXsa1+9Jem/geC2phBImpjWgVjqOjH3L5Oi8SHe0NeYBagDxwP5g== integrity sha512-8jWUjZ65urNHZPg9/Ywa9V0PlNfqjhewKgSPF4nraN9X5v434lDJkRBQGN7meNixQ6aM2B/JhXPm9UaJ/tAQmA==
dependencies: dependencies:
"@ant-design/colors" "^6.0.0" "@ant-design/colors" "^6.0.0"
"@ant-design/cssinjs" "^1.0.0" "@ant-design/cssinjs" "^1.0.0"
@ -3159,7 +3166,7 @@ antd@5.0.3:
rc-field-form "~1.27.0" rc-field-form "~1.27.0"
rc-image "~5.12.0" rc-image "~5.12.0"
rc-input "~0.1.4" rc-input "~0.1.4"
rc-input-number "~7.3.9" rc-input-number "~7.4.0"
rc-mentions "~1.13.1" rc-mentions "~1.13.1"
rc-menu "~9.8.0" rc-menu "~9.8.0"
rc-motion "^2.6.1" rc-motion "^2.6.1"
@ -3175,15 +3182,15 @@ antd@5.0.3:
rc-steps "~6.0.0-alpha.2" rc-steps "~6.0.0-alpha.2"
rc-switch "~4.0.0" rc-switch "~4.0.0"
rc-table "~7.26.0" rc-table "~7.26.0"
rc-tabs "~12.4.1" rc-tabs "~12.4.2"
rc-textarea "~0.4.5" rc-textarea "~0.4.5"
rc-tooltip "~5.2.0" rc-tooltip "~5.2.0"
rc-tree "~5.7.0" rc-tree "~5.7.0"
rc-tree-select "~5.5.4" rc-tree-select "~5.5.4"
rc-trigger "^5.2.10" rc-trigger "^5.2.10"
rc-upload "~4.3.0" rc-upload "~4.3.0"
rc-util "^5.22.5" rc-util "^5.25.2"
scroll-into-view-if-needed "^2.2.25" scroll-into-view-if-needed "^3.0.3"
shallowequal "^1.1.0" shallowequal "^1.1.0"
anymatch@^3.0.3, anymatch@~3.1.2: anymatch@^3.0.3, anymatch@~3.1.2:
@ -4011,10 +4018,10 @@ compression@^1.7.4:
safe-buffer "5.1.2" safe-buffer "5.1.2"
vary "~1.1.2" vary "~1.1.2"
compute-scroll-into-view@^1.0.17: compute-scroll-into-view@^2.0.2:
version "1.0.17" version "2.0.2"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-2.0.2.tgz#ac5cc71ca833884866e581a82d8558a6ed7ee877"
integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== integrity sha512-W+4Iti92hktsTtNPNeRM1vE0JdqCBk5qIabRafpr5pGrQhQ+xzCv0NGnFzTCKmW4yGLm9Aovbw8YNxloe2z9tQ==
concat-map@0.0.1: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
@ -8934,12 +8941,13 @@ rc-image@~5.12.0:
rc-dialog "~9.0.0" rc-dialog "~9.0.0"
rc-util "^5.0.6" rc-util "^5.0.6"
rc-input-number@~7.3.9: rc-input-number@~7.4.0:
version "7.3.11" version "7.4.0"
resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.3.11.tgz#c7089705a220e1a59ba974fabf89693e00dd2442" resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.4.0.tgz#b8b4ffa8bbc04198e79ce8b9611756d046d128ec"
integrity sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA== integrity sha512-r/Oub/sPYbzqLNUOHnnc9sbCu78a81KX+RCbRwmpvB4W6nptUySbdWS5KHV4Hak5CAE1LAd+wWm5JjvZizG1FA==
dependencies: dependencies:
"@babel/runtime" "^7.10.1" "@babel/runtime" "^7.10.1"
"@rc-component/mini-decimal" "^1.0.1"
classnames "^2.2.5" classnames "^2.2.5"
rc-util "^5.23.0" rc-util "^5.23.0"
@ -9148,10 +9156,10 @@ rc-table@~7.26.0:
rc-util "^5.22.5" rc-util "^5.22.5"
shallowequal "^1.1.0" shallowequal "^1.1.0"
rc-tabs@~12.4.1: rc-tabs@~12.4.2:
version "12.4.1" version "12.4.2"
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.4.1.tgz#a45aa7560ae4e2a91426e74a2e76566f5c8ec9cc" resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.4.2.tgz#487a1b3f8d8cf0bfc121224013dab00d4a8e0532"
integrity sha512-yViBZypldDnPffk3IPTarplF1RAv8VQDDnOt9sHDU7pjCnqE72csCU+7kjbLPtPpYniIMQJYyWxh/lsBUcagSA== integrity sha512-FFlGwuTjQUznWzJtyhmHc6KAp5lRQFxKUv9Aj1UtsOYe2e7WGmuzcrd+/LQchuPe0VjhaZPdGkmFGcqGqNO6ow==
dependencies: dependencies:
"@babel/runtime" "^7.11.2" "@babel/runtime" "^7.11.2"
classnames "2.x" classnames "2.x"
@ -9269,6 +9277,15 @@ rc-util@^5.21.2, rc-util@^5.23.0:
react-is "^16.12.0" react-is "^16.12.0"
shallowequal "^1.1.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: rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8:
version "3.4.8" version "3.4.8"
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.8.tgz#c24c10c6940546b7e2a5e9809402c6716adfd26c" 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-formats "^2.1.1"
ajv-keywords "^5.0.0" ajv-keywords "^5.0.0"
scroll-into-view-if-needed@^2.2.25: scroll-into-view-if-needed@^3.0.3:
version "2.2.29" version "3.0.3"
resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz#551791a84b7e2287706511f8c68161e4990ab885" resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.3.tgz#57256bef78f3c3c288070d2aaa63cf547aa11e70"
integrity sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg== integrity sha512-QoCH0lVw0tbA7Rl6sToH7e1tO3n95Oi6JgBgC8hEpNNZUC91MfasJ/4E1ZdbzGueNDZ+Y7ObfRaelKUgTyPbJA==
dependencies: dependencies:
compute-scroll-into-view "^1.0.17" compute-scroll-into-view "^2.0.2"
select-hose@^2.0.0: select-hose@^2.0.0:
version "2.0.0" version "2.0.0"