Compare commits

..

15 Commits

Author SHA1 Message Date
Yaodong Yu
b34e16b145 fix: table do not have unique key (#1512) 2023-02-02 13:53:18 +08:00
Gucheng Wang
11b56c340f Add refineUser() in generateJwtToken() 2023-02-02 00:34:56 +08:00
Yaodong Yu
cc6ea1b60e feat: fix application edit page crash and language icon position (#1511)
* fix: widget position and color

* feat: fix applicationEdit crush
2023-02-01 23:11:48 +08:00
Yaodong Yu
95b32d5ebf feat: support customize theme (#1500)
* refactor: simplify functions and improve variable naming

* feat: add themeEditor component

* feat: support customize theme

* chore: resolve conflict and add LICENCE

* chore: format code

* refactor: use icon replace background url

* feat: improve organization and application theme editor
2023-02-01 22:06:40 +08:00
imp2002
b47baa06e1 fix: remove "Agreement" in edit application error (#1506) 2023-01-31 22:56:19 +08:00
wht
24a824d394 feat: return the correct error message in the Edit Model (#1504) 2023-01-30 22:19:42 +08:00
Gucheng Wang
75b8357de8 Add properties to UserWithoutThirdIdp 2023-01-29 21:51:01 +08:00
Gucheng Wang
087405dad2 Fix isAllowedInDemoMode() 2023-01-26 17:56:29 +08:00
1307
6a6a1fa920 feat: fix missing phone number prefix in login screen (#1492)
fix: #1489
2023-01-24 23:19:44 +08:00
Gucheng Wang
907d18d2e9 Fix missing roles and permissions in user table 2023-01-23 00:36:55 +08:00
Zayn Xie
a728e083eb feat: reduce the size of token's user object (#1487)
* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-01-21 09:30:23 +08:00
Chell
457e6208ad feat: terms of use auto selected (#1485) 2023-01-19 20:31:21 +08:00
Chell
d10b1347a8 feat: add terms of use in signin page (#1476)
* feat: extract terms of use renderer

* fix: layout

* fix: form styling

* fix: required state

* feat: application terms of use setting

* fix: refactor getTermsOfUseContent

* fix: refactor renderers
2023-01-19 18:39:24 +08:00
qwqcode
f5b7f8cb45 chore(frontend): remove import of the third-party js script (#1436)
Signed-off-by: qwqcode <qwqcode@gmail.com>

Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-01-19 11:31:27 +08:00
Yaodong Yu
5d9b17542f feat: end-user log out (#1356) 2023-01-17 22:57:05 +08:00
60 changed files with 14225 additions and 12439 deletions

View File

@@ -160,7 +160,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool { func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if method == "POST" { if method == "POST" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" { if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" {
return true return true
} else if urlPath == "/api/update-user" { } else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information // Allow ordinary users to update their own information

View File

@@ -17,6 +17,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"strings" "strings"
@@ -238,21 +239,67 @@ func (c *ApiController) Signup() {
// @Title Logout // @Title Logout
// @Tag Login API // @Tag Login API
// @Description logout the current user // @Description logout the current user
// @Param id_token_hint query string false "id_token_hint"
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
// @Param state query string false "state"
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /logout [get,post] // @router /logout [get,post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication() // https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
c.ClearUserSession() accessToken := c.Input().Get("id_token_hint")
redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state")
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" { if accessToken == "" && redirectUri == "" {
c.ResponseOk(user) c.ClearUserSession()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
}
c.ResponseOk(user, application.HomepageUrl)
return return
} else {
if redirectUri == "" {
c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
return
}
if accessToken == "" {
c.ResponseError(c.T("general:Missing parameter") + ": id_token_hint")
return
}
affected, application, token := object.ExpireTokenByAccessToken(accessToken)
if !affected {
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
return
}
if application.IsRedirectUriValid(redirectUri) {
if user == "" {
user = util.GetId(token.Organization, token.User)
}
c.ClearUserSession()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return
}
} }
c.ResponseOk(user, application.HomepageUrl)
} }
// GetAccount // GetAccount

View File

@@ -113,7 +113,7 @@ func (c *ApiController) GetOrganizationApplications() {
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
if organization == "" { if organization == "" {
c.ResponseError(c.T("application:Parameter organization is missing")) c.ResponseError(c.T("general:Missing parameter") + ": organization")
return return
} }

View File

@@ -52,7 +52,7 @@ func (c *ApiController) GetLdapUser() {
ldapServer := LdapServer{} ldapServer := LdapServer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer) err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) { if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
c.ResponseError(c.T("ldap:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@@ -120,7 +120,7 @@ func (c *ApiController) GetLdap() {
id := c.Input().Get("id") id := c.Input().Get("id")
if util.IsStrsEmpty(id) { if util.IsStrsEmpty(id) {
c.ResponseError(c.T("ldap:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@@ -136,12 +136,12 @@ func (c *ApiController) AddLdap() {
var ldap object.Ldap var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap) err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil { if err != nil {
c.ResponseError(c.T("ldap:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) { if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
c.ResponseError(c.T("ldap:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@@ -171,7 +171,7 @@ func (c *ApiController) UpdateLdap() {
var ldap object.Ldap var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap) err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) { if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
c.ResponseError(c.T("ldap:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }

View File

@@ -80,7 +80,7 @@ func (c *ApiController) UpdateModel() {
return return
} }
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model)) c.Data["json"] = wrapErrorResponse(object.UpdateModelWithCheck(id, &model))
c.ServeJSON() c.ServeJSON()
} }

View File

@@ -16,7 +16,6 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"net/http"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@@ -247,28 +246,6 @@ func (c *ApiController) RefreshToken() {
c.ServeJSON() c.ServeJSON()
} }
// TokenLogout
// @Title TokenLogout
// @Tag Token API
// @Description delete token by AccessToken
// @Param id_token_hint query string true "id_token_hint"
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
// @Param state query string true "state"
// @Success 200 {object} controllers.Response The Response object
// @router /login/oauth/logout [get]
func (c *ApiController) TokenLogout() {
token := c.Input().Get("id_token_hint")
flag, application := object.DeleteTokenByAccessToken(token)
redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state")
if application != nil && application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
return
}
c.Data["json"] = wrapActionResponse(flag)
c.ServeJSON()
}
// IntrospectToken // IntrospectToken
// @Title IntrospectToken // @Title IntrospectToken
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a // @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a

View File

@@ -51,15 +51,15 @@ func (c *ApiController) SendVerificationCode() {
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" { if destType == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": type.") c.ResponseError(c.T("general:Missing parameter") + ": type.")
return return
} }
if dest == "" { if dest == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": dest.") c.ResponseError(c.T("general:Missing parameter") + ": dest.")
return return
} }
if applicationId == "" { if applicationId == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": applicationId.") c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
return return
} }
if !strings.Contains(applicationId, "/") { if !strings.Contains(applicationId, "/") {
@@ -67,7 +67,7 @@ func (c *ApiController) SendVerificationCode() {
return return
} }
if checkType == "" { if checkType == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": checkType.") c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
return return
} }
@@ -75,7 +75,7 @@ func (c *ApiController) SendVerificationCode() {
if captchaProvider != nil { if captchaProvider != nil {
if checkKey == "" { if checkKey == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": checkKey.") c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
return return
} }
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId) isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
@@ -170,7 +170,7 @@ func (c *ApiController) ResetEmailOrPhone() {
dest := c.Ctx.Request.Form.Get("dest") dest := c.Ctx.Request.Form.Get("dest")
code := c.Ctx.Request.Form.Get("code") code := c.Ctx.Request.Form.Get("code")
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 { if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
c.ResponseError(c.T("verification:Missing parameter")) c.ResponseError(c.T("general:Missing parameter"))
return return
} }
@@ -247,11 +247,11 @@ func (c *ApiController) VerifyCaptcha() {
captchaToken := c.Ctx.Request.Form.Get("captchaToken") captchaToken := c.Ctx.Request.Form.Get("captchaToken")
clientSecret := c.Ctx.Request.Form.Get("clientSecret") clientSecret := c.Ctx.Request.Form.Get("clientSecret")
if captchaToken == "" { if captchaToken == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": captchaToken.") c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
return return
} }
if clientSecret == "" { if clientSecret == "" {
c.ResponseError(c.T("verification:Missing parameter") + ": clientSecret.") c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.")
return return
} }

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "Please sign out first before signing up", "Please sign out first before signing up": "Please sign out first before signing up",
"The application does not allow to sign up new account": "The application does not allow to sign up new account" "The application does not allow to sign up new account": "The application does not allow to sign up new account"
}, },
"application": {
"Parameter organization is missing": "Parameter organization is missing"
},
"auth": { "auth": {
"%s No phone prefix": "%s No phone prefix", "%s No phone prefix": "%s No phone prefix",
"Challenge method should be S256": "Challenge method should be S256", "Challenge method should be S256": "Challenge method should be S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "unsupported password type: %s" "unsupported password type: %s": "unsupported password type: %s"
}, },
"general": { "general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first", "Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist" "The user: %s doesn't exist": "The user: %s doesn't exist"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Ldap server exist", "Ldap server exist": "Ldap server exist"
"Missing parameter": "Missing parameter"
}, },
"link": { "link": {
"Please link first": "Please link first", "Please link first": "Please link first",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application", "Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret", "Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id", "Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
}, },
"user": { "user": {
"Display name cannot be empty": "Display name cannot be empty", "Display name cannot be empty": "Display name cannot be empty",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "Code has not been sent yet!", "Code has not been sent yet!": "Code has not been sent yet!",
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Invalid captcha provider.": "Invalid captcha provider.", "Invalid captcha provider.": "Invalid captcha provider.",
"Missing parameter": "Missing parameter",
"Organization does not exist": "Organization does not exist", "Organization does not exist": "Organization does not exist",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",
"Turing test failed.": "Turing test failed.", "Turing test failed.": "Turing test failed.",

View File

@@ -8,9 +8,6 @@
"Please sign out first before signing up": "请在注册前先退出登录", "Please sign out first before signing up": "请在注册前先退出登录",
"The application does not allow to sign up new account": "该应用不允许注册新用户" "The application does not allow to sign up new account": "该应用不允许注册新用户"
}, },
"application": {
"Parameter organization is missing": "缺少organization参数"
},
"auth": { "auth": {
"%s No phone prefix": "%s 无此手机号前缀", "%s No phone prefix": "%s 无此手机号前缀",
"Challenge method should be S256": "Challenge 方法应该为 S256", "Challenge method should be S256": "Challenge 方法应该为 S256",
@@ -64,12 +61,12 @@
"unsupported password type: %s": "不支持的密码类型: %s" "unsupported password type: %s": "不支持的密码类型: %s"
}, },
"general": { "general": {
"Missing parameter": "缺少参数",
"Please login first": "请先登录", "Please login first": "请先登录",
"The user: %s doesn't exist": "用户: %s 不存在" "The user: %s doesn't exist": "用户: %s 不存在"
}, },
"ldap": { "ldap": {
"Ldap server exist": "LDAP服务器已存在", "Ldap server exist": "LDAP服务器已存在"
"Missing parameter": "LDAP缺少参数"
}, },
"link": { "link": {
"Please link first": "请先绑定", "Please link first": "请先绑定",
@@ -112,7 +109,8 @@
"Grant_type: %s is not supported in this application": "该应用不支持Grant_type: %s", "Grant_type: %s is not supported in this application": "该应用不支持Grant_type: %s",
"Invalid application or wrong clientSecret": "无效应用或错误的clientSecret", "Invalid application or wrong clientSecret": "无效应用或错误的clientSecret",
"Invalid client_id": "无效的ClientId", "Invalid client_id": "无效的ClientId",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "重定向 URI%s 在许可跳转列表中未找到" "Redirect URI: %s doesn't exist in the allowed Redirect URI list": "重定向 URI%s 在许可跳转列表中未找到",
"Token not found, invalid accessToken": "未查询到对应token, accessToken无效"
}, },
"user": { "user": {
"Display name cannot be empty": "显示名称不可为空", "Display name cannot be empty": "显示名称不可为空",
@@ -131,7 +129,6 @@
"Code has not been sent yet!": "验证码还未发送", "Code has not been sent yet!": "验证码还未发送",
"Email is invalid": "非法的邮箱", "Email is invalid": "非法的邮箱",
"Invalid captcha provider.": "非法的验证码提供商", "Invalid captcha provider.": "非法的验证码提供商",
"Missing parameter": "缺少参数",
"Organization does not exist": "组织不存在", "Organization does not exist": "组织不存在",
"Phone number is invalid": "非法的手机号码", "Phone number is invalid": "非法的手机号码",
"Turing test failed.": "验证码还未发送", "Turing test failed.": "验证码还未发送",

View File

@@ -57,23 +57,24 @@ type Application struct {
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"` RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"` TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
ExpireInHours int `json:"expireInHours"` ExpireInHours int `json:"expireInHours"`
RefreshExpireInHours int `json:"refreshExpireInHours"` RefreshExpireInHours int `json:"refreshExpireInHours"`
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"` SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"` SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"` ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"` AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"` TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
SignupHtml string `xorm:"mediumtext" json:"signupHtml"` SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
SigninHtml string `xorm:"mediumtext" json:"signinHtml"` SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
FormCss string `xorm:"text" json:"formCss"` ThemeData *ThemeData `xorm:"json" json:"themeData"`
FormOffset int `json:"formOffset"` FormCss string `xorm:"text" json:"formCss"`
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"` FormOffset int `json:"formOffset"`
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"` FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
} }
func GetApplicationCount(owner, field, value string) int { func GetApplicationCount(owner, field, value string) int {

View File

@@ -86,6 +86,17 @@ func GetModel(id string) *Model {
return getModel(owner, name) return getModel(owner, name)
} }
func UpdateModelWithCheck(id string, modelObj *Model) error {
// check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
return err
}
UpdateModel(id, modelObj)
return nil
}
func UpdateModel(id string, modelObj *Model) bool { func UpdateModel(id string, modelObj *Model) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getModel(owner, name) == nil { if getModel(owner, name) == nil {
@@ -98,11 +109,6 @@ func UpdateModel(id string, modelObj *Model) bool {
return false return false
} }
} }
// check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
panic(err)
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj) affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj)
if err != nil { if err != nil {

View File

@@ -40,6 +40,7 @@ type OidcDiscovery struct {
ClaimsSupported []string `json:"claims_supported"` ClaimsSupported []string `json:"claims_supported"`
RequestParameterSupported bool `json:"request_parameter_supported"` RequestParameterSupported bool `json:"request_parameter_supported"`
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"` RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
EndSessionEndpoint string `json:"end_session_endpoint"`
} }
func getOriginFromHost(host string) (string, string) { func getOriginFromHost(host string) (string, string) {
@@ -84,6 +85,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"}, ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"},
RequestParameterSupported: true, RequestParameterSupported: true,
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"}, RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
EndSessionEndpoint: fmt.Sprintf("%s/api/logout", originBackend),
} }
return oidcDiscovery return oidcDiscovery

View File

@@ -31,25 +31,34 @@ type AccountItem struct {
ModifyRule string `json:"modifyRule"` ModifyRule string `json:"modifyRule"`
} }
type ThemeData struct {
ThemeType string `xorm:"varchar(30)" json:"themeType"`
ColorPrimary string `xorm:"varchar(10)" json:"colorPrimary"`
BorderRadius int `xorm:"int" json:"borderRadius"`
IsCompact bool `xorm:"bool" json:"isCompact"`
IsEnabled bool `xorm:"bool" json:"isEnabled"`
}
type Organization struct { type Organization struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
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"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"` WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"` Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
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"` ThemeData *ThemeData `xorm:"json" json:"themeData"`
InitScore int `json:"initScore"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` InitScore int `json:"initScore"`
IsProfilePublic bool `json:"isProfilePublic"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"` AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
} }

View File

@@ -15,8 +15,6 @@
package object package object
import ( import (
"time"
"github.com/beego/beego" "github.com/beego/beego"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@@ -42,7 +40,7 @@ func SetSession(id string, sessionId string) {
if get { if get {
_, err = adapter.Engine.ID(core.PK{owner, name}).Update(session) _, err = adapter.Engine.ID(core.PK{owner, name}).Update(session)
} else { } else {
session.CreatedTime = time.Now().Format(time.RFC3339) session.CreatedTime = util.GetCurrentTime()
_, err = adapter.Engine.Insert(session) _, err = adapter.Engine.Insert(session)
} }
if err != nil { if err != nil {
@@ -66,7 +64,7 @@ func DeleteSession(id string) bool {
} }
func DeleteSessionId(id string, sessionId string) bool { func DeleteSessionId(id string, sessionId string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id) owner, name := util.GetOwnerAndNameFromId(id)
session := &Session{Owner: owner, Name: name} session := &Session{Owner: owner, Name: name}
_, err := adapter.Engine.ID(core.PK{owner, name}).Get(session) _, err := adapter.Engine.ID(core.PK{owner, name}).Get(session)

View File

@@ -27,7 +27,7 @@ import (
) )
const ( const (
hourSeconds = 3600 hourMinutes = 60
InvalidRequest = "invalid_request" InvalidRequest = "invalid_request"
InvalidClient = "invalid_client" InvalidClient = "invalid_client"
InvalidGrant = "invalid_grant" InvalidGrant = "invalid_grant"
@@ -204,7 +204,7 @@ func DeleteToken(token *Token) bool {
return affected != 0 return affected != 0
} }
func DeleteTokenByAccessToken(accessToken string) (bool, *Application) { func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token) {
token := Token{AccessToken: accessToken} token := Token{AccessToken: accessToken}
existed, err := adapter.Engine.Get(&token) existed, err := adapter.Engine.Get(&token)
if err != nil { if err != nil {
@@ -212,15 +212,17 @@ func DeleteTokenByAccessToken(accessToken string) (bool, *Application) {
} }
if !existed { if !existed {
return false, nil return false, nil, nil
} }
application := getApplication(token.Owner, token.Application)
affected, err := adapter.Engine.Where("access_token=?", accessToken).Delete(&Token{}) token.ExpiresIn = 0
affected, err := adapter.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(&token)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return affected != 0, application application := getApplication(token.Owner, token.Application)
return affected != 0, application, &token
} }
func GetTokenByAccessToken(accessToken string) *Token { func GetTokenByAccessToken(accessToken string) *Token {
@@ -304,7 +306,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds, ExpiresIn: application.ExpireInHours * hourMinutes,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeChallenge: challenge, CodeChallenge: challenge,
@@ -438,7 +440,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: newAccessToken, AccessToken: newAccessToken,
RefreshToken: newRefreshToken, RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds, ExpiresIn: application.ExpireInHours * hourMinutes,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
} }
@@ -588,7 +590,7 @@ func GetPasswordToken(application *Application, username string, password string
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds, ExpiresIn: application.ExpireInHours * hourMinutes,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@@ -628,7 +630,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
User: nullUser.Name, User: nullUser.Name,
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * hourSeconds, ExpiresIn: application.ExpireInHours * hourMinutes,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@@ -655,7 +657,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds, ExpiresIn: application.ExpireInHours * hourMinutes,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,

View File

@@ -36,6 +36,60 @@ type UserShort struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
} }
type UserWithoutThirdIdp struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100) index" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
FirstName string `xorm:"varchar(100)" json:"firstName"`
LastName string `xorm:"varchar(100)" json:"lastName"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
Title string `xorm:"varchar(100)" json:"title"`
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
IdCard string `xorm:"varchar(100) index" json:"idCard"`
Homepage string `xorm:"varchar(100)" json:"homepage"`
Bio string `xorm:"varchar(100)" json:"bio"`
Tag string `xorm:"varchar(100)" json:"tag"`
Region string `xorm:"varchar(100)" json:"region"`
Language string `xorm:"varchar(100)" json:"language"`
Gender string `xorm:"varchar(100)" json:"gender"`
Birthday string `xorm:"varchar(100)" json:"birthday"`
Education string `xorm:"varchar(100)" json:"education"`
Score int `json:"score"`
Karma int `json:"karma"`
Ranking int `json:"ranking"`
IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `xorm:"-" json:"roles"`
Permissions []*Permission `xorm:"-" json:"permissions"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
}
type ClaimsShort struct { type ClaimsShort struct {
*UserShort *UserShort
TokenType string `json:"tokenType,omitempty"` TokenType string `json:"tokenType,omitempty"`
@@ -44,6 +98,15 @@ type ClaimsShort struct {
jwt.RegisteredClaims jwt.RegisteredClaims
} }
type ClaimsWithoutThirdIdp struct {
*UserWithoutThirdIdp
TokenType string `json:"tokenType,omitempty"`
Nonce string `json:"nonce,omitempty"`
Tag string `json:"tag,omitempty"`
Scope string `json:"scope,omitempty"`
jwt.RegisteredClaims
}
func getShortUser(user *User) *UserShort { func getShortUser(user *User) *UserShort {
res := &UserShort{ res := &UserShort{
Owner: user.Owner, Owner: user.Owner,
@@ -52,6 +115,69 @@ func getShortUser(user *User) *UserShort {
return res return res
} }
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
res := &UserWithoutThirdIdp{
Owner: user.Owner,
Name: user.Name,
CreatedTime: user.CreatedTime,
UpdatedTime: user.UpdatedTime,
Id: user.Id,
Type: user.Type,
Password: user.Password,
PasswordSalt: user.PasswordSalt,
DisplayName: user.DisplayName,
FirstName: user.FirstName,
LastName: user.LastName,
Avatar: user.Avatar,
PermanentAvatar: user.PermanentAvatar,
Email: user.Email,
EmailVerified: user.EmailVerified,
Phone: user.Phone,
Location: user.Location,
Address: user.Address,
Affiliation: user.Affiliation,
Title: user.Title,
IdCardType: user.IdCardType,
IdCard: user.IdCard,
Homepage: user.Homepage,
Bio: user.Bio,
Tag: user.Tag,
Region: user.Region,
Language: user.Language,
Gender: user.Gender,
Birthday: user.Birthday,
Education: user.Education,
Score: user.Score,
Karma: user.Karma,
Ranking: user.Ranking,
IsDefaultAvatar: user.IsDefaultAvatar,
IsOnline: user.IsOnline,
IsAdmin: user.IsAdmin,
IsGlobalAdmin: user.IsGlobalAdmin,
IsForbidden: user.IsForbidden,
IsDeleted: user.IsDeleted,
SignupApplication: user.SignupApplication,
Hash: user.Hash,
PreHash: user.PreHash,
CreatedIp: user.CreatedIp,
LastSigninTime: user.LastSigninTime,
LastSigninIp: user.LastSigninIp,
Ldap: user.Ldap,
Properties: user.Properties,
Roles: user.Roles,
Permissions: user.Permissions,
LastSigninWrongTime: user.LastSigninWrongTime,
SigninWrongTimes: user.SigninWrongTimes,
}
return res
}
func getShortClaims(claims Claims) ClaimsShort { func getShortClaims(claims Claims) ClaimsShort {
res := ClaimsShort{ res := ClaimsShort{
UserShort: getShortUser(claims.User), UserShort: getShortUser(claims.User),
@@ -63,12 +189,44 @@ func getShortClaims(claims Claims) ClaimsShort {
return res return res
} }
func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
res := ClaimsWithoutThirdIdp{
UserWithoutThirdIdp: getUserWithoutThirdIdp(claims.User),
TokenType: claims.TokenType,
Nonce: claims.Nonce,
Tag: claims.Tag,
Scope: claims.Scope,
RegisteredClaims: claims.RegisteredClaims,
}
return res
}
func refineUser(user *User) *User {
user.Password = ""
if user.Address == nil {
user.Address = []string{}
}
if user.Properties == nil {
user.Properties = map[string]string{}
}
if user.Roles == nil {
user.Roles = []*Role{}
}
if user.Permissions == nil {
user.Permissions = []*Permission{}
}
return user
}
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) { func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
nowTime := time.Now() nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour) expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour) refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = "" user = refineUser(user)
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
name := util.GenerateId() name := util.GenerateId()
@@ -104,10 +262,12 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
claimsShort.TokenType = "refresh-token" claimsShort.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort) refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
} else { } else {
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime) claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
claims.TokenType = "refresh-token" claims.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claims) refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
} }
cert := getCertByApplication(application) cert := getCertByApplication(application)

View File

@@ -110,8 +110,8 @@ type User struct {
Ldap string `xorm:"ldap varchar(100)" json:"ldap"` Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"` Properties map[string]string `json:"properties"`
Roles []*Role `xorm:"-" json:"roles"` Roles []*Role `json:"roles"`
Permissions []*Permission `xorm:"-" json:"permissions"` Permissions []*Permission `json:"permissions"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"` SigninWrongTimes int `json:"signinWrongTimes"`

View File

@@ -158,8 +158,6 @@ func initAPI() {
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken") beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken") beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken") beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
beego.Router("/api/login/oauth/logout", &controllers.ApiController{}, "GET:TokenLogout")
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords") beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter") beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord") beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")

View File

@@ -275,6 +275,34 @@
} }
} }
}, },
"/api/add-record": {
"post": {
"tags": [
"Record API"
],
"description": "add a record",
"operationId": "ApiController.AddRecord",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the record",
"required": true,
"schema": {
"$ref": "#/definitions/object.Record"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/add-resource": { "/api/add-resource": {
"post": { "post": {
"tags": [ "tags": [
@@ -431,6 +459,14 @@
"operationId": "ApiController.GetCaptcha" "operationId": "ApiController.GetCaptcha"
} }
}, },
"/api/api/get-webhook-event": {
"get": {
"tags": [
"GetWebhookEventType API"
],
"operationId": "ApiController.GetWebhookEventType"
}
},
"/api/api/reset-email-or-phone": { "/api/api/reset-email-or-phone": {
"post": { "post": {
"tags": [ "tags": [
@@ -523,6 +559,14 @@
} }
} }
}, },
"/api/api/webhook": {
"post": {
"tags": [
"HandleOfficialAccountEvent API"
],
"operationId": "ApiController.HandleOfficialAccountEvent"
}
},
"/api/buy-product": { "/api/buy-product": {
"post": { "post": {
"tags": [ "tags": [
@@ -840,6 +884,35 @@
} }
} }
}, },
"/api/delete-session": {
"post": {
"tags": [
"Session API"
],
"description": "Delete session by userId",
"operationId": "ApiController.DeleteSession",
"parameters": [
{
"in": "query",
"name": "ID",
"description": "The ID(owner/name) of user.",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/api/delete-syncer": { "/api/delete-syncer": {
"post": { "post": {
"tags": [ "tags": [
@@ -1133,6 +1206,32 @@
} }
} }
}, },
"/api/get-default-application": {
"get": {
"tags": [
"Organization API"
],
"description": "get default application",
"operationId": "ApiController.GetDefaultApplication",
"parameters": [
{
"in": "query",
"name": "id",
"description": "organization id",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
},
"/api/get-email-and-phone": { "/api/get-email-and-phone": {
"post": { "post": {
"tags": [ "tags": [
@@ -1166,6 +1265,26 @@
} }
} }
}, },
"/api/get-global-providers": {
"get": {
"tags": [
"Provider API"
],
"description": "get Global providers",
"operationId": "ApiController.GetGlobalProviders",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Provider"
}
}
}
}
}
},
"/api/get-global-users": { "/api/get-global-users": {
"get": { "get": {
"tags": [ "tags": [
@@ -1459,6 +1578,55 @@
} }
} }
}, },
"/api/get-permissions-by-role": {
"get": {
"tags": [
"Permission API"
],
"description": "get permissions by role",
"operationId": "ApiController.GetPermissionsByRole",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id of the role",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Permission"
}
}
}
}
}
},
"/api/get-permissions-by-submitter": {
"get": {
"tags": [
"Permission API"
],
"description": "get permissions by submitter",
"operationId": "ApiController.GetPermissionsBySubmitter",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Permission"
}
}
}
}
}
},
"/api/get-product": { "/api/get-product": {
"get": { "get": {
"tags": [ "tags": [
@@ -1631,6 +1799,20 @@
} }
} }
}, },
"/api/get-release": {
"get": {
"tags": [
"System API"
],
"description": "get local github repo's latest release version info",
"operationId": "ApiController.GitRepoVersion",
"responses": {
"200": {
"description": "{string} local latest version hash of casdoor"
}
}
}
},
"/api/get-resource": { "/api/get-resource": {
"get": { "get": {
"tags": [ "tags": [
@@ -1702,6 +1884,35 @@
} }
} }
}, },
"/api/get-sessions": {
"get": {
"tags": [
"Session API"
],
"description": "Get organization user sessions",
"operationId": "ApiController.GetSessions",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The organization name",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"/api/get-sorted-users": { "/api/get-sorted-users": {
"get": { "get": {
"tags": [ "tags": [
@@ -1799,6 +2010,32 @@
} }
} }
}, },
"/api/get-system-info": {
"get": {
"tags": [
"System API"
],
"description": "get user's system info",
"operationId": "ApiController.GetSystemInfo",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id of the user",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.SystemInfo"
}
}
}
}
},
"/api/get-token": { "/api/get-token": {
"get": { "get": {
"tags": [ "tags": [
@@ -2360,45 +2597,6 @@
} }
} }
}, },
"/api/login/oauth/logout": {
"get": {
"tags": [
"Token API"
],
"description": "delete token by AccessToken",
"operationId": "ApiController.TokenLogout",
"parameters": [
{
"in": "query",
"name": "id_token_hint",
"description": "id_token_hint",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "post_logout_redirect_uri",
"description": "post_logout_redirect_uri",
"type": "string"
},
{
"in": "query",
"name": "state",
"description": "state",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/login/oauth/refresh_token": { "/api/login/oauth/refresh_token": {
"post": { "post": {
"tags": [ "tags": [
@@ -2471,6 +2669,26 @@
], ],
"description": "logout the current user", "description": "logout the current user",
"operationId": "ApiController.Logout", "operationId": "ApiController.Logout",
"parameters": [
{
"in": "query",
"name": "id_token_hint",
"description": "id_token_hint",
"type": "string"
},
{
"in": "query",
"name": "post_logout_redirect_uri",
"description": "post_logout_redirect_uri",
"type": "string"
},
{
"in": "query",
"name": "state",
"description": "state",
"type": "string"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "The Response object", "description": "The Response object",
@@ -2486,6 +2704,26 @@
], ],
"description": "logout the current user", "description": "logout the current user",
"operationId": "ApiController.Logout", "operationId": "ApiController.Logout",
"parameters": [
{
"in": "query",
"name": "id_token_hint",
"description": "id_token_hint",
"type": "string"
},
{
"in": "query",
"name": "post_logout_redirect_uri",
"description": "post_logout_redirect_uri",
"type": "string"
},
{
"in": "query",
"name": "state",
"description": "state",
"type": "string"
}
],
"responses": { "responses": {
"200": { "200": {
"description": "The Response object", "description": "The Response object",
@@ -3267,11 +3505,11 @@
} }
}, },
"definitions": { "definitions": {
"2200.0xc0003f8480.false": { "2268.0xc0000f9650.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2235.0xc0003f84b0.false": { "2302.0xc0000f9680.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@@ -3316,6 +3554,15 @@
"autoSignin": { "autoSignin": {
"type": "boolean" "type": "boolean"
}, },
"captchaToken": {
"type": "string"
},
"captchaType": {
"type": "string"
},
"clientSecret": {
"type": "string"
},
"code": { "code": {
"type": "string" "type": "string"
}, },
@@ -3389,10 +3636,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2200.0xc0003f8480.false" "$ref": "#/definitions/2268.0xc0000f9650.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2235.0xc0003f84b0.false" "$ref": "#/definitions/2302.0xc0000f9680.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -3491,9 +3738,15 @@
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"enableAutoSignin": {
"type": "boolean"
},
"enableCodeSignin": { "enableCodeSignin": {
"type": "boolean" "type": "boolean"
}, },
"enableLinkWithEmail": {
"type": "boolean"
},
"enablePassword": { "enablePassword": {
"type": "boolean" "type": "boolean"
}, },
@@ -3516,6 +3769,19 @@
"forgetUrl": { "forgetUrl": {
"type": "string" "type": "string"
}, },
"formBackgroundUrl": {
"type": "string"
},
"formCss": {
"type": "string"
},
"formOffset": {
"type": "integer",
"format": "int64"
},
"formSideHtml": {
"type": "string"
},
"grantTypes": { "grantTypes": {
"type": "array", "type": "array",
"items": { "items": {
@@ -3556,6 +3822,9 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"samlReplyUrl": {
"type": "string"
},
"signinHtml": { "signinHtml": {
"type": "string" "type": "string"
}, },
@@ -3689,6 +3958,24 @@
} }
} }
}, },
"object.ManagedAccount": {
"title": "ManagedAccount",
"type": "object",
"properties": {
"application": {
"type": "string"
},
"password": {
"type": "string"
},
"signinUrl": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"object.Model": { "object.Model": {
"title": "Model", "title": "Model",
"type": "object", "type": "object",
@@ -3726,6 +4013,9 @@
"type": "string" "type": "string"
} }
}, },
"end_session_endpoint": {
"type": "string"
},
"grant_types_supported": { "grant_types_supported": {
"type": "array", "type": "array",
"items": { "items": {
@@ -3801,6 +4091,9 @@
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"defaultApplication": {
"type": "string"
},
"defaultAvatar": { "defaultAvatar": {
"type": "string" "type": "string"
}, },
@@ -3813,9 +4106,19 @@
"favicon": { "favicon": {
"type": "string" "type": "string"
}, },
"initScore": {
"type": "integer",
"format": "int64"
},
"isProfilePublic": { "isProfilePublic": {
"type": "boolean" "type": "boolean"
}, },
"languages": {
"type": "array",
"items": {
"type": "string"
}
},
"masterPassword": { "masterPassword": {
"type": "string" "type": "string"
}, },
@@ -3943,12 +4246,27 @@
"type": "string" "type": "string"
} }
}, },
"adapter": {
"type": "string"
},
"approveTime": {
"type": "string"
},
"approver": {
"type": "string"
},
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"effect": { "effect": {
"type": "string" "type": "string"
}, },
@@ -3979,6 +4297,12 @@
"type": "string" "type": "string"
} }
}, },
"state": {
"type": "string"
},
"submitter": {
"type": "string"
},
"users": { "users": {
"type": "array", "type": "array",
"items": { "items": {
@@ -3997,6 +4321,9 @@
"currency": { "currency": {
"type": "string" "type": "string"
}, },
"description": {
"type": "string"
},
"detail": { "detail": {
"type": "string" "type": "string"
}, },
@@ -4016,6 +4343,12 @@
"type": "number", "type": "number",
"format": "double" "format": "double"
}, },
"providerObjs": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Provider"
}
},
"providers": { "providers": {
"type": "array", "type": "array",
"items": { "items": {
@@ -4090,6 +4423,9 @@
"customUserInfoUrl": { "customUserInfoUrl": {
"type": "string" "type": "string"
}, },
"disableSsl": {
"type": "boolean"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
@@ -4126,6 +4462,9 @@
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"pathPrefix": {
"type": "string"
},
"port": { "port": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
@@ -4133,6 +4472,9 @@
"providerUrl": { "providerUrl": {
"type": "string" "type": "string"
}, },
"receiver": {
"type": "string"
},
"regionId": { "regionId": {
"type": "string" "type": "string"
}, },
@@ -4172,11 +4514,17 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"owner": {
"type": "string"
},
"prompted": { "prompted": {
"type": "boolean" "type": "boolean"
}, },
"provider": { "provider": {
"$ref": "#/definitions/object.Provider" "$ref": "#/definitions/object.Provider"
},
"rule": {
"type": "string"
} }
} }
}, },
@@ -4233,6 +4581,12 @@
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"isEnabled": { "isEnabled": {
"type": "boolean" "type": "boolean"
}, },
@@ -4345,6 +4699,10 @@
} }
} }
}, },
"object.SystemInfo": {
"title": "SystemInfo",
"type": "object"
},
"object.TableColumn": { "object.TableColumn": {
"title": "TableColumn", "title": "TableColumn",
"type": "object", "type": "object",
@@ -4605,15 +4963,27 @@
"lastSigninTime": { "lastSigninTime": {
"type": "string" "type": "string"
}, },
"lastSigninWrongTime": {
"type": "string"
},
"ldap": { "ldap": {
"type": "string" "type": "string"
}, },
"line": {
"type": "string"
},
"linkedin": { "linkedin": {
"type": "string" "type": "string"
}, },
"location": { "location": {
"type": "string" "type": "string"
}, },
"managedAccounts": {
"type": "array",
"items": {
"$ref": "#/definitions/object.ManagedAccount"
}
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@@ -4669,6 +5039,10 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"signinWrongTimes": {
"type": "integer",
"format": "int64"
},
"signupApplication": { "signupApplication": {
"type": "string" "type": "string"
}, },
@@ -4687,9 +5061,6 @@
"type": { "type": {
"type": "string" "type": "string"
}, },
"unionId": {
"type": "string"
},
"updatedTime": { "updatedTime": {
"type": "string" "type": "string"
}, },

View File

@@ -177,6 +177,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/add-record:
post:
tags:
- Record API
description: add a record
operationId: ApiController.AddRecord
parameters:
- in: body
name: body
description: The details of the record
required: true
schema:
$ref: '#/definitions/object.Record'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/add-resource: /api/add-resource:
post: post:
tags: tags:
@@ -277,6 +295,11 @@ paths:
tags: tags:
- Login API - Login API
operationId: ApiController.GetCaptcha operationId: ApiController.GetCaptcha
/api/api/get-webhook-event:
get:
tags:
- GetWebhookEventType API
operationId: ApiController.GetWebhookEventType
/api/api/reset-email-or-phone: /api/api/reset-email-or-phone:
post: post:
tags: tags:
@@ -338,6 +361,11 @@ paths:
description: object description: object
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/Response'
/api/api/webhook:
post:
tags:
- HandleOfficialAccountEvent API
operationId: ApiController.HandleOfficialAccountEvent
/api/buy-product: /api/buy-product:
post: post:
tags: tags:
@@ -542,6 +570,25 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/delete-session:
post:
tags:
- Session API
description: Delete session by userId
operationId: ApiController.DeleteSession
parameters:
- in: query
name: ID
description: The ID(owner/name) of user.
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
type: string
/api/delete-syncer: /api/delete-syncer:
post: post:
tags: tags:
@@ -734,6 +781,23 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Cert' $ref: '#/definitions/object.Cert'
/api/get-default-application:
get:
tags:
- Organization API
description: get default application
operationId: ApiController.GetDefaultApplication
parameters:
- in: query
name: id
description: organization id
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/Response'
/api/get-email-and-phone: /api/get-email-and-phone:
post: post:
tags: tags:
@@ -756,6 +820,19 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $ref: '#/definitions/controllers.Response'
/api/get-global-providers:
get:
tags:
- Provider API
description: get Global providers
operationId: ApiController.GetGlobalProviders
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Provider'
/api/get-global-users: /api/get-global-users:
get: get:
tags: tags:
@@ -947,6 +1024,38 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Permission' $ref: '#/definitions/object.Permission'
/api/get-permissions-by-role:
get:
tags:
- Permission API
description: get permissions by role
operationId: ApiController.GetPermissionsByRole
parameters:
- in: query
name: id
description: The id of the role
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Permission'
/api/get-permissions-by-submitter:
get:
tags:
- Permission API
description: get permissions by submitter
operationId: ApiController.GetPermissionsBySubmitter
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Permission'
/api/get-product: /api/get-product:
get: get:
tags: tags:
@@ -1060,6 +1169,15 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.Record' $ref: '#/definitions/object.Record'
/api/get-release:
get:
tags:
- System API
description: get local github repo's latest release version info
operationId: ApiController.GitRepoVersion
responses:
"200":
description: '{string} local latest version hash of casdoor'
/api/get-resource: /api/get-resource:
get: get:
tags: tags:
@@ -1106,6 +1224,25 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Role' $ref: '#/definitions/object.Role'
/api/get-sessions:
get:
tags:
- Session API
description: Get organization user sessions
operationId: ApiController.GetSessions
parameters:
- in: query
name: owner
description: The organization name
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
type: string
/api/get-sorted-users: /api/get-sorted-users:
get: get:
tags: tags:
@@ -1170,6 +1307,23 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.Syncer' $ref: '#/definitions/object.Syncer'
/api/get-system-info:
get:
tags:
- System API
description: get user's system info
operationId: ApiController.GetSystemInfo
parameters:
- in: query
name: id
description: The id of the user
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.SystemInfo'
/api/get-token: /api/get-token:
get: get:
tags: tags:
@@ -1544,32 +1698,6 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.TokenError' $ref: '#/definitions/object.TokenError'
/api/login/oauth/logout:
get:
tags:
- Token API
description: delete token by AccessToken
operationId: ApiController.TokenLogout
parameters:
- in: query
name: id_token_hint
description: id_token_hint
required: true
type: string
- in: query
name: post_logout_redirect_uri
description: post_logout_redirect_uri
type: string
- in: query
name: state
description: state
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/login/oauth/refresh_token: /api/login/oauth/refresh_token:
post: post:
tags: tags:
@@ -1620,6 +1748,19 @@ paths:
- Login API - Login API
description: logout the current user description: logout the current user
operationId: ApiController.Logout operationId: ApiController.Logout
parameters:
- in: query
name: id_token_hint
description: id_token_hint
type: string
- in: query
name: post_logout_redirect_uri
description: post_logout_redirect_uri
type: string
- in: query
name: state
description: state
type: string
responses: responses:
"200": "200":
description: The Response object description: The Response object
@@ -1630,6 +1771,19 @@ paths:
- Login API - Login API
description: logout the current user description: logout the current user
operationId: ApiController.Logout operationId: ApiController.Logout
parameters:
- in: query
name: id_token_hint
description: id_token_hint
type: string
- in: query
name: post_logout_redirect_uri
description: post_logout_redirect_uri
type: string
- in: query
name: state
description: state
type: string
responses: responses:
"200": "200":
description: The Response object description: The Response object
@@ -2139,10 +2293,10 @@ paths:
schema: schema:
$ref: '#/definitions/Response' $ref: '#/definitions/Response'
definitions: definitions:
2200.0xc0003f8480.false: 2268.0xc0000f9650.false:
title: "false" title: "false"
type: object type: object
2235.0xc0003f84b0.false: 2302.0xc0000f9680.false:
title: "false" title: "false"
type: object type: object
Response: Response:
@@ -2174,6 +2328,12 @@ definitions:
type: string type: string
autoSignin: autoSignin:
type: boolean type: boolean
captchaToken:
type: string
captchaType:
type: string
clientSecret:
type: string
code: code:
type: string type: string
email: email:
@@ -2223,9 +2383,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2200.0xc0003f8480.false' $ref: '#/definitions/2268.0xc0000f9650.false'
data2: data2:
$ref: '#/definitions/2235.0xc0003f84b0.false' $ref: '#/definitions/2302.0xc0000f9680.false'
msg: msg:
type: string type: string
name: name:
@@ -2291,8 +2451,12 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
enableAutoSignin:
type: boolean
enableCodeSignin: enableCodeSignin:
type: boolean type: boolean
enableLinkWithEmail:
type: boolean
enablePassword: enablePassword:
type: boolean type: boolean
enableSamlCompress: enableSamlCompress:
@@ -2308,6 +2472,15 @@ definitions:
format: int64 format: int64
forgetUrl: forgetUrl:
type: string type: string
formBackgroundUrl:
type: string
formCss:
type: string
formOffset:
type: integer
format: int64
formSideHtml:
type: string
grantTypes: grantTypes:
type: array type: array
items: items:
@@ -2335,6 +2508,8 @@ definitions:
refreshExpireInHours: refreshExpireInHours:
type: integer type: integer
format: int64 format: int64
samlReplyUrl:
type: string
signinHtml: signinHtml:
type: string type: string
signinUrl: signinUrl:
@@ -2424,6 +2599,18 @@ definitions:
type: string type: string
username: username:
type: string type: string
object.ManagedAccount:
title: ManagedAccount
type: object
properties:
application:
type: string
password:
type: string
signinUrl:
type: string
username:
type: string
object.Model: object.Model:
title: Model title: Model
type: object type: object
@@ -2450,6 +2637,8 @@ definitions:
type: array type: array
items: items:
type: string type: string
end_session_endpoint:
type: string
grant_types_supported: grant_types_supported:
type: array type: array
items: items:
@@ -2500,6 +2689,8 @@ definitions:
$ref: '#/definitions/object.AccountItem' $ref: '#/definitions/object.AccountItem'
createdTime: createdTime:
type: string type: string
defaultApplication:
type: string
defaultAvatar: defaultAvatar:
type: string type: string
displayName: displayName:
@@ -2508,8 +2699,15 @@ definitions:
type: boolean type: boolean
favicon: favicon:
type: string type: string
initScore:
type: integer
format: int64
isProfilePublic: isProfilePublic:
type: boolean type: boolean
languages:
type: array
items:
type: string
masterPassword: masterPassword:
type: string type: string
name: name:
@@ -2595,10 +2793,20 @@ definitions:
type: array type: array
items: items:
type: string type: string
adapter:
type: string
approveTime:
type: string
approver:
type: string
createdTime: createdTime:
type: string type: string
displayName: displayName:
type: string type: string
domains:
type: array
items:
type: string
effect: effect:
type: string type: string
isEnabled: isEnabled:
@@ -2619,6 +2827,10 @@ definitions:
type: array type: array
items: items:
type: string type: string
state:
type: string
submitter:
type: string
users: users:
type: array type: array
items: items:
@@ -2631,6 +2843,8 @@ definitions:
type: string type: string
currency: currency:
type: string type: string
description:
type: string
detail: detail:
type: string type: string
displayName: displayName:
@@ -2644,6 +2858,10 @@ definitions:
price: price:
type: number type: number
format: double format: double
providerObjs:
type: array
items:
$ref: '#/definitions/object.Provider'
providers: providers:
type: array type: array
items: items:
@@ -2694,6 +2912,8 @@ definitions:
type: string type: string
customUserInfoUrl: customUserInfoUrl:
type: string type: string
disableSsl:
type: boolean
displayName: displayName:
type: string type: string
domain: domain:
@@ -2718,11 +2938,15 @@ definitions:
type: string type: string
owner: owner:
type: string type: string
pathPrefix:
type: string
port: port:
type: integer type: integer
format: int64 format: int64
providerUrl: providerUrl:
type: string type: string
receiver:
type: string
regionId: regionId:
type: string type: string
signName: signName:
@@ -2749,10 +2973,14 @@ definitions:
type: boolean type: boolean
name: name:
type: string type: string
owner:
type: string
prompted: prompted:
type: boolean type: boolean
provider: provider:
$ref: '#/definitions/object.Provider' $ref: '#/definitions/object.Provider'
rule:
type: string
object.Record: object.Record:
title: Record title: Record
type: object type: object
@@ -2790,6 +3018,10 @@ definitions:
type: string type: string
displayName: displayName:
type: string type: string
domains:
type: array
items:
type: string
isEnabled: isEnabled:
type: boolean type: boolean
name: name:
@@ -2864,6 +3096,9 @@ definitions:
type: string type: string
user: user:
type: string type: string
object.SystemInfo:
title: SystemInfo
type: object
object.TableColumn: object.TableColumn:
title: TableColumn title: TableColumn
type: object type: object
@@ -3040,12 +3275,20 @@ definitions:
type: string type: string
lastSigninTime: lastSigninTime:
type: string type: string
lastSigninWrongTime:
type: string
ldap: ldap:
type: string type: string
line:
type: string
linkedin: linkedin:
type: string type: string
location: location:
type: string type: string
managedAccounts:
type: array
items:
$ref: '#/definitions/object.ManagedAccount'
name: name:
type: string type: string
okta: okta:
@@ -3083,6 +3326,9 @@ definitions:
score: score:
type: integer type: integer
format: int64 format: int64
signinWrongTimes:
type: integer
format: int64
signupApplication: signupApplication:
type: string type: string
slack: slack:
@@ -3095,8 +3341,6 @@ definitions:
type: string type: string
type: type:
type: string type: string
unionId:
type: string
updatedTime: updatedTime:
type: string type: string
webauthnCredentials: webauthnCredentials:

View File

@@ -53,7 +53,7 @@ func Test_IsTokenExpired(t *testing.T) {
for _, scenario := range []testCases{ for _, scenario := range []testCases{
{ {
description: "Token emited now is valid for 60 minutes", description: "Token emitted now is valid for 60 minutes",
input: input{ input: input{
createdTime: time.Now().Format(time.RFC3339), createdTime: time.Now().Format(time.RFC3339),
expiresIn: 60, expiresIn: 60,
@@ -61,7 +61,7 @@ func Test_IsTokenExpired(t *testing.T) {
expected: false, expected: false,
}, },
{ {
description: "Token emited 60 minutes before now is valid for 60 minutes", description: "Token emitted 60 minutes before now is valid for 60 minutes",
input: input{ input: input{
createdTime: time.Now().Add(-time.Minute * 60).Format(time.RFC3339), createdTime: time.Now().Add(-time.Minute * 60).Format(time.RFC3339),
expiresIn: 61, expiresIn: 61,
@@ -69,7 +69,7 @@ func Test_IsTokenExpired(t *testing.T) {
expected: false, expected: false,
}, },
{ {
description: "Token emited 2 hours before now is Expired after 60 minutes", description: "Token emitted 2 hours before now is Expired after 60 minutes",
input: input{ input: input{
createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339), createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339),
expiresIn: 60, expiresIn: 60,
@@ -77,7 +77,7 @@ func Test_IsTokenExpired(t *testing.T) {
expected: true, expected: true,
}, },
{ {
description: "Token emited 61 minutes before now is Expired after 60 minutes", description: "Token emitted 61 minutes before now is Expired after 60 minutes",
input: input{ input: input{
createdTime: time.Now().Add(-time.Minute * 61).Format(time.RFC3339), createdTime: time.Now().Add(-time.Minute * 61).Format(time.RFC3339),
expiresIn: 60, expiresIn: 60,
@@ -85,7 +85,7 @@ func Test_IsTokenExpired(t *testing.T) {
expected: true, expected: true,
}, },
{ {
description: "Token emited 2 hours before now is velid for 120 minutes", description: "Token emitted 2 hours before now is valid for 120 minutes",
input: input{ input: input{
createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339), createdTime: time.Now().Add(-time.Hour * 2).Format(time.RFC3339),
expiresIn: 121, expiresIn: 121,
@@ -93,7 +93,7 @@ func Test_IsTokenExpired(t *testing.T) {
expected: false, expected: false,
}, },
{ {
description: "Token emited 159 minutes before now is Expired after 60 minutes", description: "Token emitted 159 minutes before now is Expired after 60 minutes",
input: input{ input: input{
createdTime: time.Now().Add(-time.Minute * 159).Format(time.RFC3339), createdTime: time.Now().Add(-time.Minute * 159).Format(time.RFC3339),
expiresIn: 120, expiresIn: 120,

View File

@@ -6,10 +6,13 @@
"@ant-design/icons": "^4.7.0", "@ant-design/icons": "^4.7.0",
"@craco/craco": "^6.4.5", "@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.7.10", "@crowdin/cli": "^3.7.10",
"@ctrl/tinycolor": "^3.5.0",
"@emotion/react": "^11.10.5",
"@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.1.2", "antd": "5.1.6",
"antd-token-previewer": "^1.1.0-22",
"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

@@ -1,16 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?5998fcd123c220efc0936edf4f250504";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<meta charset="utf-8" /> <meta charset="utf-8" />
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />--> <!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />

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, theme} from "antd"; import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} 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";
@@ -83,8 +83,8 @@ class App extends Component {
account: undefined, account: undefined,
uri: null, uri: null,
menuVisible: false, menuVisible: false,
themeAlgorithm: null, themeAlgorithm: ["default"],
logo: null, themeData: Setting.ThemeDefault,
}; };
Setting.initServerUrl(); Setting.initServerUrl();
@@ -99,16 +99,6 @@ 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;
@@ -198,8 +188,12 @@ class App extends Component {
return ""; return "";
} }
getTheme() { getLogo(themes) {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"]; if (themes.includes("dark")) {
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
} else {
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
}
} }
setLanguage(account) { setLanguage(account) {
@@ -209,6 +203,19 @@ class App extends Component {
} }
} }
setTheme = (theme, initThemeAlgorithm) => {
this.setState({
themeData: theme,
});
if (initThemeAlgorithm) {
this.setState({
logo: this.getLogo(Setting.getAlgorithmNames(theme)),
themeAlgorithm: Setting.getAlgorithmNames(theme),
});
}
};
getAccount() { getAccount() {
const params = new URLSearchParams(this.props.location.search); const params = new URLSearchParams(this.props.location.search);
@@ -233,7 +240,9 @@ class App extends Component {
if (res.status === "ok") { if (res.status === "ok") {
account = res.data; account = res.data;
account.organization = res.data2; account.organization = res.data2;
this.setLanguage(account); this.setLanguage(account);
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
} else { } else {
if (res.data !== "Please login first") { if (res.data !== "Please login first") {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
@@ -259,6 +268,7 @@ class App extends Component {
this.setState({ this.setState({
account: null, account: null,
themeAlgorithm: ["default"],
}); });
Setting.showMessage("success", i18next.t("application:Logged out successfully")); Setting.showMessage("success", i18next.t("application:Logged out successfully"));
@@ -282,14 +292,6 @@ class App extends Component {
}); });
} }
handleRightDropdownClick(e) {
if (e.key === "/account") {
this.props.history.push("/account");
} else if (e.key === "/logout") {
this.logout();
}
}
renderAvatar() { renderAvatar() {
if (this.state.account.avatar === "") { if (this.state.account.avatar === "") {
return ( return (
@@ -313,13 +315,18 @@ class App extends Component {
)); ));
items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>, items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>,
"/logout")); "/logout"));
const onClick = this.handleRightDropdownClick.bind(this);
const onClick = (e) => {
if (e.key === "/account") {
this.props.history.push("/account");
} else if (e.key === "/logout") {
this.logout();
}
};
return ( return (
<Dropdown key="/rightDropDown" menu={{items, onClick}} className="rightDropDown"> <Dropdown key="/rightDropDown" menu={{items, onClick}} >
<div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}> <div className="rightDropDown">
&nbsp;
&nbsp;
{ {
this.renderAvatar() this.renderAvatar()
} }
@@ -334,34 +341,30 @@ class App extends Component {
); );
} }
renderAccount() { renderAccountMenu() {
const res = [];
if (this.state.account === undefined) { if (this.state.account === undefined) {
return null; return null;
} else if (this.state.account === null) { } else if (this.state.account === null) {
// res.push( return null;
// <Menu.Item key="/signup" style={{float: 'right', marginRight: '20px'}}>
// <Link to="/signup">
// {i18next.t("account:Sign Up")}
// </Link>
// </Menu.Item>
// );
// res.push(
// <Menu.Item key="/login" style={{float: 'right'}}>
// <Link to="/login">
// {i18next.t("account:Login")}
// </Link>
// </Menu.Item>
// );
} else { } else {
res.push(this.renderRightDropdown()); return (
<React.Fragment>
{this.renderRightDropdown()}
<SelectThemeBox
themeAlgorithm={this.state.themeAlgorithm}
onChange={(nextThemeAlgorithm) => {
this.setState({
themeAlgorithm: nextThemeAlgorithm,
logo: this.getLogo(nextThemeAlgorithm),
});
}} />
<SelectLanguageBox languages={this.state.account.organization.languages} />
</React.Fragment>
);
} }
return res;
} }
renderMenu() { getMenuItems() {
const res = []; const res = [];
if (this.state.account === null || this.state.account === undefined) { if (this.state.account === null || this.state.account === undefined) {
@@ -487,63 +490,53 @@ class App extends Component {
renderRouter() { renderRouter() {
return ( return (
<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} onChangeTheme={this.setTheme} {...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="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage 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" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/} <Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage 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" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} /> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage 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} />)} />
<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>
); );
} }
@@ -560,85 +553,56 @@ class App extends Component {
}; };
renderContent() { renderContent() {
if (!Setting.isMobile()) { return (
return ( <Layout id="parent-area">
<Layout id="parent-area"> {/* https://github.com/ant-design/ant-design/issues/40394 ant design bug. If it will be fixed, we can delete the code for control the color of Header*/}
<Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}> <Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}>
{ {Setting.isMobile() ? null : (
Setting.isMobile() ? null : ( <Link to={"/"}>
<Link to={"/"}> <div className="logo" style={{background: `url(${this.getLogo(Setting.getAlgorithmNames(this.state.themeData))})`}} />
<div className="logo" style={{background: `url(${this.state.logo})`}} /> </Link>
</Link> )}
) {Setting.isMobile() ?
} <React.Fragment>
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
<Menu
items={this.getMenuItems()}
mode={"inline"}
selectedKeys={[this.state.selectedMenuKey]}
style={{lineHeight: "64px"}}
onClick={this.onClose}
>
</Menu>
</Drawer>
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")}
</Button>
</React.Fragment> :
<Menu <Menu
items={this.renderMenu()} items={this.getMenuItems()}
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} mode={"horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]} selectedKeys={[this.state.selectedMenuKey]}
style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}} style={{position: "absolute", left: "145px", right: "260px"}}
/> />
{
this.renderAccount()
}
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</Header>
<Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
<Card className="content-warp-card">
{
this.renderRouter()
}
</Card>
</Content>
{
this.renderFooter()
} }
</Layout> {
); this.renderAccountMenu()
} else { }
return ( </Header>
<Layout> <Content style={{display: "flex", flexDirection: "column"}} >
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}> {Setting.isMobile() ?
{ this.renderRouter() :
Setting.isMobile() ? null : ( <Card className="content-warp-card">
<Link to={"/"}> {this.renderRouter()}
<div className="logo" style={{background: `url(${this.state.logo})`}} /> </Card>
</Link> }
) </Content>
} {this.renderFooter()}
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}> </Layout>
<Menu );
// theme="dark"
items={this.renderMenu()}
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
onClick={this.onClose}
>
</Menu>
</Drawer>
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")}
</Button>
{
this.renderAccount()
}
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</Header>
<Content style={{display: "flex", flexDirection: "column"}} >{
this.renderRouter()}
</Content>
{this.renderFooter()}
</Layout>
);
}
} }
renderFooter() { renderFooter() {
// How to keep your footer where it belongs ?
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return ( return (
<React.Fragment> <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} />}
@@ -647,7 +611,7 @@ class App extends Component {
textAlign: "center", textAlign: "center",
} }
}> }>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a> Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.getLogo(Setting.getAlgorithmNames(this.state.themeData))} /></a>
</Footer> </Footer>
</React.Fragment> </React.Fragment>
); );
@@ -669,26 +633,35 @@ class App extends Component {
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"}}>
{
this.isEntryPages() ?
<EntryPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />
:
<Switch>
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
}
</Content>
{ {
this.renderFooter() this.isEntryPages() ?
<EntryPage
account={this.state.account}
theme={this.state.themeData}
onUpdateAccount={(account) => {
this.onUpdateAccount(account);
}}
updataThemeData={(nextThemeData) => {
this.setState({
themeData: nextThemeData,
});
localStorage.setItem("themeAlgorithm", Setting.getAlgorithmNames(nextThemeData).toString());
}}
/> :
<Switch>
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
} }
</Layout> </Content>
</React.Fragment> {
this.renderFooter()
}
</Layout>
); );
} }
@@ -704,40 +677,24 @@ class App extends Component {
} }
render() { render() {
if (this.state.account === undefined || this.state.account === null) {
return (
<React.Fragment>
<Helmet>
<link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
</Helmet>
<ConfigProvider theme={{
token: {
colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)",
},
algorithm: this.state.themeAlgorithm,
}}>
{
this.renderPage()
}
</ConfigProvider>
</React.Fragment>
);
}
const organization = this.state.account.organization;
return ( return (
<React.Fragment> <React.Fragment>
<Helmet> {(this.state.account === undefined || this.state.account === null) ?
<title>{organization.displayName}</title> <Helmet>
<link rel="icon" href={organization.favicon} /> <link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
</Helmet> </Helmet> :
<Helmet>
<title>{this.state.account.organization?.displayName}</title>
<link rel="icon" href={this.state.account.organization?.favicon} />
</Helmet>
}
<ConfigProvider theme={{ <ConfigProvider theme={{
token: { token: {
colorPrimary: "rgb(89,54,213)", colorPrimary: this.state.themeData.colorPrimary,
colorInfo: "rgb(89,54,213)", colorInfo: this.state.themeData.colorPrimary,
borderRadius: this.state.themeData.borderRadius,
}, },
algorithm: this.state.themeAlgorithm, algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
}}> }}>
{ {
this.renderPage() this.renderPage()

View File

@@ -1,8 +1,6 @@
/* stylelint-disable at-rule-name-case */ /* stylelint-disable at-rule-name-case */
/* stylelint-disable selector-class-pattern */ /* stylelint-disable selector-class-pattern */
@StaticBaseUrl: "https://cdn.casbin.org";
.App { .App {
text-align: center; text-align: center;
} }
@@ -45,34 +43,13 @@ img {
margin-bottom: 30px; margin-bottom: 30px;
} }
.language-box { .select-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg"); display: flex;
background-size: 25px, 25px; align-items: center;
background-position: center; justify-content: center;
background-repeat: no-repeat;
border-radius: 5px; border-radius: 5px;
width: 45px; width: 45px;
height: 100%; height: 64px;
float: right;
cursor: pointer;
&:hover {
background-color: #f5f5f5;
}
}
.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; float: right;
cursor: pointer; cursor: pointer;
@@ -82,7 +59,12 @@ img {
} }
.rightDropDown { .rightDropDown {
display: flex;
align-items: center;
justify-content: center;
border-radius: 7px; border-radius: 7px;
float: right;
cursor: pointer;
&:hover { &:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
@@ -151,6 +133,4 @@ img {
.ant-menu-horizontal { .ant-menu-horizontal {
border-bottom: none !important; border-bottom: none !important;
margin-right: 30px;
right: 230px;
} }

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, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd"; import {Button, Card, Col, ConfigProvider, Input, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons"; import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as CertBackend from "./backend/CertBackend"; import * as CertBackend from "./backend/CertBackend";
@@ -32,6 +32,7 @@ import copy from "copy-to-clipboard";
import {Controlled as CodeMirror} from "react-codemirror2"; import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
import ThemeEditor from "./common/theme/ThemeEditor";
require("codemirror/theme/material-darker.css"); require("codemirror/theme/material-darker.css");
require("codemirror/mode/htmlmixed/htmlmixed"); require("codemirror/mode/htmlmixed/htmlmixed");
@@ -709,6 +710,31 @@ class ApplicationEditPage extends React.Component {
: null} : null}
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
</Col>
<Col span={22} style={{marginTop: "5px"}}>
<Row>
<Radio.Group value={this.state.application.themeData?.isEnabled ?? false} onChange={e => {
const {_, ...theme} = this.state.application.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
this.updateApplicationField("themeData", {...theme, isEnabled: e.target.value});
}} >
<Radio.Button value={false}>{i18next.t("application:Follow organization theme")}</Radio.Button>
<Radio.Button value={true}>{i18next.t("theme:Customize theme")}</Radio.Button>
</Radio.Group>
</Row>
{
this.state.application.themeData?.isEnabled ?
<Row style={{marginTop: "20px"}}>
<ThemeEditor themeData={this.state.application.themeData} onThemeChange={(_, nextThemeData) => {
const {isEnabled} = this.state.application.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
this.updateApplicationField("themeData", {...nextThemeData, isEnabled});
}} />
</Row> : null
}
</Col>
</Row>
{ {
!this.state.application.enableSignUp ? null : ( !this.state.application.enableSignUp ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
@@ -738,6 +764,7 @@ class ApplicationEditPage extends React.Component {
} }
renderSignupSigninPreview() { renderSignupSigninPreview() {
const themeData = this.state.application.themeData ?? Setting.ThemeDefault;
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: "97%", 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)"};
@@ -756,20 +783,28 @@ 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", overflow: "auto"}}> <ConfigProvider theme={{
{ token: {
this.state.application.enablePassword ? ( colorPrimary: themeData.colorPrimary,
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}> colorInfo: themeData.colorPrimary,
<SignupPage application={this.state.application} preview = "auto" /> borderRadius: themeData.borderRadius,
</div> },
) : ( }}>
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "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={"signup"} application={this.state.application} preview = "auto" /> {
</div> this.state.application.enablePassword ? (
) <div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
} <SignupPage application={this.state.application} preview = "auto" />
<div style={{overflow: "auto", ...maskStyle}} /> </div>
</div> ) : (
<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={{overflow: "auto", ...maskStyle}} />
</div>
</ConfigProvider>
</Col> </Col>
<Col span={previewGrid}> <Col span={previewGrid}>
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => { <Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
@@ -780,18 +815,27 @@ 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", overflow: "auto"}}> <ConfigProvider theme={{
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}> token: {
<LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" /> colorPrimary: themeData.colorPrimary,
colorInfo: themeData.colorPrimary,
borderRadius: themeData.borderRadius,
},
}}>
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
</div>
<div style={{overflow: "auto", ...maskStyle}} />
</div> </div>
<div style={{overflow: "auto", ...maskStyle}} /> </ConfigProvider>
</div>
</Col> </Col>
</React.Fragment> </React.Fragment>
); );
} }
renderPromptPreview() { renderPromptPreview() {
const themeData = this.state.application.themeData ?? Setting.ThemeDefault;
const promptUrl = `/prompt/${this.state.application.name}`; const promptUrl = `/prompt/${this.state.application.name}`;
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: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
return ( return (
@@ -804,10 +848,18 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy prompt page URL")} {i18next.t("application:Copy prompt page URL")}
</Button> </Button>
<br /> <br />
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}> <ConfigProvider theme={{
<PromptPage application={this.state.application} account={this.props.account} /> token: {
<div style={maskStyle} /> colorPrimary: themeData.colorPrimary,
</div> colorInfo: themeData.colorPrimary,
borderRadius: themeData.borderRadius,
},
}}>
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
<PromptPage application={this.state.application} account={this.props.account} />
<div style={maskStyle} />
</div>
</ConfigProvider>
</Col> </Col>
); );
} }

View File

@@ -17,5 +17,6 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
export const ForceLanguage = ""; export const ForceLanguage = "";
export const DefaultLanguage = "en"; export const DefaultLanguage = "en";
export const InitThemeAlgorithm = true;
export const EnableExtraPages = true; export const EnableExtraPages = true;

View File

@@ -52,32 +52,41 @@ class EntryPage extends React.Component {
} }
} }
getApplicationObj() {
return this.state.application || null;
}
render() { render() {
const onUpdateApplication = (application) => { const onUpdateApplication = (application) => {
this.setState({ this.setState({
application: application, application: application,
}); });
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Setting.ThemeDefault;
this.props.updataThemeData(themeData);
}; };
return <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}> return (
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} /> <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Switch> <Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} /> <Switch>
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} /> <Route exact path="/signup" 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="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...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="/login" 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="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...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="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} 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/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} 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="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} 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/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} /> <Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...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="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...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="/prompt" 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="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} /> <Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
</Switch> <Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
</div>; </Switch>
</div>
);
} }
} }

View File

@@ -25,11 +25,27 @@ class ManagedAccountTable extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
managedAccounts: [],
}; };
this.state.managedAccounts = this.props.table.map((item, index) => {
item.key = index;
return item;
});
} }
count = this.props.table.length;
updateTable(table) { updateTable(table) {
this.props.onUpdateTable(table); this.setState({
managedAccounts: table,
});
this.props.onUpdateTable([...table].map((item) => {
const newItem = Setting.deepCopy(item);
delete newItem.key;
return newItem;
}));
} }
updateField(table, index, key, value) { updateField(table, index, key, value) {
@@ -38,10 +54,12 @@ class ManagedAccountTable extends React.Component {
} }
addRow(table) { addRow(table) {
const row = {application: "", username: "", password: ""}; const row = {key: this.count, application: "", username: "", password: ""};
if (table === undefined || table === null) { if (table === undefined || table === null) {
table = []; table = [];
} }
this.count += 1;
table = Setting.addRow(table, row); table = Setting.addRow(table, row);
this.updateTable(table); this.updateTable(table);
} }
@@ -131,7 +149,7 @@ class ManagedAccountTable extends React.Component {
]; ];
return ( return (
<Table scroll={{x: "max-content"}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table scroll={{x: "max-content"}} rowKey="key" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => ( title={() => (
<div> <div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp; {this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
@@ -148,7 +166,7 @@ class ManagedAccountTable extends React.Component {
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.state.managedAccounts)
} }
</Col> </Col>
</Row> </Row>

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, InputNumber, Row, Select, Switch} from "antd"; import {Button, Card, Col, Input, InputNumber, Radio, 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";
@@ -22,6 +22,7 @@ import i18next from "i18next";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import LdapTable from "./LdapTable"; import LdapTable from "./LdapTable";
import AccountTable from "./AccountTable"; import AccountTable from "./AccountTable";
import ThemeEditor from "./common/theme/ThemeEditor";
const {Option} = Select; const {Option} = Select;
@@ -316,6 +317,31 @@ class OrganizationEditPage extends React.Component {
/> />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
</Col>
<Col span={22} style={{marginTop: "5px"}}>
<Row>
<Radio.Group value={this.state.organization.themeData?.isEnabled ?? false} onChange={e => {
const {_, ...theme} = this.state.organization.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
this.updateOrganizationField("themeData", {...theme, isEnabled: e.target.value});
}} >
<Radio.Button value={false}>{i18next.t("organization:Follow global theme")}</Radio.Button>
<Radio.Button value={true}>{i18next.t("theme:Customize theme")}</Radio.Button>
</Radio.Group>
</Row>
{
this.state.organization.themeData?.isEnabled ?
<Row style={{marginTop: "20px"}}>
<ThemeEditor themeData={this.state.organization.themeData} onThemeChange={(_, nextThemeData) => {
const {isEnabled} = this.state.organization.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
this.updateOrganizationField("themeData", {...nextThemeData, isEnabled});
}} />
</Row> : null
}
</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("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} : {Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
@@ -341,6 +367,11 @@ class OrganizationEditPage extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved")); Setting.showMessage("success", i18next.t("general:Successfully saved"));
if (this.props.account.organization.name === this.state.organizationName) {
this.props.onChangeTheme(Setting.getThemeData(this.state.organization));
}
this.setState({ this.setState({
organizationName: this.state.organization.name, organizationName: this.state.organization.name,
}); });

View File

@@ -16,6 +16,7 @@ import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Dropdown} from "antd"; import {Dropdown} from "antd";
import "./App.less"; import "./App.less";
import {GlobalOutlined} from "@ant-design/icons";
function flagIcon(country, alt) { function flagIcon(country, alt) {
return ( return (
@@ -30,6 +31,10 @@ class SelectLanguageBox extends React.Component {
classes: props, classes: props,
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"], languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
}; };
Setting.Countries.forEach((country) => {
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
});
} }
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt))); items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
@@ -50,7 +55,9 @@ class SelectLanguageBox extends React.Component {
return ( return (
<Dropdown menu={{items: languageItems, onClick}} > <Dropdown menu={{items: languageItems, onClick}} >
<div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} /> <div className="select-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} >
<GlobalOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
</div>
</Dropdown> </Dropdown>
); );
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved. // Copyright 2023 The Casdoor Authors. All Rights Reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -17,62 +17,75 @@ import * as Setting from "./Setting";
import {Dropdown} from "antd"; import {Dropdown} from "antd";
import "./App.less"; import "./App.less";
import i18next from "i18next"; import i18next from "i18next";
import {CheckOutlined} from "@ant-design/icons";
import {CompactTheme, DarkTheme, Light} from "antd-token-previewer/es/icons";
function themeIcon(themeKey) { export const Themes = [
return <img width={24} alt={themeKey} src={getLogoURL(themeKey)} />; {label: "Default", key: "default", icon: <Light style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Default")
} {label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Dark")
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Compact")
];
function getLogoURL(themeKey) { function getIcon(themeKey) {
if (themeKey) { if (themeKey?.includes("dark")) {
return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"]; return Themes.find(t => t.key === "dark").icon;
} else { } else if (themeKey?.includes("default")) {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"]; return Themes.find(t => t.key === "default").icon;
} }
} }
class SelectThemeBox extends React.Component { class SelectThemeBox extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
classes: props,
themes: props.theme ?? ["Default", "Dark", "Compact"],
icon: null,
};
} }
items = this.getThemes(); icon = getIcon(this.props.themeAlgorithm);
componentDidMount() { getThemeItems() {
i18next.on("languageChanged", () => { return Themes.map((theme) => Setting.getItem(
this.items = this.getThemes(); <div style={{display: "flex", justifyContent: "space-between"}}>
}); <div>{i18next.t(`theme:${theme.label}`)}</div>
localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")}); {this.props.themeAlgorithm.includes(theme.key) ? <CheckOutlined style={{marginLeft: "5px"}} /> : null}
addEventListener("themeChange", (e) => { </div>,
this.setState({"icon": getLogoURL()}); theme.key, theme.icon));
});
}
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() { render() {
const themeItems = this.getOrganizationThemes(this.state.themes);
const onClick = (e) => { const onClick = (e) => {
Setting.setTheme(e.key); let nextTheme;
if (e.key === "compact") {
if (this.props.themeAlgorithm.includes("compact")) {
nextTheme = this.props.themeAlgorithm.filter((theme) => theme !== "compact");
} else {
nextTheme = [...this.props.themeAlgorithm, "compact"];
}
} else {
if (!this.props.themeAlgorithm.includes(e.key)) {
if (e.key === "dark") {
nextTheme = [...this.props.themeAlgorithm.filter((theme) => theme !== "default"), e.key];
} else {
nextTheme = [...this.props.themeAlgorithm.filter((theme) => theme !== "dark"), e.key];
}
} else {
nextTheme = [...this.props.themeAlgorithm];
}
}
this.icon = getIcon(nextTheme);
this.props.onChange(nextTheme);
}; };
return ( return (
<Dropdown menu={{items: themeItems, onClick}} > <Dropdown menu={{
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} /> items: this.getThemeItems(),
onClick,
selectable: true,
multiple: true,
selectedKeys: [...this.props.themeAlgorithm],
}}>
<div className="select-box">
{this.icon}
</div>
</Dropdown> </Dropdown>
); );
} }

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, theme} from "antd"; import {Checkbox, Form, Modal, 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";
@@ -43,12 +43,43 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
{label: "Русский", key: "ru", country: "RU", alt: "Русский"}, {label: "Русский", key: "ru", country: "RU", alt: "Русский"},
]; ];
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme; export const ThemeDefault = {
themeType: "default",
colorPrimary: "#5734d3",
borderRadius: 6,
isCompact: false,
};
export const Themes = [{label: i18next.t("general:Dark"), key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`}, export function getThemeData(organization, application) {
{label: i18next.t("general:Compact"), key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`}, if (application?.themeData?.isEnabled) {
{label: i18next.t("general:Default"), key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`}, return application.themeData;
]; } else if (organization?.themeData?.isEnabled) {
return organization.themeData;
} else {
return ThemeDefault;
}
}
export function getAlgorithm(themeAlgorithmNames) {
return themeAlgorithmNames.map((algorithmName) => {
if (algorithmName === "dark") {
return theme.darkAlgorithm;
}
if (algorithmName === "compact") {
return theme.compactAlgorithm;
}
return theme.defaultAlgorithm;
});
}
export function getAlgorithmNames(themeData) {
const algorithms = [themeData?.themeType !== "dark" ? "default" : "dark"];
if (themeData?.isCompact === true) {
algorithms.push("compact");
}
return algorithms;
}
export const OtherProviderInfo = { export const OtherProviderInfo = {
SMS: { SMS: {
@@ -509,6 +540,76 @@ export function isMobile() {
return isMobileDevice; return isMobileDevice;
} }
export function getTermsOfUseContent(url, setTermsOfUseContent) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(setTermsOfUseContent);
});
}
export function isAgreementRequired(application) {
if (application) {
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
if (!agreementItem || agreementItem.rule === "None" || !agreementItem.rule) {
return false;
}
if (agreementItem.required) {
return true;
}
}
return false;
}
export function isDefaultTrue(application) {
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
return isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)";
}
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
return (
<Form.Item
name="agreement"
key="agreement"
valuePropName="checked"
rules={[
{
required: required,
message: i18next.t("signup:Please accept the agreement!"),
},
]}
{...layout}
noStyle={noStyle}
initialValue={initialValue}
>
<Checkbox style={{float: "left"}}>
{i18next.t("signup:Accept")}&nbsp;
<a onClick={onClick}>
{i18next.t("signup:Terms of Use")}
</a>
</Checkbox>
</Form.Item>
);
}
export function renderModal(isOpen, onOk, onCancel, doc) {
return (
<Modal
title={i18next.t("signup:Terms of Use")}
open={isOpen}
width={"55vw"}
closable={false}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={onOk}
onCancel={onCancel}
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
</Modal>
);
}
export function getFormattedDate(date) { export function getFormattedDate(date) {
if (date === undefined) { if (date === undefined) {
return null; return null;
@@ -598,13 +699,12 @@ export function getLanguage() {
export function setLanguage(language) { export function setLanguage(language) {
localStorage.setItem("language", language); localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language); i18next.changeLanguage(language);
} }
export function setTheme(themeKey) { export function setTheme(themeKey) {
localStorage.setItem("theme", themeKey); localStorage.setItem("theme", themeKey);
dispatchEvent(new Event("themeChange")); dispatchEvent(new Event("changeTheme"));
} }
export function getAcceptLanguage() { export function getAcceptLanguage() {
@@ -614,29 +714,6 @@ export function getAcceptLanguage() {
return i18next.language + ";q=0.9,en;q=0.8"; return i18next.language + ";q=0.9,en;q=0.8";
} }
export function changeMomentLanguage(language) {
// if (language === "zh") {
// moment.locale("zh", {
// relativeTime: {
// future: "%s内",
// past: "%s前",
// s: "几秒",
// ss: "%d秒",
// m: "1分钟",
// mm: "%d分钟",
// h: "1小时",
// hh: "%d小时",
// d: "1天",
// dd: "%d天",
// M: "1个月",
// MM: "%d个月",
// y: "1年",
// yy: "%d年",
// },
// });
// }
}
export function getClickable(text) { export function getClickable(text) {
return ( return (
<a onClick={() => { <a onClick={() => {

View File

@@ -186,6 +186,12 @@ class SignupTable extends React.Component {
{id: "Normal", name: "Normal"}, {id: "Normal", name: "Normal"},
{id: "No verification", name: "No verification"}, {id: "No verification", name: "No verification"},
]; ];
} else if (record.name === "Agreement") {
options = [
{id: "None", name: "None"},
{id: "Signin", name: "Signin"},
{id: "Signin (Default True)", name: "Signin (Default True)"},
];
} }
if (options.length === 0) { if (options.length === 0) {

View File

@@ -571,7 +571,7 @@ class UserEditPage extends React.Component {
<Col span={22} > <Col span={22} >
<ManagedAccountTable <ManagedAccountTable
title={i18next.t("user:Managed accounts")} title={i18next.t("user:Managed accounts")}
table={this.state.user.managedAccounts ?? []} table={this.state.user.managedAccounts}
onUpdateTable={(table) => {this.updateUserField("managedAccounts", table);}} onUpdateTable={(table) => {this.updateUserField("managedAccounts", table);}}
applications={this.state.applications} applications={this.state.applications}
/> />

View File

@@ -53,12 +53,16 @@ class LoginPage extends React.Component {
samlResponse: "", samlResponse: "",
relayState: "", relayState: "",
redirectUrl: "", redirectUrl: "",
isTermsOfUseVisible: false,
termsOfUseContent: "",
}; };
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) { if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner; this.state.owner = props.match?.params.owner;
this.state.applicationName = props.match?.params.casApplicationName; this.state.applicationName = props.match?.params.casApplicationName;
} }
this.form = React.createRef();
} }
componentDidMount() { componentDidMount() {
@@ -73,10 +77,6 @@ class LoginPage extends React.Component {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`); 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) {
@@ -122,7 +122,9 @@ class LoginPage extends React.Component {
this.onUpdateApplication(application); this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); }, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
}));
}); });
} else { } else {
OrganizationBackend.getDefaultApplication("admin", this.state.owner) OrganizationBackend.getDefaultApplication("admin", this.state.owner)
@@ -132,7 +134,9 @@ class LoginPage extends React.Component {
this.setState({ this.setState({
application: res.data, application: res.data,
applicationName: res.data.name, applicationName: res.data.name,
}); }, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
}));
} else { } else {
this.onUpdateApplication(null); this.onUpdateApplication(null);
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
@@ -383,6 +387,7 @@ class LoginPage extends React.Component {
onFinish={(values) => {this.onFinish(values);}} onFinish={(values) => {this.onFinish(values);}}
style={{width: "300px"}} style={{width: "300px"}}
size="large" size="large"
ref={this.form}
> >
<Form.Item <Form.Item
hidden={true} hidden={true}
@@ -456,11 +461,20 @@ class LoginPage extends React.Component {
} }
</Row> </Row>
<Form.Item> <Form.Item>
<Form.Item name="autoSignin" valuePropName="checked" noStyle> {
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}> Setting.isAgreementRequired(application) ?
{i18next.t("login:Auto sign in")} Setting.renderAgreement(true, () => {
</Checkbox> this.setState({
</Form.Item> isTermsOfUseVisible: true,
});
}, true, {}, Setting.isDefaultTrue(application)) : (
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
{i18next.t("login:Auto sign in")}
</Checkbox>
</Form.Item>
)
}
{ {
Setting.renderForgetLink(application, i18next.t("login:Forgot password?")) Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
} }
@@ -827,6 +841,19 @@ class LoginPage extends React.Component {
{ {
this.renderForm(application) this.renderForm(application)
} }
{
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
this.form.current.setFieldsValue({agreement: true});
this.setState({
isTermsOfUseVisible: false,
});
}, () => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
}, this.state.termsOfUseContent)
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,8 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Button, Form, Input, Result} from "antd";
import {Button, Checkbox, Form, Input, Modal, Result} from "antd";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton"; import * as ProviderButton from "./ProviderButton";
@@ -113,7 +112,9 @@ class SignupPage extends React.Component {
}); });
if (application !== null && application !== undefined) { if (application !== null && application !== undefined) {
this.getTermsofuseContent(application.termsOfUse); Setting.getTermsOfUseContent(application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
});
} }
}); });
} }
@@ -134,16 +135,6 @@ class SignupPage extends React.Component {
return this.props.application ?? this.state.application; return this.props.application ?? this.state.application;
} }
getTermsofuseContent(url) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(res => {
this.setState({termsOfUseContent: res});
});
});
}
onUpdateAccount(account) { onUpdateAccount(account) {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
@@ -413,7 +404,7 @@ class SignupPage extends React.Component {
style={{ style={{
width: "100%", width: "100%",
}} }}
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} addonBefore={`+${this.getApplicationObj()?.organizationObj.phonePrefix}`}
onChange={e => this.setState({phone: e.target.value})} onChange={e => this.setState({phone: e.target.value})}
/> />
</Form.Item> </Form.Item>
@@ -484,58 +475,28 @@ class SignupPage extends React.Component {
); );
} else if (signupItem.name === "Agreement") { } else if (signupItem.name === "Agreement") {
return ( return (
<Form.Item Setting.renderAgreement(Setting.isAgreementRequired(application), () => {
name="agreement" this.setState({
key="agreement" isTermsOfUseVisible: true,
valuePropName="checked" });
rules={[ }, false, tailFormItemLayout, Setting.isDefaultTrue(application))
{
required: required,
message: i18next.t("signup:Please accept the agreement!"),
},
]}
{...tailFormItemLayout}
>
<Checkbox>
{i18next.t("signup:Accept")}&nbsp;
<Link onClick={() => {
this.setState({
isTermsOfUseVisible: true,
});
}}>
{i18next.t("signup:Terms of Use")}
</Link>
</Checkbox>
</Form.Item>
); );
} }
} }
renderModal() { renderModal() {
return ( return (
<Modal Setting.renderModal(this.state.isTermsOfUseVisible, () => {
title={i18next.t("signup:Terms of Use")} this.form.current.setFieldsValue({agreement: true});
open={this.state.isTermsOfUseVisible} this.setState({
width={"55vw"} isTermsOfUseVisible: false,
closable={false} });
okText={i18next.t("signup:Accept")} }, () => {
cancelText={i18next.t("signup:Decline")} this.form.current.setFieldsValue({agreement: false});
onOk={() => { this.setState({
this.form.current.setFieldsValue({agreement: true}); isTermsOfUseVisible: false,
this.setState({ });
isTermsOfUseVisible: false, }, this.state.termsOfUseContent)
});
}}
onCancel={() => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
this.props.history.goBack();
}}
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent} />
</Modal>
); );
} }

View File

@@ -71,8 +71,6 @@ export function deleteAdapter(Adapter) {
} }
export function UpdatePolicy(owner, name, policy) { export function UpdatePolicy(owner, name, policy) {
// eslint-disable-next-line no-console
console.log(policy);
return fetch(`${Setting.ServerUrl}/api/update-policy?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-policy?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",

View File

@@ -28,9 +28,18 @@ class PolicyTable extends React.Component {
editingIndex: "", editingIndex: "",
oldPolicy: "", oldPolicy: "",
add: false, add: false,
page: 1,
}; };
} }
count = 0;
pageSize = 10;
getIndex(index) {
// Need to be used in all place when modify table. Parameter is the row index in table, need to calculate the index in dataSource.
return index + (this.state.page - 1) * 10;
}
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
if (this.props.mode === "edit") { if (this.props.mode === "edit") {
this.synPolicies(); this.synPolicies();
@@ -46,8 +55,8 @@ class PolicyTable extends React.Component {
}; };
cancel = (table, index) => { cancel = (table, index) => {
Object.keys(table[index]).forEach((key) => { Object.keys(table[this.getIndex(index)]).forEach((key) => {
table[index][key] = this.state.oldPolicy[key]; table[this.getIndex(index)][key] = this.state.oldPolicy[key];
}); });
this.updateTable(table); this.updateTable(table);
this.setState({editingIndex: "", oldPolicy: ""}); this.setState({editingIndex: "", oldPolicy: ""});
@@ -62,23 +71,28 @@ class PolicyTable extends React.Component {
} }
updateField(table, index, key, value) { updateField(table, index, key, value) {
table[index][key] = value; table[this.getIndex(index)][key] = value;
this.updateTable(table); this.updateTable(table);
} }
addRow(table) { addRow(table) {
const row = {Ptype: "p"}; const row = {key: this.count, Ptype: "p"};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
table = Setting.addRow(table, row, "top"); table = Setting.addRow(table, row, "top");
this.count = this.count + 1;
this.updateTable(table); this.updateTable(table);
this.edit(row, 0); this.edit(row, 0);
this.setState({add: true}); this.setState({
page: 1,
add: true,
});
} }
deleteRow(table, i) { deleteRow(table, index) {
table = Setting.deleteRow(table, i); table = Setting.deleteRow(table, this.getIndex(index));
this.updateTable(table); this.updateTable(table);
} }
@@ -91,8 +105,14 @@ 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.data});
Setting.showMessage("success", i18next.t("adapter:Sync policies successfully")); Setting.showMessage("success", i18next.t("adapter:Sync policies successfully"));
const policyList = res.data;
policyList.map((policy, index) => {
policy.key = index;
});
this.count = policyList.length;
this.setState({policyLists: policyList});
} 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}`);
} }
@@ -129,12 +149,12 @@ class PolicyTable extends React.Component {
}); });
} }
deletePolicy(table, i) { deletePolicy(table, index) {
AdapterBackend.RemovePolicy(this.props.owner, this.props.name, table[i]).then(res => { AdapterBackend.RemovePolicy(this.props.owner, this.props.name, table[this.getIndex(index)]).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
table = Setting.deleteRow(table, i);
this.updateTable(table);
Setting.showMessage("success", i18next.t("general:Successfully deleted")); Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.deleteRow(table, index);
} else { } else {
Setting.showMessage("error", i18next.t("general:Failed to delete")); Setting.showMessage("error", i18next.t("general:Failed to delete"));
} }
@@ -279,9 +299,14 @@ class PolicyTable extends React.Component {
return ( return (
<Table <Table
pagination={{ pagination={{
defaultPageSize: 10, defaultPageSize: this.pageSize,
onChange: (page) => this.setState({
page: page,
}),
disabled: this.state.editingIndex !== "",
current: this.state.page,
}} }}
columns={columns} dataSource={table} rowKey="index" size="middle" bordered columns={columns} dataSource={table} rowKey="key" size="middle" bordered
loading={this.state.loading} loading={this.state.loading}
title={() => ( title={() => (
<div> <div>

View File

@@ -0,0 +1,173 @@
// Copyright 2023 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.
/** @jsxImportSource @emotion/react */
import {Input, Popover, Space, theme} from "antd";
import React, {useEffect, useMemo, useState} from "react";
import {css} from "@emotion/react";
import {TinyColor} from "@ctrl/tinycolor";
import ColorPanel from "antd-token-previewer/es/ColorPanel";
export const BLUE_COLOR = "#1677FF";
export const PINK_COLOR = "#ED4192";
export const GREEN_COLOR = "#00B96B";
export const COLORS = [
{
color: BLUE_COLOR,
},
{
color: "#5734d3",
},
{
color: "#9E339F",
},
{
color: PINK_COLOR,
},
{
color: "#E0282E",
},
{
color: "#F4801A",
},
{
color: "#F2BD27",
},
{
color: GREEN_COLOR,
},
];
export const PRESET_COLORS = COLORS.map(({color}) => color);
const {useToken} = theme;
const useStyle = () => {
const {token} = useToken();
return {
color: css `
width: ${token.controlHeightLG / 2}px;
height: ${token.controlHeightLG / 2}px;
border-radius: 100%;
cursor: pointer;
transition: all ${token.motionDurationFast};
display: inline-block;
& > input[type="radio"] {
width: 0;
height: 0;
opacity: 0;
}
`,
colorActive: css `
box-shadow: 0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
`,
};
};
const DebouncedColorPanel = ({color, onChange}) => {
const [value, setValue] = useState(color);
useEffect(() => {
const timeout = setTimeout(() => {
onChange?.(value);
}, 200);
return () => clearTimeout(timeout);
}, [value]);
useEffect(() => {
setValue(color);
}, [color]);
return <ColorPanel color={value} onChange={setValue} />;
};
export default function ColorPicker({value, onChange}) {
const style = useStyle();
const matchColors = useMemo(() => {
const valueStr = new TinyColor(value).toRgbString();
let existActive = false;
const colors = PRESET_COLORS.map((color) => {
const colorStr = new TinyColor(color).toRgbString();
const active = colorStr === valueStr;
existActive = existActive || active;
return {color, active, picker: false};
});
return [
...colors,
{
color: "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",
picker: true,
active: !existActive,
},
];
}, [value]);
return (
<Space size="large">
<Input
value={value}
onChange={(event) => {
onChange?.(event.target.value);
}}
style={{width: 120}}
/>
<Space size="middle">
{matchColors.map(({color, active, picker}) => {
let colorNode = (
<label
key={color}
css={[style.color, active && style.colorActive]}
style={{
background: color,
}}
onClick={() => {
if (!picker) {
onChange?.(color);
}
}}
>
<input type="radio" name={picker ? "picker" : "color"} tabIndex={picker ? -1 : 0} />
</label>
);
if (picker) {
colorNode = (
<Popover
key={color}
overlayInnerStyle={{padding: 0}}
content={
<DebouncedColorPanel color={value || ""} onChange={(c) => onChange?.(c)} />
}
trigger="click"
showArrow={false}
>
{colorNode}
</Popover>
);
}
return colorNode;
})}
</Space>
</Space>
);
}

View File

@@ -0,0 +1,38 @@
// Copyright 2023 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 {InputNumber, Slider, Space} from "antd";
export default function RadiusPicker({value, onChange}) {
return (
<Space size="large">
<InputNumber
value={value}
onChange={onChange}
style={{width: 120}}
min={0}
formatter={(val) => `${val}px`}
parser={(str) => (str ? parseFloat(str) : str)}
/>
<Slider
tooltip={{open: false}}
style={{width: 128}}
min={0}
value={value}
max={20}
onChange={onChange}
/>
</Space>
);
}

View File

@@ -0,0 +1,108 @@
// Copyright 2023 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 {Card, ConfigProvider, Form, Layout, Switch, theme} from "antd";
import ThemePicker from "./ThemePicker";
import ColorPicker from "./ColorPicker";
import RadiusPicker from "./RadiusPicker";
import * as React from "react";
import {GREEN_COLOR, PINK_COLOR} from "./ColorPicker";
import {Content} from "antd/es/layout/layout";
import i18next from "i18next";
import {useEffect} from "react";
import * as Setting from "../../Setting";
const ThemesInfo = {
default: {},
dark: {
borderRadius: 2,
},
lark: {
colorPrimary: GREEN_COLOR,
borderRadius: 4,
},
comic: {
colorPrimary: PINK_COLOR,
borderRadius: 16,
},
};
const onChange = () => {};
export default function ThemeEditor(props) {
const themeData = props.themeData ?? Setting.ThemeDefault;
const onThemeChange = props.onThemeChange ?? onChange;
const {isCompact, themeType, ...themeToken} = themeData;
const isLight = themeType !== "dark";
const [form] = Form.useForm();
const algorithmFn = React.useMemo(() => {
const algorithms = [isLight ? theme.defaultAlgorithm : theme.darkAlgorithm];
if (isCompact === true) {
algorithms.push(theme.compactAlgorithm);
}
return algorithms;
}, [isLight, isCompact]);
useEffect(() => {
const mergedData = Object.assign(Object.assign(Object.assign({}, Setting.ThemeDefault), {themeType}), ThemesInfo[themeType]);
onThemeChange(null, mergedData);
form.setFieldsValue(mergedData);
}, [themeType]);
return (
<ConfigProvider
theme={{
token: {
...themeToken,
},
hashed: true,
algorithm: algorithmFn,
}}
>
<Layout style={{width: "800px", backgroundColor: "white"}}>
<Content >
<Card
title={i18next.t("theme:Theme")}
>
<Form
form={form}
initialValues={themeData}
onValuesChange={onThemeChange}
labelCol={{span: 4}}
wrapperCol={{span: 20}}
style={{width: "800px"}}
>
<Form.Item label={i18next.t("theme:Theme")} name="themeType">
<ThemePicker />
</Form.Item>
<Form.Item label={i18next.t("theme:Primary color")} name="colorPrimary">
<ColorPicker />
</Form.Item>
<Form.Item label={i18next.t("theme:Border radius")} name="borderRadius">
<RadiusPicker />
</Form.Item>
<Form.Item label={i18next.t("theme:Is compact")} valuePropName="checked" name="isCompact">
<Switch />
</Form.Item>
</Form>
</Card>
</Content>
</Layout>
</ConfigProvider>
);
}

View File

@@ -0,0 +1,104 @@
// Copyright 2023 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.
/** @jsxImportSource @emotion/react */
import {css} from "@emotion/react";
import {Space, theme} from "antd";
import * as React from "react";
import i18next from "i18next";
import * as Setting from "../../Setting";
const {useToken} = theme;
export const THEMES = {
default: `${Setting.StaticBaseUrl}/img/theme_default.svg`,
dark: `${Setting.StaticBaseUrl}/img/theme_dark.svg`,
lark: `${Setting.StaticBaseUrl}/img/theme_lark.svg`,
comic: `${Setting.StaticBaseUrl}/img/theme_comic.svg`,
};
Object.values(THEMES).map(value => new Image().src = value);
const themeTypes = {
default: "Default", // i18next.t("theme:Default")
dark: "Dark", // i18next.t("theme:Dark")
lark: "Document", // i18next.t("theme:Document")
comic: "Blossom", // i18next.t("theme:Blossom")
};
const useStyle = () => {
const {token} = useToken();
return {
themeCard: css `
border-radius: ${token.borderRadius}px;
cursor: pointer;
transition: all ${token.motionDurationSlow};
overflow: hidden;
display: inline-block;
& > input[type="radio"] {
width: 0;
height: 0;
opacity: 0;
}
img {
vertical-align: top;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
}
&:focus-within,
&:hover {
transform: scale(1.04);
}
`,
themeCardActive: css `
box-shadow: 0 0 0 1px ${token.colorBgContainer},
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
&:hover:not(:focus-within) {
transform: scale(1);
}
`,
};
};
export default function ThemePicker({value, onChange}) {
const {token} = useToken();
const style = useStyle();
return (
<Space size={token.paddingLG}>
{Object.keys(THEMES).map((theme) => {
const url = THEMES[theme];
return (
<Space key={theme} direction="vertical" align="center">
<label
css={[style.themeCard, value === theme && style.themeCardActive]}
onClick={() => {
onChange?.(theme);
}}
>
<input type="radio" name="theme" />
<img src={url} alt={theme} />
</label>
<span>{i18next.t(`theme:${themeTypes[theme]}`)}</span>
</Space>
);
})}
</Space>
);
}

View File

@@ -22,7 +22,6 @@ import ru from "./locales/ru/data.json";
import ja from "./locales/ja/data.json"; import ja from "./locales/ja/data.json";
import es from "./locales/es/data.json"; import es from "./locales/es/data.json";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import * as Setting from "./Setting";
import {initReactI18next} from "react-i18next"; import {initReactI18next} from "react-i18next";
const resources = { const resources = {
@@ -76,7 +75,6 @@ function initLanguage() {
} }
} }
} }
Setting.changeMomentLanguage(language);
return language; return language;
} }

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Anmelden",
"Logout": "Abmelden", "Logout": "Abmelden",
"My Account": "Mein Konto", "My Account": "Mein Konto",
"Sign Up": "Registrieren" "Sign Up": "Registrieren"
@@ -41,6 +40,7 @@
"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", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "Datei erfolgreich hochgeladen", "File uploaded successfully": "Datei erfolgreich hochgeladen",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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",
@@ -341,6 +338,7 @@
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
"Edit Organization": "Organisation bearbeiten", "Edit Organization": "Organisation bearbeiten",
"Favicon": "Févicon", "Favicon": "Févicon",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Zugangs-Token", "Access token": "Zugangs-Token",
"Authorization code": "Autorisierungscode", "Authorization code": "Autorisierungscode",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Login",
"Logout": "Logout", "Logout": "Logout",
"My Account": "My Account", "My Account": "My Account",
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
@@ -41,6 +40,7 @@
"Enable signup - Tooltip": "Enable signup - Tooltip", "Enable signup - Tooltip": "Enable signup - Tooltip",
"Failed to sign in": "Failed to sign in", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully", "File uploaded successfully": "File uploaded successfully",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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",
@@ -341,6 +338,7 @@
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",
"Favicon": "Favicon", "Favicon": "Favicon",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Access token", "Access token": "Access token",
"Authorization code": "Authorization code", "Authorization code": "Authorization code",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Iniciar sesión",
"Logout": "Cerrar sesión", "Logout": "Cerrar sesión",
"My Account": "Mi cuenta", "My Account": "Mi cuenta",
"Sign Up": "Registrarme" "Sign Up": "Registrarme"
@@ -41,6 +40,7 @@
"Enable signup - Tooltip": "Habilitar nuevos registros - Tooltip", "Enable signup - Tooltip": "Habilitar nuevos registros - Tooltip",
"Failed to sign in": "Failed to sign in", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "El archivo ha sido subido con éxito", "File uploaded successfully": "El archivo ha sido subido con éxito",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"Click to Upload": "Click para subir archivo", "Click to Upload": "Click para subir archivo",
"Client IP": "IP del Cliente", "Client IP": "IP del Cliente",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Fecha de creación", "Created time": "Fecha de creación",
"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 por defecto", "Default avatar": "Avatar por defecto",
@@ -341,6 +338,7 @@
"Default avatar": "Avatar por defecto", "Default avatar": "Avatar por defecto",
"Edit Organization": "Editar Organización", "Edit Organization": "Editar Organización",
"Favicon": "Favicon", "Favicon": "Favicon",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "InitScore": "InitScore",
"Is profile public": "Es el perfil publico", "Is profile public": "Es el perfil publico",
"Is profile public - Tooltip": "Es el perfil publico - Tooltip", "Is profile public - Tooltip": "Es el perfil publico - Tooltip",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Token de acceso", "Access token": "Token de acceso",
"Authorization code": "Código de autorización", "Authorization code": "Código de autorización",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Se connecter",
"Logout": "Déconnexion", "Logout": "Déconnexion",
"My Account": "Mon Compte", "My Account": "Mon Compte",
"Sign Up": "S'inscrire" "Sign Up": "S'inscrire"
@@ -41,6 +40,7 @@
"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", "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",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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",
@@ -341,6 +338,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",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Jeton d'accès", "Access token": "Jeton d'accès",
"Authorization code": "Code d'autorisation", "Authorization code": "Code d'autorisation",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "ログイン",
"Logout": "ログアウト", "Logout": "ログアウト",
"My Account": "マイアカウント", "My Account": "マイアカウント",
"Sign Up": "新規登録" "Sign Up": "新規登録"
@@ -41,6 +40,7 @@
"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", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "ファイルが正常にアップロードされました", "File uploaded successfully": "ファイルが正常にアップロードされました",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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": "デフォルトのアバター",
@@ -341,6 +338,7 @@
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
"Edit Organization": "組織を編集", "Edit Organization": "組織を編集",
"Favicon": "ファビコン", "Favicon": "ファビコン",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "アクセストークン", "Access token": "アクセストークン",
"Authorization code": "認証コード", "Authorization code": "認証コード",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Login",
"Logout": "Logout", "Logout": "Logout",
"My Account": "My Account", "My Account": "My Account",
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
@@ -41,6 +40,7 @@
"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", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "File uploaded successfully", "File uploaded successfully": "File uploaded successfully",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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",
@@ -341,6 +338,7 @@
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
"Edit Organization": "Edit Organization", "Edit Organization": "Edit Organization",
"Favicon": "Favicon", "Favicon": "Favicon",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Access token", "Access token": "Access token",
"Authorization code": "Authorization code", "Authorization code": "Authorization code",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "Логин",
"Logout": "Выйти", "Logout": "Выйти",
"My Account": "Мой аккаунт", "My Account": "Мой аккаунт",
"Sign Up": "Регистрация" "Sign Up": "Регистрация"
@@ -41,6 +40,7 @@
"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", "Failed to sign in": "Failed to sign in",
"File uploaded successfully": "Файл успешно загружен", "File uploaded successfully": "Файл успешно загружен",
"Follow organization theme": "Follow organization theme",
"Form CSS": "Form CSS", "Form CSS": "Form CSS",
"Form CSS - Edit": "Form CSS - Edit", "Form CSS - Edit": "Form CSS - Edit",
"Form CSS - Tooltip": "Form CSS - Tooltip", "Form CSS - Tooltip": "Form CSS - Tooltip",
@@ -158,10 +158,7 @@
"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": "Аватар по умолчанию",
@@ -341,6 +338,7 @@
"Default avatar": "Аватар по умолчанию", "Default avatar": "Аватар по умолчанию",
"Edit Organization": "Изменить организацию", "Edit Organization": "Изменить организацию",
"Favicon": "Иконка", "Favicon": "Иконка",
"Follow global theme": "Follow global theme",
"InitScore": "InitScore", "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",
@@ -690,6 +688,19 @@
"Unknown Version": "Unknown Version", "Unknown Version": "Unknown Version",
"Version": "Version" "Version": "Version"
}, },
"theme": {
"Blossom": "Blossom",
"Border radius": "Border radius",
"Compact": "Compact",
"Customize theme": "Customize theme",
"Dark": "Dark",
"Default": "Default",
"Document": "Document",
"Is compact": "Is compact",
"Primary color": "Primary color",
"Theme": "Theme",
"Theme - Tooltip": "Theme - Tooltip"
},
"token": { "token": {
"Access token": "Маркер доступа", "Access token": "Маркер доступа",
"Authorization code": "Код авторизации", "Authorization code": "Код авторизации",

View File

@@ -1,6 +1,5 @@
{ {
"account": { "account": {
"Login": "登录",
"Logout": "登出", "Logout": "登出",
"My Account": "我的账户", "My Account": "我的账户",
"Sign Up": "注册" "Sign Up": "注册"
@@ -41,6 +40,7 @@
"Enable signup - Tooltip": "是否允许用户注册", "Enable signup - Tooltip": "是否允许用户注册",
"Failed to sign in": "登录失败", "Failed to sign in": "登录失败",
"File uploaded successfully": "文件上传成功", "File uploaded successfully": "文件上传成功",
"Follow organization theme": "使用组织主题",
"Form CSS": "表单CSS", "Form CSS": "表单CSS",
"Form CSS - Edit": "编辑表单CSS", "Form CSS - Edit": "编辑表单CSS",
"Form CSS - Tooltip": "表单的CSS样式如增加边框和阴影", "Form CSS - Tooltip": "表单的CSS样式如增加边框和阴影",
@@ -158,10 +158,7 @@
"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": "默认头像",
@@ -341,6 +338,7 @@
"Default avatar": "默认头像", "Default avatar": "默认头像",
"Edit Organization": "编辑组织", "Edit Organization": "编辑组织",
"Favicon": "图标", "Favicon": "图标",
"Follow global theme": "使用全局默认主题",
"InitScore": "初始积分", "InitScore": "初始积分",
"Is profile public": "用户个人页公开", "Is profile public": "用户个人页公开",
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页", "Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
@@ -690,6 +688,19 @@
"Unknown Version": "未知版本", "Unknown Version": "未知版本",
"Version": "版本" "Version": "版本"
}, },
"theme": {
"Blossom": "桃花缘",
"Border radius": "圆角",
"Compact": "紧凑",
"Customize theme": "定制主题",
"Dark": "暗黑",
"Default": "默认",
"Document": "知识协作",
"Is compact": "宽松度",
"Primary color": "主色",
"Theme": "主题",
"Theme - Tooltip": "为你的应用设置主题"
},
"token": { "token": {
"Access token": "访问令牌", "Access token": "访问令牌",
"Authorization code": "授权码", "Authorization code": "授权码",

View File

@@ -23,18 +23,19 @@ class PropertyTable extends React.Component {
super(props); super(props);
this.state = { this.state = {
properties: [], properties: [],
count: this.props.properties !== null ? Object.entries(this.props.properties).length : 0,
}; };
// transfer the Object to object[] // transfer the Object to object[]
if (this.props.properties !== null) { if (this.props.properties !== null) {
Object.entries(this.props.properties).map((item, index) => { Object.entries(this.props.properties).map((item, index) => {
this.state.properties.push({key: index, name: item[0], value: item[1]}); this.state.properties.push({key: index, name: item[0], value: item[1]});
}); });
} }
} }
page = 1; page = 1;
pageSize = 10;
count = this.props.properties !== null ? Object.entries(this.props.properties).length : 0;
updateTable(table) { updateTable(table) {
this.setState({properties: table}); this.setState({properties: table});
@@ -46,12 +47,12 @@ class PropertyTable extends React.Component {
} }
addRow(table) { addRow(table) {
const row = {key: this.state.count, name: "", value: ""}; const row = {key: this.count, name: "", value: ""};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
table = Setting.addRow(table, row); table = Setting.addRow(table, row);
this.setState({count: this.state.count + 1}); this.count = this.count + 1;
this.updateTable(table); this.updateTable(table);
} }
@@ -61,8 +62,8 @@ class PropertyTable extends React.Component {
} }
getIndex(index) { getIndex(index) {
// Parameter is the row index in table, need to calculate the index in dataSource. 10 is the pageSize. // Need to be used in all place when modify table. Parameter is the row index in table, need to calculate the index in dataSource.
return index + (this.page - 1) * 10; return index + (this.page - 1) * this.pageSize;
} }
updateField(table, index, key, value) { updateField(table, index, key, value) {
@@ -114,7 +115,10 @@ class PropertyTable extends React.Component {
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button> <Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
pagination={{onChange: page => {this.page = page;}}} pagination={{
defaultPageSize: this.pageSize,
onChange: page => {this.page = page;},
}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered columns={columns} dataSource={table} rowKey="key" size="middle" bordered
/> />
); );

File diff suppressed because it is too large Load Diff