Compare commits

...

31 Commits

Author SHA1 Message Date
Mikey
2fec3f72ae fix: check reset phone & email modify rules (#927)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-30 18:17:13 +08:00
Yang Luo
11695220a8 Use user.GetId() 2022-07-30 17:40:30 +08:00
Resulte Lee
155660b0d7 feat: get user api return roles and permissions (#929) 2022-07-30 17:31:56 +08:00
imp2002
1c72f5300c feat: fix 'Enable code sign' is not displayed in the login page (#925) 2022-07-28 23:11:33 +08:00
q1anx1
3dd56195d9 fix: fix the problem of link error (#923) 2022-07-28 21:52:10 +08:00
Resulte Lee
8865244262 fix: add oauth login auto close page (#915) 2022-07-26 23:03:55 +08:00
Yixiang Zhao
3400fa1e9c feat: support local login for non-built-in users (#911)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-26 19:27:24 +08:00
Resulte Lee
bdc5c92ef0 fix: send code missing parameter & show more detail responseError (#910) 2022-07-25 23:46:38 +08:00
SLingyu
4e3eedf246 feat: fix bug that the default permission prevents admin to login in (#907)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name

* fix: Default action prevents admin to login in
2022-07-24 23:36:55 +08:00
SLingyu
8e98fc5a9f feat: rename all publicKey occurrences to certificate (#894)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name
2022-07-23 09:40:51 +08:00
leoshine
6f6159be07 feat: add GET method of logout API (#903) 2022-07-22 21:13:49 +08:00
Gucheng Wang
3e4dbc2dcb fix: URL bug in getUploadFileUrl function 2022-07-20 17:49:11 +08:00
Yixiang Zhao
48b5b27982 fix: invalid redirect url after sign up (#896)
* fix: invalid redirect url after sign up

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update App.js

* Update Setting.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-19 23:31:17 +08:00
q1anx1
1839252c30 chore(web): sort import members (#895) 2022-07-18 20:57:38 +08:00
q1anx1
1fff1db6a7 fix(web): fix the bug of infinity loop animate when unauthorized (#891)
* fix(web): fix the bug of infinity loop when unauthorized

* fix

* fix

* fix

* Update BaseListPage.js

* Update OrganizationListPage.js

* Update OrganizationListPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-17 18:20:52 +08:00
Yang Luo
a0b0e186b7 Improve i18n code and data. 2022-07-17 17:56:43 +08:00
Yang Luo
8c7f235ee1 Fix bug in uploadFile()'s URL. 2022-07-17 14:29:06 +08:00
waltcow
a0a762aa6f fix: typo in field tag in BilibiliUserInfo (#890) 2022-07-17 11:31:43 +08:00
Yixiang Zhao
2eec53a6d0 fix: actions initialized to null and model/resources not updated with the owner (#887)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 15:00:42 +08:00
Yixiang Zhao
117dec4542 feat: failed to sync keycloak users in the PostgreSQL database (#886)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 12:14:35 +08:00
waltcow
895cdd024d fix: Typo in user model xorm tag (#883) 2022-07-15 12:01:27 +08:00
q1anx1
f0b0891ac9 feat: query user by userId (#879)
* feat: add `getUserByUserId` func

* Update user.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-14 21:46:13 +08:00
Yang Luo
10449e89ab Fix owner bug in GetUser(). 2022-07-13 22:56:35 +08:00
Gucheng Wang
6e70f0fc58 Refactor CheckAccessPermission(). 2022-07-13 00:50:32 +08:00
Yixiang Zhao
2bca424370 feat: implement access control using casbin (#806)
* feat: implement access control using casbin

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* chore: sort imports

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: remove

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* Update auth.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-13 00:34:35 +08:00
Gucheng Wang
de49a45e19 Add escapePath for getUploadFileUrl(). 2022-07-12 23:24:24 +08:00
Gucheng Wang
f7243f879b Fix some JS warnings. 2022-07-12 20:47:11 +08:00
Товарищ программист
7f3b2500b3 feat: support webauthn (#407)
* feat: support webauthn

* Update init.go

* Update user_webauthn.go

* Update UserEditPage.js

* Update WebauthnCredentialTable.js

* Update LoginPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-12 20:06:01 +08:00
Bingchang Chen
208dc11d25 fix: set SessionOn always true (#877)
* fix: set SessionOn always true

* Update adapter.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-11 12:36:20 +08:00
キリサメ qianxi
503d244166 feat(web): add lint (#875)
* feat: add lint

* feat: fix lint error

* chore: add ignore file

* chore: close indent
2022-07-10 15:45:55 +08:00
Gucheng Wang
475b6da35a Rename session storage item to signinUrl. 2022-07-10 11:50:48 +08:00
173 changed files with 18527 additions and 17014 deletions

View File

@@ -78,6 +78,7 @@ p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, * p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, * p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, * p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, GET, /api/get-account, *, * p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, * p, *, *, GET, /api/userinfo, *, *
p, *, *, *, /api/login/oauth, *, * p, *, *, *, /api/login/oauth, *, *
@@ -92,6 +93,7 @@ p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, * p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, * p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, * p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/notify-payment, *, *
p, *, *, POST, /api/unlink, *, * p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, * p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, * p, *, *, POST, /api/send-verification-code, *, *
@@ -105,6 +107,7 @@ p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, * p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, * p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, * p, *, *, *, /cas, *, *
p, *, *, *, /api/webauthn, *, *
` `
sa := stringadapter.NewAdapter(ruleText) sa := stringadapter.NewAdapter(ruleText)

View File

@@ -1,7 +1,6 @@
appname = casdoor appname = casdoor
httpport = 8000 httpport = 8000
runmode = dev runmode = dev
SessionOn = true
copyrequestbody = true copyrequestbody = true
driverName = mysql driverName = mysql
dataSourceName = root:123456@tcp(localhost:3306)/ dataSourceName = root:123456@tcp(localhost:3306)/

View File

@@ -79,13 +79,9 @@ func TestGetConfBool(t *testing.T) {
input string input string
expected interface{} expected interface{}
}{ }{
{"Should be return false", "SessionOn", false},
{"Should be return false", "copyrequestbody", true}, {"Should be return false", "copyrequestbody", true},
} }
//do some set up job
os.Setenv("SessionOn", "false")
err := beego.LoadAppConfig("ini", "app.conf") err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err) assert.Nil(t, err)
for _, scenery := range scenarios { for _, scenery := range scenarios {

View File

@@ -217,7 +217,7 @@ func (c *ApiController) Signup() {
record.User = user.Name record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) }) util.SafeGoroutine(func() { object.AddRecord(record) })
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name) userId := user.GetId()
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId) util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId) c.ResponseOk(userId)
@@ -228,7 +228,7 @@ func (c *ApiController) Signup() {
// @Tag Login API // @Tag Login API
// @Description logout the current user // @Description logout the current user
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /logout [post] // @router /logout [get,post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)

View File

@@ -50,6 +50,17 @@ func tokenToResponse(token *object.Token) *Response {
// HandleLoggedIn ... // HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) { func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
userId := user.GetId() userId := user.GetId()
allowed, err := object.CheckAccessPermission(userId, application)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
if !allowed {
c.ResponseError("Unauthorized operation")
return
}
if form.Type == ResponseTypeLogin { if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId) c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId) util.LogInfo(c.Ctx, "API: [%s] signed in", userId)

View File

@@ -85,11 +85,15 @@ func (c *ApiController) GetUsers() {
// @router /get-user [get] // @router /get-user [get]
func (c *ApiController) GetUser() { func (c *ApiController) GetUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email") email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id) userId := c.Input().Get("userId")
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
owner := c.Input().Get("owner")
if owner == "" {
owner, _ = util.GetOwnerAndNameFromId(id)
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
if !organization.IsProfilePublic { if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false) hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
@@ -100,12 +104,19 @@ func (c *ApiController) GetUser() {
} }
var user *object.User var user *object.User
if email == "" { if email != "" {
user = object.GetUser(id)
} else {
user = object.GetUserByEmail(owner, email) user = object.GetUserByEmail(owner, email)
} else if userId != "" {
user = object.GetUserByUserId(owner, userId)
} else {
user = object.GetUser(id)
} }
roles := object.GetRolesByUser(user.GetId())
user.Roles = roles
permissions := object.GetPermissionsByUser(user.GetId())
user.Permissions = permissions
c.Data["json"] = object.GetMaskedUser(user) c.Data["json"] = object.GetMaskedUser(user)
c.ServeJSON() c.ServeJSON()
} }

View File

@@ -49,8 +49,24 @@ func (c *ApiController) SendVerificationCode() {
applicationId := c.Ctx.Request.Form.Get("applicationId") applicationId := c.Ctx.Request.Form.Get("applicationId")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" || dest == "" || applicationId == "" || !strings.Contains(applicationId, "/") || checkType == "" { if destType == "" {
c.ResponseError("Missing parameter.") c.ResponseError("Missing parameter: type.")
return
}
if dest == "" {
c.ResponseError("Missing parameter: dest.")
return
}
if applicationId == "" {
c.ResponseError("Missing parameter: applicationId.")
return
}
if !strings.Contains(applicationId, "/") {
c.ResponseError("Wrong parameter: applicationId.")
return
}
if checkType == "" {
c.ResponseError("Missing parameter: checkType.")
return return
} }
@@ -152,13 +168,35 @@ func (c *ApiController) ResetEmailOrPhone() {
} }
checkDest := dest checkDest := dest
org := object.GetOrganizationByUser(user)
if destType == "phone" { if destType == "phone" {
org := object.GetOrganizationByUser(user) phoneItem := object.GetAccountItemByName("Phone", org)
if phoneItem == nil {
c.ResponseError("Unable to get the phone modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user); !pass {
c.ResponseError(errMsg)
return
}
phonePrefix := "86" phonePrefix := "86"
if org != nil && org.PhonePrefix != "" { if org != nil && org.PhonePrefix != "" {
phonePrefix = org.PhonePrefix phonePrefix = org.PhonePrefix
} }
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest) checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" {
emailItem := object.GetAccountItemByName("Email", org)
if emailItem == nil {
c.ResponseError("Unable to get the email modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user); !pass {
c.ResponseError(errMsg)
return
}
} }
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 { if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
c.ResponseError(ret) c.ResponseError(ret)

138
controllers/webauthn.go Normal file
View File

@@ -0,0 +1,138 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"bytes"
"io/ioutil"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
// @Title WebAuthnSignupBegin
// @Tag User API
// @Description WebAuthn Registration Flow 1st stage
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
// @router /webauthn/signup/begin [get]
func (c *ApiController) WebAuthnSignupBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
}
options, sessionData, err := webauthnObj.BeginRegistration(
user,
registerOptions,
)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("registration", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// @Title WebAuthnSignupFinish
// @Tag User API
// @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
sessionObj := c.GetSession("registration")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSignupBegin first")
return
}
c.Ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
credential, err := webauthnObj.FinishRegistration(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
isGlobalAdmin := c.IsGlobalAdmin()
user.AddCredentials(*credential, isGlobalAdmin)
c.ResponseOk()
}
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 1st stage
// @Param owner query string true "owner"
// @Param name query string true "name"
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
// @router /webauthn/signin/begin [get]
func (c *ApiController) WebAuthnSigninBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError("Please Giveout Owner and Username.")
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("authentication", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
sessionObj := c.GetSession("authentication")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSigninBegin first")
return
}
c.Ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
userId := string(sessionData.UserID)
user := object.GetUser(userId)
_, err := webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
c.ResponseOk(userId)
}

1
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/casdoor/goth v1.69.0-FIX2 github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0 github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72

14
go.sum
View File

@@ -111,6 +111,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
@@ -124,6 +126,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b h1:L63RATZFZuFMXy6ixnKmv3eNAXwYQF6HW1vd4IYsQqQ=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
@@ -135,6 +139,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@@ -164,6 +170,7 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -201,6 +208,8 @@ github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNu
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -298,6 +307,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -397,6 +408,8 @@ github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnD
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA= github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4= github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -413,6 +426,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

View File

@@ -39,6 +39,7 @@ func readI18nFile(language string) *I18nData {
func writeI18nFile(language string, data *I18nData) { func writeI18nFile(language string, data *I18nData) {
s := util.StructToJsonFormatted(data) s := util.StructToJsonFormatted(data)
s = strings.ReplaceAll(s, "\\u0026", "&") s = strings.ReplaceAll(s, "\\u0026", "&")
s += "\n"
println(s) println(s)
util.WriteStringToPath(s, getI18nFilePath(language)) util.WriteStringToPath(s, getI18nFilePath(language))

View File

@@ -144,7 +144,7 @@ func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
type BilibiliUserInfo struct { type BilibiliUserInfo struct {
Name string `json:"name"` Name string `json:"name"`
Face string `json:"face"` Face string `json:"face"`
OpenId string `json:"openid` OpenId string `json:"openid"`
} }
type BilibiliUserInfoResponse struct { type BilibiliUserInfoResponse struct {

View File

@@ -139,7 +139,7 @@
"cryptoAlgorithm": "RS256", "cryptoAlgorithm": "RS256",
"bitSize": 4096, "bitSize": 4096,
"expireInYears": 20, "expireInYears": 20,
"publicKey": "", "certificate": "",
"privateKey": "" "privateKey": ""
} }
], ],

View File

@@ -56,6 +56,7 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage) beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionOn = true
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id" beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if conf.GetConfigString("redisEndpoint") == "" { if conf.GetConfigString("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file" beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

@@ -22,7 +22,7 @@ import (
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
//_ "github.com/denisenkom/go-mssqldb" // db = mssql //_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql _ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres //_ "github.com/lib/pq" // db = postgres
"xorm.io/core" "xorm.io/core"
"xorm.io/xorm" "xorm.io/xorm"
@@ -36,11 +36,12 @@ func InitConfig() {
panic(err) panic(err)
} }
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true) InitAdapter(true)
} }
func InitAdapter(createDatabase bool) { func InitAdapter(createDatabase bool) {
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName")) adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase { if createDatabase {
adapter.CreateDatabase() adapter.CreateDatabase()
@@ -202,6 +203,11 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(PermissionRule))
if err != nil {
panic(err)
}
} }
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session { func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {

View File

@@ -47,6 +47,7 @@ type Application struct {
EnableSigninSession bool `json:"enableSigninSession"` EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"` EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"` EnableSamlCompress bool `json:"enableSamlCompress"`
EnableWebAuthn bool `json:"enableWebAuthn"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"` Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`

View File

@@ -33,7 +33,7 @@ type Cert struct {
BitSize int `json:"bitSize"` BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"` ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"` Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"` PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"` AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"` AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
@@ -123,9 +123,9 @@ func UpdateCert(id string, cert *Cert) bool {
} }
func AddCert(cert *Cert) bool { func AddCert(cert *Cert) bool {
if cert.PublicKey == "" || cert.PrivateKey == "" { if cert.Certificate == "" || cert.PrivateKey == "" {
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner) certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.PublicKey = publicKey cert.Certificate = certificate
cert.PrivateKey = privateKey cert.PrivateKey = privateKey
} }

View File

@@ -229,4 +229,30 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
} }
return hasPermission, fmt.Errorf("you don't have the permission to do this") return hasPermission, fmt.Errorf("you don't have the permission to do this")
} }
func CheckAccessPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled || len(permission.Users) == 0 {
continue
}
isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
}
}
if isHit {
enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read")
break
}
}
return allowed, err
}

View File

@@ -15,20 +15,25 @@
package object package object
import ( import (
"encoding/gob"
"io/ioutil" "io/ioutil"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
) )
func InitDb() { func InitDb() {
existed := initBuiltInOrganization() existed := initBuiltInOrganization()
if !existed { if !existed {
initBuiltInPermission()
initBuiltInProvider() initBuiltInProvider()
initBuiltInUser() initBuiltInUser()
initBuiltInApplication() initBuiltInApplication()
initBuiltInCert() initBuiltInCert()
initBuiltInLdap() initBuiltInLdap()
} }
initWebAuthn()
} }
func initBuiltInOrganization() bool { func initBuiltInOrganization() bool {
@@ -66,12 +71,15 @@ func initBuiltInOrganization() bool {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
}, },
} }
AddOrganization(organization) AddOrganization(organization)
@@ -162,7 +170,7 @@ func readTokenFromFile() (string, string) {
} }
func initBuiltInCert() { func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile() tokenJwtCertificate, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in") cert := getCert("admin", "cert-built-in")
if cert != nil { if cert != nil {
return return
@@ -178,7 +186,7 @@ func initBuiltInCert() {
CryptoAlgorithm: "RS256", CryptoAlgorithm: "RS256",
BitSize: 4096, BitSize: 4096,
ExpireInYears: 20, ExpireInYears: 20,
PublicKey: tokenJwtPublicKey, Certificate: tokenJwtCertificate,
PrivateKey: tokenJwtPrivateKey, PrivateKey: tokenJwtPrivateKey,
} }
AddCert(cert) AddCert(cert)
@@ -221,3 +229,29 @@ func initBuiltInProvider() {
} }
AddProvider(provider) AddProvider(provider)
} }
func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}
func initBuiltInPermission() {
permission := GetPermission("built-in/permission-built-in")
if permission != nil {
return
}
permission = &Permission{
Owner: "built-in",
Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission",
Users: []string{"built-in/admin"},
Roles: []string{},
ResourceType: "Application",
Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow",
IsEnabled: true,
}
AddPermission(permission)
}

View File

@@ -89,6 +89,8 @@ func initDefinedOrganization(organization *Organization) {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"}, {Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, {Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},

View File

@@ -97,7 +97,7 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html //link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key //or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
for _, cert := range certs { for _, cert := range certs {
certPemBlock := []byte(cert.PublicKey) certPemBlock := []byte(cert.Certificate)
certDerBlock, _ := pem.Decode(certPemBlock) certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes) x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)

View File

@@ -15,6 +15,8 @@
package object package object
import ( import (
"fmt"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@@ -186,3 +188,31 @@ func DeleteOrganization(organization *Organization) bool {
func GetOrganizationByUser(user *User) *Organization { func GetOrganizationByUser(user *User) *Organization {
return getOrganization("admin", user.Owner) return getOrganization("admin", user.Owner)
} }
func GetAccountItemByName(name string, organization *Organization) *AccountItem {
if organization == nil {
return nil
}
for _, accountItem := range organization.AccountItems {
if accountItem.Name == name {
return accountItem
}
}
return nil
}
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, string) {
switch accountItem.ModifyRule {
case "Admin":
if !(user.IsAdmin || user.IsGlobalAdmin) {
return false, fmt.Sprintf("Only admin can modify the %s.", accountItem.Name)
}
case "Immutable":
return false, fmt.Sprintf("The %s is immutable.", accountItem.Name)
case "Self":
break
default:
return false, fmt.Sprintf("Unknown modify rule %s.", accountItem.ModifyRule)
}
return true, ""
}

View File

@@ -16,7 +16,12 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@@ -39,6 +44,16 @@ type Permission struct {
IsEnabled bool `json:"isEnabled"` IsEnabled bool `json:"isEnabled"`
} }
type PermissionRule struct {
PType string `xorm:"varchar(100) index not null default ''"`
V0 string `xorm:"varchar(100) index not null default ''"`
V1 string `xorm:"varchar(100) index not null default ''"`
V2 string `xorm:"varchar(100) index not null default ''"`
V3 string `xorm:"varchar(100) index not null default ''"`
V4 string `xorm:"varchar(100) index not null default ''"`
V5 string `xorm:"varchar(100) index not null default ''"`
}
func GetPermissionCount(owner, field, value string) int { func GetPermissionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "") session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Permission{}) count, err := session.Count(&Permission{})
@@ -95,7 +110,8 @@ func GetPermission(id string) *Permission {
func UpdatePermission(id string, permission *Permission) bool { func UpdatePermission(id string, permission *Permission) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getPermission(owner, name) == nil { oldPermission := getPermission(owner, name)
if oldPermission == nil {
return false return false
} }
@@ -104,6 +120,11 @@ func UpdatePermission(id string, permission *Permission) bool {
panic(err) panic(err)
} }
if affected != 0 {
removePolicies(oldPermission)
addPolicies(permission)
}
return affected != 0 return affected != 0
} }
@@ -113,6 +134,10 @@ func AddPermission(permission *Permission) bool {
panic(err) panic(err)
} }
if affected != 0 {
addPolicies(permission)
}
return affected != 0 return affected != 0
} }
@@ -122,9 +147,95 @@ func DeletePermission(permission *Permission) bool {
panic(err) panic(err)
} }
if affected != 0 {
removePolicies(permission)
}
return affected != 0 return affected != 0
} }
func (permission *Permission) GetId() string { func (permission *Permission) GetId() string {
return fmt.Sprintf("%s/%s", permission.Owner, permission.Name) return fmt.Sprintf("%s/%s", permission.Owner, permission.Name)
} }
func getEnforcer(permission *Permission) *casbin.Enforcer {
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "permission_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = permission, sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`
permissionModel := getModel(permission.Owner, permission.Model)
if permissionModel != nil {
modelText = permissionModel.ModelText
}
m, err := model.NewModelFromString(modelText)
if err != nil {
panic(err)
}
enforcer, err := casbin.NewEnforcer(m, adapter)
if err != nil {
panic(err)
}
err = enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}})
if err != nil {
panic(err)
}
return enforcer
}
func getPolicies(permission *Permission) [][]string {
var policies [][]string
for _, user := range permission.Users {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
policies = append(policies, []string{permission.GetId(), user, resource, strings.ToLower(action)})
}
}
}
return policies
}
func addPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies)
if err != nil {
panic(err)
}
}
func removePolicies(permission *Permission) {
enforcer := getEnforcer(permission)
_, err := enforcer.RemoveFilteredPolicy(0, permission.GetId())
if err != nil {
panic(err)
}
}
func GetPermissionsByUser(userId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}

View File

@@ -30,7 +30,7 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123") product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay") provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay") cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey) pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentName := util.GenerateTimeId() paymentName := util.GenerateTimeId()
returnUrl := "" returnUrl := ""

View File

@@ -214,7 +214,7 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
} }
} }
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey) pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil { if pProvider == nil {
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type) return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
} }

View File

@@ -121,3 +121,13 @@ func DeleteRole(role *Role) bool {
func (role *Role) GetId() string { func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name) return fmt.Sprintf("%s/%s", role.Owner, role.Name)
} }
func GetRolesByUser(userId string) []*Role {
roles := []*Role{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&roles)
if err != nil {
panic(err)
}
return roles
}

View File

@@ -36,7 +36,7 @@ import (
) )
//returns a saml2 response //returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) { func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{ samlResponse := &etree.Element{
Space: "samlp", Space: "samlp",
Tag: "Response", Tag: "Response",
@@ -177,8 +177,8 @@ type Attribute struct {
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) { func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
//_, originBackend := getOriginFromHost(host) //_, originBackend := getOriginFromHost(host)
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin") origin := beego.AppConfig.String("origin")
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
@@ -199,7 +199,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
KeyInfo: KeyInfo{ KeyInfo: KeyInfo{
X509Data: X509Data{ X509Data: X509Data{
X509Certificate: X509Certificate{ X509Certificate: X509Certificate{
Cert: publicKey, Cert: certificate,
}, },
}, },
}, },
@@ -248,18 +248,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
return "", "", fmt.Errorf("err: invalid issuer url") return "", "", fmt.Errorf("err: invalid issuer url")
} }
// get public key string // get certificate string
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
// build signedResponse // build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris) samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey, PrivateKey: cert.PrivateKey,
X509Certificate: publicKey, X509Certificate: certificate,
} }
ctx := dsig.NewDefaultSigningContext(randomKeyStore) ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1 ctx.Hash = crypto.SHA1

View File

@@ -17,6 +17,7 @@ package object
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/url"
"strings" "strings"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@@ -42,8 +43,19 @@ func getProviderEndpoint(provider *Provider) string {
return endpoint return endpoint
} }
func escapePath(path string) string {
tokens := strings.Split(path, "/")
if len(tokens) > 0 {
tokens[len(tokens)-1] = url.QueryEscape(tokens[len(tokens)-1])
}
res := strings.Join(tokens, "/")
return res
}
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) { func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath) escapedPath := escapePath(fullFilePath)
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
host := "" host := ""
if provider.Type != "Local File System" { if provider.Type != "Local File System" {
@@ -60,9 +72,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = fmt.Sprintf("%s/%s", host, provider.Bucket) host = fmt.Sprintf("%s/%s", host, provider.Bucket)
} }
fileUrl := util.UrlJoin(host, objectKey) fileUrl := util.UrlJoin(host, escapePath(objectKey))
if hasTimestamp { if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime()) fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
} }
return fileUrl, objectKey return fileUrl, objectKey

View File

@@ -151,6 +151,8 @@ func (syncer *Syncer) initAdapter() {
var dataSourceName string var dataSourceName string
if syncer.DatabaseType == "mssql" { if syncer.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database) dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else if syncer.DatabaseType == "postgres" {
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else { } else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port) dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
} }

View File

@@ -173,16 +173,21 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
} }
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
tableColumnName := tableColumn.Name
if syncer.Type == "Keycloak" && syncer.DatabaseType == "postgres" {
tableColumnName = strings.ToLower(tableColumnName)
}
value := "" value := ""
if strings.Contains(tableColumn.Name, "+") { if strings.Contains(tableColumnName, "+") {
names := strings.Split(tableColumn.Name, "+") names := strings.Split(tableColumnName, "+")
var values []string var values []string
for _, name := range names { for _, name := range names {
values = append(values, result[strings.Trim(name, " ")]) values = append(values, result[strings.Trim(name, " ")])
} }
value = strings.Join(values, " ") value = strings.Join(values, " ")
} else { } else {
value = result[tableColumn.Name] value = result[tableColumnName]
} }
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value) syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
} }
@@ -198,7 +203,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
originalUser.PasswordSalt = credential.Salt originalUser.PasswordSalt = credential.Salt
} }
// query and set signup application from user group table // query and set signup application from user group table
sql = fmt.Sprintf("select name from keycloak_group where id = " + sql = fmt.Sprintf("select name from keycloak_group where id = "+
"(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id) "(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id)
groupResult, _ := syncer.Adapter.Engine.QueryString(sql) groupResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(groupResult) > 0 { if len(groupResult) > 0 {
@@ -209,7 +214,12 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
tm := time.Unix(i/int64(1000), 0) tm := time.Unix(i/int64(1000), 0)
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00") originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
// enable // enable
originalUser.IsForbidden = !(result["ENABLED"] == "\x01") value, ok := result["ENABLED"]
if ok {
originalUser.IsForbidden = !util.ParseBool(value)
} else {
originalUser.IsForbidden = !util.ParseBool(result["enabled"])
}
} }
users = append(users, originalUser) users = append(users, originalUser)

View File

@@ -241,11 +241,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
samlResponse := NewSamlResponse11(user, request.RequestID, host) samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application) cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey)) block, _ := pem.Decode([]byte(cert.Certificate))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes) certificate := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey, PrivateKey: cert.PrivateKey,
X509Certificate: publicKey, X509Certificate: certificate,
} }
ctx := dsig.NewDefaultSigningContext(randomKeyStore) ctx := dsig.NewDefaultSigningContext(randomKeyStore)

View File

@@ -129,13 +129,13 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
} }
// RSA public key // RSA certificate
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey)) certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return publicKey, nil return certificate, nil
}) })
if t != nil { if t != nil {

View File

@@ -23,10 +23,10 @@ import (
func TestGenerateRsaKeys(t *testing.T) { func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key" fileId := "token_jwt_key"
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization") certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
// Write certificate (aka public key) to file. // Write certificate (aka certificate) to file.
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId)) util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file. // Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId)) util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))

View File

@@ -20,6 +20,7 @@ import (
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
"xorm.io/core" "xorm.io/core"
) )
@@ -96,11 +97,16 @@ type User struct {
Steam string `xorm:"steam varchar(100)" json:"steam"` Steam string `xorm:"steam varchar(100)" json:"steam"`
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"` Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"` Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin vachar(100)" json:"douyin"` Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"` Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
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 `json:"roles"`
Permissions []*Permission `json:"permissions"`
} }
type Userinfo struct { type Userinfo struct {
@@ -267,6 +273,24 @@ func GetUserByEmail(owner string, email string) *User {
} }
} }
func GetUserByUserId(owner string, userId string) *User {
if owner == "" || userId == "" {
return nil
}
user := User{Owner: owner, Id: userId}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User { func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name) return getUser(owner, name)
@@ -326,9 +350,11 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
} }
if len(columns) == 0 { if len(columns) == 0 {
columns = []string{"owner", "display_name", "avatar", columns = []string{
"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application", "location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"} "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials",
}
} }
if isGlobalAdmin { if isGlobalAdmin {
columns = append(columns, "name", "email", "phone") columns = append(columns, "name", "email", "phone")
@@ -395,10 +421,10 @@ func AddUsers(users []*User) bool {
return false return false
} }
//organization := GetOrganizationByUser(users[0]) // organization := GetOrganizationByUser(users[0])
for _, user := range users { for _, user := range users {
// this function is only used for syncer or batch upload, so no need to encrypt the password // this function is only used for syncer or batch upload, so no need to encrypt the password
//user.UpdateUserPassword(organization) // user.UpdateUserPassword(organization)
user.UpdateUserHash() user.UpdateUserHash()
user.PreHash = user.Hash user.PreHash = user.Hash

View File

@@ -106,6 +106,10 @@ func setUserProperty(user *User, field string, value string) {
if value == "" { if value == "" {
delete(user.Properties, field) delete(user.Properties, field)
} else { } else {
if user.Properties == nil {
user.Properties = make(map[string]string)
}
user.Properties[field] = value user.Properties[field] = value
} }
} }

102
object/user_webauthn.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2022 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"encoding/base64"
"net/url"
"strings"
"github.com/astaxie/beego"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
func GetWebAuthnObject(host string) *webauthn.WebAuthn {
var err error
origin := beego.AppConfig.String("origin")
if origin == "" {
_, origin = getOriginFromHost(host)
}
localUrl, err := url.Parse(origin)
if err != nil {
panic("error when parsing origin:" + err.Error())
}
webAuthn, err := webauthn.New(&webauthn.Config{
RPDisplayName: beego.AppConfig.String("appname"), // Display Name for your site
RPID: strings.Split(localUrl.Host, ":")[0], // Generally the domain name for your site, it's ok because splits cannot return empty array
RPOrigin: origin, // The origin URL for WebAuthn requests
// RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site
})
if err != nil {
panic(err)
}
return webAuthn
}
// implementation of webauthn.User interface
func (u *User) WebAuthnID() []byte {
return []byte(u.GetId())
}
func (u *User) WebAuthnName() string {
return u.Name
}
func (u *User) WebAuthnDisplayName() string {
return u.DisplayName
}
func (u *User) WebAuthnCredentials() []webauthn.Credential {
return u.WebauthnCredentials
}
func (u *User) WebAuthnIcon() string {
return u.Avatar
}
// CredentialExcludeList returns a CredentialDescriptor array filled with all the user's credentials
func (u *User) CredentialExcludeList() []protocol.CredentialDescriptor {
credentials := u.WebAuthnCredentials()
credentialExcludeList := []protocol.CredentialDescriptor{}
for _, cred := range credentials {
descriptor := protocol.CredentialDescriptor{
Type: protocol.PublicKeyCredentialType,
CredentialID: cred.ID,
}
credentialExcludeList = append(credentialExcludeList, descriptor)
}
return credentialExcludeList
}
func (u *User) AddCredentials(credential webauthn.Credential, isGlobalAdmin bool) bool {
u.WebauthnCredentials = append(u.WebauthnCredentials, credential)
return UpdateUser(u.GetId(), u, []string{"webauthnCredentials"}, isGlobalAdmin)
}
func (u *User) DeleteCredentials(credentialIdBase64 string) bool {
for i, credential := range u.WebauthnCredentials {
if base64.StdEncoding.EncodeToString(credential.ID) == credentialIdBase64 {
u.WebauthnCredentials = append(u.WebauthnCredentials[0:i], u.WebauthnCredentials[i+1:]...)
return UpdateUserForAllFields(u.GetId(), u)
}
}
return false
}

View File

@@ -28,7 +28,7 @@ type AlipayPaymentProvider struct {
Client *alipay.Client Client *alipay.Client
} }
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider { func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
pp := &AlipayPaymentProvider{} pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true) client, err := alipay.NewClient(appId, appPrivateKey, true)
@@ -36,7 +36,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
panic(err) panic(err)
} }
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey)) err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -22,9 +22,9 @@ type PaymentProvider interface {
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
} }
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider { func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
if typ == "Alipay" { if typ == "Alipay" {
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey) return NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
} else if typ == "GC" { } else if typ == "GC" {
return NewGcPaymentProvider(appId, clientSecret, host) return NewGcPaymentProvider(appId, clientSecret, host)
} }

View File

@@ -109,6 +109,10 @@ func getUrlPath(urlPath string) string {
return "/api/login/oauth" return "/api/login/oauth"
} }
if strings.HasPrefix(urlPath, "/api/webauthn") {
return "/api/webauthn"
}
return urlPath return urlPath
} }
@@ -118,6 +122,10 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path) urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx) objOwner, objName := getObject(ctx)
if strings.HasPrefix(urlPath, "/api/notify-payment") {
urlPath = "/api/notify-payment"
}
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName) isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
result := "deny" result := "deny"

View File

@@ -48,7 +48,7 @@ func initAPI() {
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup") beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login") beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin") beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout") beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout")
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount") beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo") beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink") beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
@@ -191,4 +191,9 @@ func initAPI() {
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate") beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate") beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin")
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish")
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "Get:WebAuthnSigninBegin")
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
} }

View File

@@ -2418,6 +2418,21 @@
} }
}, },
"/api/logout": { "/api/logout": {
"get": {
"tags": [
"Login API"
],
"description": "logout the current user",
"operationId": "ApiController.Logout",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
},
"post": { "post": {
"tags": [ "tags": [
"Login API" "Login API"
@@ -3096,14 +3111,120 @@
], ],
"operationId": "ApiController.VerifyCaptcha" "operationId": "ApiController.VerifyCaptcha"
} }
},
"/api/webauthn/signin/begin": {
"get": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 1st stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "owner",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "name",
"description": "name",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The CredentialAssertion object",
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertion"
}
}
}
}
},
"/api/webauthn/signin/finish": {
"post": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 2nd stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator assertion Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertionResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
},
"/api/webauthn/signup/begin": {
"get": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 1st stage",
"operationId": "ApiController.WebAuthnSignupBegin",
"responses": {
"200": {
"description": "The CredentialCreationOptions object",
"schema": {
"$ref": "#/definitions/protocol.CredentialCreation"
}
}
}
}
},
"/api/webauthn/signup/finish": {
"post": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 2nd stage",
"operationId": "ApiController.WebAuthnSignupFinish",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator attestation Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialCreationResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
"2127.0xc000398090.false": { "2127.0xc000427560.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2161.0xc0003980c0.false": { "2161.0xc000427590.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@@ -3221,10 +3342,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2127.0xc000398090.false" "$ref": "#/definitions/2127.0xc000427560.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2161.0xc0003980c0.false" "$ref": "#/definitions/2161.0xc000427590.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -3329,12 +3450,18 @@
"enablePassword": { "enablePassword": {
"type": "boolean" "type": "boolean"
}, },
"enableSamlCompress": {
"type": "boolean"
},
"enableSignUp": { "enableSignUp": {
"type": "boolean" "type": "boolean"
}, },
"enableSigninSession": { "enableSigninSession": {
"type": "boolean" "type": "boolean"
}, },
"enableWebAuthn": {
"type": "boolean"
},
"expireInHours": { "expireInHours": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
@@ -3444,7 +3571,7 @@
"privateKey": { "privateKey": {
"type": "string" "type": "string"
}, },
"publicKey": { "certificate": {
"type": "string" "type": "string"
}, },
"scope": { "scope": {
@@ -4507,6 +4634,12 @@
"updatedTime": { "updatedTime": {
"type": "string" "type": "string"
}, },
"webauthnCredentials": {
"type": "array",
"items": {
"$ref": "#/definitions/webauthn.Credential"
}
},
"wechat": { "wechat": {
"type": "string" "type": "string"
}, },
@@ -4596,6 +4729,26 @@
} }
} }
}, },
"protocol.CredentialAssertion": {
"title": "CredentialAssertion",
"type": "object"
},
"protocol.CredentialAssertionResponse": {
"title": "CredentialAssertionResponse",
"type": "object"
},
"protocol.CredentialCreation": {
"title": "CredentialCreation",
"type": "object"
},
"protocol.CredentialCreationResponse": {
"title": "CredentialCreationResponse",
"type": "object"
},
"webauthn.Credential": {
"title": "Credential",
"type": "object"
},
"xorm.Engine": { "xorm.Engine": {
"title": "Engine", "title": "Engine",
"type": "object" "type": "object"

View File

@@ -1584,6 +1584,16 @@ paths:
schema: schema:
$ref: '#/definitions/object.TokenError' $ref: '#/definitions/object.TokenError'
/api/logout: /api/logout:
get:
tags:
- Login API
description: logout the current user
operationId: ApiController.Logout
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
post: post:
tags: tags:
- Login API - Login API
@@ -2028,11 +2038,80 @@ paths:
tags: tags:
- Verification API - Verification API
operationId: ApiController.VerifyCaptcha operationId: ApiController.VerifyCaptcha
/api/webauthn/signin/begin:
get:
tags:
- Login API
description: WebAuthn Login Flow 1st stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: query
name: owner
description: owner
required: true
type: string
- in: query
name: name
description: name
required: true
type: string
responses:
"200":
description: The CredentialAssertion object
schema:
$ref: '#/definitions/protocol.CredentialAssertion'
/api/webauthn/signin/finish:
post:
tags:
- Login API
description: WebAuthn Login Flow 2nd stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: body
name: body
description: authenticator assertion Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialAssertionResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
/api/webauthn/signup/begin:
get:
tags:
- User API
description: WebAuthn Registration Flow 1st stage
operationId: ApiController.WebAuthnSignupBegin
responses:
"200":
description: The CredentialCreationOptions object
schema:
$ref: '#/definitions/protocol.CredentialCreation'
/api/webauthn/signup/finish:
post:
tags:
- User API
description: WebAuthn Registration Flow 2nd stage
operationId: ApiController.WebAuthnSignupFinish
parameters:
- in: body
name: body
description: authenticator attestation Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialCreationResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
definitions: definitions:
2127.0xc000398090.false: 2127.0xc000427560.false:
title: "false" title: "false"
type: object type: object
2161.0xc0003980c0.false: 2161.0xc000427590.false:
title: "false" title: "false"
type: object type: object
Response: Response:
@@ -2113,9 +2192,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2127.0xc000398090.false' $ref: '#/definitions/2127.0xc000427560.false'
data2: data2:
$ref: '#/definitions/2161.0xc0003980c0.false' $ref: '#/definitions/2161.0xc000427590.false'
msg: msg:
type: string type: string
name: name:
@@ -2185,10 +2264,14 @@ definitions:
type: boolean type: boolean
enablePassword: enablePassword:
type: boolean type: boolean
enableSamlCompress:
type: boolean
enableSignUp: enableSignUp:
type: boolean type: boolean
enableSigninSession: enableSigninSession:
type: boolean type: boolean
enableWebAuthn:
type: boolean
expireInHours: expireInHours:
type: integer type: integer
format: int64 format: int64
@@ -2263,7 +2346,7 @@ definitions:
type: string type: string
privateKey: privateKey:
type: string type: string
publicKey: certificate:
type: string type: string
scope: scope:
type: string type: string
@@ -2977,6 +3060,10 @@ definitions:
type: string type: string
updatedTime: updatedTime:
type: string type: string
webauthnCredentials:
type: array
items:
$ref: '#/definitions/webauthn.Credential'
wechat: wechat:
type: string type: string
wecom: wecom:
@@ -3035,6 +3122,21 @@ definitions:
type: string type: string
url: url:
type: string type: string
protocol.CredentialAssertion:
title: CredentialAssertion
type: object
protocol.CredentialAssertionResponse:
title: CredentialAssertionResponse
type: object
protocol.CredentialCreation:
title: CredentialCreation
type: object
protocol.CredentialCreationResponse:
title: CredentialCreationResponse
type: object
webauthn.Credential:
title: Credential
type: object
xorm.Engine: xorm.Engine:
title: Engine title: Engine
type: object type: object

View File

@@ -52,8 +52,10 @@ func ParseFloat(s string) float64 {
} }
func ParseBool(s string) bool { func ParseBool(s string) bool {
if s == "\x01" { if s == "\x01" || s == "true" {
return true return true
} else if s == "false" {
return false
} }
i := ParseInt(s) i := ParseInt(s)

2
web/.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
build

63
web/.eslintrc Normal file
View File

@@ -0,0 +1,63 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"settings": {
"react": {
"version": "detect"
}
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"rules": {
// "eqeqeq": "error",
"semi": ["error", "always"],
// "indent": ["error", 2],
// follow antd's style guide
"quotes": ["error", "double"],
"jsx-quotes": ["error", "prefer-double"],
"space-in-parens": ["error", "never"],
"object-curly-spacing": ["error", "never"],
"array-bracket-spacing": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
"react/jsx-curly-spacing": [
"error",
{ "when": "never", "allowMultiline": true, "children": true }
],
"arrow-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": ["error", "always"],
"spaced-comment": ["error", "always"],
"react/jsx-tag-spacing": ["error", { "beforeSelfClosing": "always" }],
"block-spacing": ["error", "never"],
"space-before-function-paren": ["error", "never"],
"no-trailing-spaces": ["error", { "ignoreComments": true }],
"eol-last": ["error", "always"],
// "no-var": ["error"],
"curly": ["error", "all"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"no-mixed-spaces-and-tabs": "error",
"sort-imports": ["error", {
"ignoreDeclarationSort": true
}],
"react/prop-types": "off",
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
// don't use strict mod now, otherwise there are a lot of errors in the codebase
"no-unused-vars": "warn",
"react/no-deprecated": "warn",
"no-case-declarations": "warn",
"react/jsx-key": "warn"
}
}

View File

@@ -1,38 +1,38 @@
const CracoLessPlugin = require('craco-less'); const CracoLessPlugin = require("craco-less");
module.exports = { module.exports = {
devServer: { devServer: {
proxy: { proxy: {
'/api': { "/api": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/swagger': { "/swagger": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/files': { "/files": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/.well-known/openid-configuration': { "/.well-known/openid-configuration": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/cas/serviceValidate': { "/cas/serviceValidate": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/cas/proxyValidate': { "/cas/proxyValidate": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/cas/proxy': { "/cas/proxy": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
}, },
'/cas/validate': { "/cas/validate": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
} }
}, },
@@ -43,7 +43,7 @@ module.exports = {
options: { options: {
lessLoaderOptions: { lessLoaderOptions: {
lessOptions: { lessOptions: {
modifyVars: {'@primary-color': 'rgb(45,120,213)'}, modifyVars: {"@primary-color": "rgb(45,120,213)"},
javascriptEnabled: true, javascriptEnabled: true,
}, },
}, },

View File

@@ -59,6 +59,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3" "cross-env": "^7.0.3",
"eslint": "^7.11.0",
"eslint-plugin-react": "^7.30.1"
} }
} }

View File

@@ -13,12 +13,12 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd'; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
const { Option } = Select; const {Option} = Select;
class AccountTable extends React.Component { class AccountTable extends React.Component {
constructor(props) { constructor(props) {
@@ -65,8 +65,8 @@ class AccountTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("provider:Name"), title: i18next.t("provider:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
render: (text, record, index) => { render: (text, record, index) => {
const items = [ const items = [
{name: "Organization", displayName: i18next.t("general:Organization")}, {name: "Organization", displayName: i18next.t("general:Organization")},
@@ -86,12 +86,15 @@ class AccountTable extends React.Component {
{name: "Bio", displayName: i18next.t("user:Bio")}, {name: "Bio", displayName: i18next.t("user:Bio")},
{name: "Tag", displayName: i18next.t("user:Tag")}, {name: "Tag", displayName: i18next.t("user:Tag")},
{name: "Signup application", displayName: i18next.t("general:Signup application")}, {name: "Signup application", displayName: i18next.t("general:Signup application")},
{name: "Roles", displayName: i18next.t("general:Roles")},
{name: "Permissions", displayName: i18next.t("general:Permissions")},
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")}, {name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
{name: "Properties", displayName: i18next.t("user:Properties")}, {name: "Properties", displayName: i18next.t("user:Properties")},
{name: "Is admin", displayName: i18next.t("user:Is admin")}, {name: "Is admin", displayName: i18next.t("user:Is admin")},
{name: "Is global admin", displayName: i18next.t("user:Is global admin")}, {name: "Is global admin", displayName: i18next.t("user:Is global admin")},
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")}, {name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},
{name: "Is deleted", displayName: i18next.t("user:Is deleted")}, {name: "Is deleted", displayName: i18next.t("user:Is deleted")},
{name: "WebAuthn credentials", displayName: i18next.t("user:WebAuthn credentials")},
]; ];
const getItemDisplayName = (text) => { const getItemDisplayName = (text) => {
@@ -103,63 +106,63 @@ class AccountTable extends React.Component {
}; };
return ( return (
<Select virtual={false} style={{width: '100%'}} <Select virtual={false} style={{width: "100%"}}
value={getItemDisplayName(text)} value={getItemDisplayName(text)}
onChange={value => { onChange={value => {
this.updateField(table, index, 'name', value); this.updateField(table, index, "name", value);
}} > }} >
{ {
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>) Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("provider:visible"),
dataIndex: 'visible', dataIndex: "visible",
key: 'visible', key: "visible",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'visible', checked); this.updateField(table, index, "visible", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("organization:viewRule"), title: i18next.t("organization:viewRule"),
dataIndex: 'viewRule', dataIndex: "viewRule",
key: 'viewRule', key: "viewRule",
width: '155px', width: "155px",
render: (text, record, index) => { render: (text, record, index) => {
if (!record.visible) { if (!record.visible) {
return null; return null;
} }
let options = [ let options = [
{id: 'Public', name: 'Public'}, {id: "Public", name: "Public"},
{id: 'Self', name: 'Self'}, {id: "Self", name: "Self"},
{id: 'Admin', name: 'Admin'}, {id: "Admin", name: "Admin"},
]; ];
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
this.updateField(table, index, 'viewRule', value); this.updateField(table, index, "viewRule", value);
})}> })}>
{ {
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("organization:modifyRule"), title: i18next.t("organization:modifyRule"),
dataIndex: 'modifyRule', dataIndex: "modifyRule",
key: 'modifyRule', key: "modifyRule",
width: '155px', width: "155px",
render: (text, record, index) => { render: (text, record, index) => {
if (!record.visible) { if (!record.visible) {
return null; return null;
@@ -168,32 +171,32 @@ class AccountTable extends React.Component {
let options; let options;
if (record.viewRule === "Admin") { if (record.viewRule === "Admin") {
options = [ options = [
{id: 'Admin', name: 'Admin'}, {id: "Admin", name: "Admin"},
{id: 'Immutable', name: 'Immutable'}, {id: "Immutable", name: "Immutable"},
]; ];
} else { } else {
options = [ options = [
{id: 'Self', name: 'Self'}, {id: "Self", name: "Self"},
{id: 'Admin', name: 'Admin'}, {id: "Admin", name: "Admin"},
{id: 'Immutable', name: 'Immutable'}, {id: "Immutable", name: "Immutable"},
]; ];
} }
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
this.updateField(table, index, 'modifyRule', value); this.updateField(table, index, "modifyRule", value);
})}> })}>
{ {
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -213,13 +216,13 @@ class AccountTable 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="name" 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;
<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>
)} )}
/> />
); );
} }
@@ -227,7 +230,7 @@ class AccountTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -235,7 +238,7 @@ class AccountTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import React, {Component} from 'react'; import React, {Component} from "react";
import './App.less'; import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons'; import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from 'antd'; import {Avatar, BackTop, Button, Card, Dropdown, 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";
import UserListPage from "./UserListPage"; import UserListPage from "./UserListPage";
@@ -63,16 +63,16 @@ import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage"; import ForgetPage from "./auth/ForgetPage";
import * as AuthBackend from "./auth/AuthBackend"; import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from './SelectLanguageBox'; import SelectLanguageBox from "./SelectLanguageBox";
import i18next from 'i18next'; import i18next from "i18next";
import PromptPage from "./auth/PromptPage"; import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from './auth/SamlCallback'; import SamlCallback from "./auth/SamlCallback";
import CasLogout from "./auth/CasLogout"; import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage"; import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage"; import ModelEditPage from "./ModelEditPage";
const { Header, Footer } = Layout; const {Header, Footer} = Layout;
class App extends Component { class App extends Component {
constructor(props) { constructor(props) {
@@ -110,46 +110,46 @@ class App extends Component {
this.setState({ this.setState({
uri: uri, uri: uri,
}); });
if (uri === '/') { if (uri === "/") {
this.setState({ selectedMenuKey: '/' }); this.setState({selectedMenuKey: "/"});
} else if (uri.includes('/organizations')) { } else if (uri.includes("/organizations")) {
this.setState({ selectedMenuKey: '/organizations' }); this.setState({selectedMenuKey: "/organizations"});
} else if (uri.includes('/users')) { } else if (uri.includes("/users")) {
this.setState({ selectedMenuKey: '/users' }); this.setState({selectedMenuKey: "/users"});
} else if (uri.includes('/roles')) { } else if (uri.includes("/roles")) {
this.setState({ selectedMenuKey: '/roles' }); this.setState({selectedMenuKey: "/roles"});
} else if (uri.includes('/permissions')) { } else if (uri.includes("/permissions")) {
this.setState({ selectedMenuKey: '/permissions' }); this.setState({selectedMenuKey: "/permissions"});
} else if (uri.includes('/models')) { } else if (uri.includes("/models")) {
this.setState({ selectedMenuKey: '/models' }); this.setState({selectedMenuKey: "/models"});
} else if (uri.includes('/providers')) { } else if (uri.includes("/providers")) {
this.setState({ selectedMenuKey: '/providers' }); this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes('/applications')) { } else if (uri.includes("/applications")) {
this.setState({ selectedMenuKey: '/applications' }); this.setState({selectedMenuKey: "/applications"});
} else if (uri.includes('/resources')) { } else if (uri.includes("/resources")) {
this.setState({ selectedMenuKey: '/resources' }); this.setState({selectedMenuKey: "/resources"});
} else if (uri.includes('/tokens')) { } else if (uri.includes("/tokens")) {
this.setState({ selectedMenuKey: '/tokens' }); this.setState({selectedMenuKey: "/tokens"});
} else if (uri.includes('/records')) { } else if (uri.includes("/records")) {
this.setState({ selectedMenuKey: '/records' }); this.setState({selectedMenuKey: "/records"});
} else if (uri.includes('/webhooks')) { } else if (uri.includes("/webhooks")) {
this.setState({ selectedMenuKey: '/webhooks' }); this.setState({selectedMenuKey: "/webhooks"});
} else if (uri.includes('/syncers')) { } else if (uri.includes("/syncers")) {
this.setState({ selectedMenuKey: '/syncers' }); this.setState({selectedMenuKey: "/syncers"});
} else if (uri.includes('/certs')) { } else if (uri.includes("/certs")) {
this.setState({ selectedMenuKey: '/certs' }); this.setState({selectedMenuKey: "/certs"});
} else if (uri.includes('/products')) { } else if (uri.includes("/products")) {
this.setState({ selectedMenuKey: '/products' }); this.setState({selectedMenuKey: "/products"});
} else if (uri.includes('/payments')) { } else if (uri.includes("/payments")) {
this.setState({ selectedMenuKey: '/payments' }); this.setState({selectedMenuKey: "/payments"});
} else if (uri.includes('/signup')) { } else if (uri.includes("/signup")) {
this.setState({ selectedMenuKey: '/signup' }); this.setState({selectedMenuKey: "/signup"});
} else if (uri.includes('/login')) { } else if (uri.includes("/login")) {
this.setState({ selectedMenuKey: '/login' }); this.setState({selectedMenuKey: "/login"});
} else if (uri.includes('/result')) { } else if (uri.includes("/result")) {
this.setState({ selectedMenuKey: '/result' }); this.setState({selectedMenuKey: "/result"});
} else { } else {
this.setState({ selectedMenuKey: -1 }); this.setState({selectedMenuKey: -1});
} }
} }
@@ -234,16 +234,20 @@ class App extends Component {
AuthBackend.logout() AuthBackend.logout()
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === "ok") {
const owner = this.state.account.owner;
this.setState({ this.setState({
account: null account: null
}); });
Setting.showMessage("success", `Logged out successfully`); Setting.showMessage("success", "Logged out successfully");
let redirectUri = res.data2; let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
}else{ } else if (owner !== "built-in") {
Setting.goToLink(`${window.location.origin}/login/${owner}`);
} else {
Setting.goToLinkSoft(this, "/"); Setting.goToLinkSoft(this, "/");
} }
} else { } else {
@@ -259,9 +263,9 @@ class App extends Component {
} }
handleRightDropdownClick(e) { handleRightDropdownClick(e) {
if (e.key === '/account') { if (e.key === "/account") {
this.props.history.push(`/account`); this.props.history.push("/account");
} else if (e.key === '/logout') { } else if (e.key === "/logout") {
this.logout(); this.logout();
} }
} }
@@ -269,16 +273,16 @@ class App extends Component {
renderAvatar() { renderAvatar() {
if (this.state.account.avatar === "") { if (this.state.account.avatar === "") {
return ( return (
<Avatar style={{ backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: 'middle' }} size="large"> <Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)} {Setting.getShortName(this.state.account.name)}
</Avatar> </Avatar>
) );
} else { } else {
return ( return (
<Avatar src={this.state.account.avatar} style={{verticalAlign: 'middle' }} size="large"> <Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)} {Setting.getShortName(this.state.account.name)}
</Avatar> </Avatar>
) );
} }
} }
@@ -287,10 +291,12 @@ class App extends Component {
<Menu onClick={this.handleRightDropdownClick.bind(this)}> <Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account"> <Menu.Item key="/account">
<SettingOutlined /> <SettingOutlined />
&nbsp;
{i18next.t("account:My Account")} {i18next.t("account:My Account")}
</Menu.Item> </Menu.Item>
<Menu.Item key="/logout"> <Menu.Item key="/logout">
<LogoutOutlined /> <LogoutOutlined />
&nbsp;
{i18next.t("account:Logout")} {i18next.t("account:Logout")}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
@@ -298,7 +304,7 @@ class App extends Component {
return ( return (
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown"> <Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}> <div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
&nbsp; &nbsp;
&nbsp; &nbsp;
{ {
@@ -312,7 +318,7 @@ class App extends Component {
&nbsp; &nbsp;
</div> </div>
</Dropdown> </Dropdown>
) );
} }
renderAccount() { renderAccount() {
@@ -484,7 +490,7 @@ class App extends Component {
renderHomeIfLoggedIn(component) { renderHomeIfLoggedIn(component) {
if (this.state.account !== null && this.state.account !== undefined) { if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to='/' /> return <Redirect to="/" />;
} else { } else {
return component; return component;
} }
@@ -493,77 +499,114 @@ class App extends Component {
renderLoginIfNotLoggedIn(component) { renderLoginIfNotLoggedIn(component) {
if (this.state.account === null) { if (this.state.account === null) {
sessionStorage.setItem("from", window.location.pathname); sessionStorage.setItem("from", window.location.pathname);
return <Redirect to='/login' /> return <Redirect to="/login" />;
} else if (this.state.account === undefined) { } else if (this.state.account === undefined) {
return null; return null;
} } else {
else {
return component; return component;
} }
} }
isStartPages() { isStartPages() {
return window.location.pathname.startsWith('/login') || return window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith('/signup') || window.location.pathname.startsWith("/signup") ||
window.location.pathname === '/'; window.location.pathname === "/";
} }
renderRouter(){ renderRouter() {
return( return(
<div> <div>
<Switch> <Switch>
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/> <Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/> <Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)}/> <Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)}/> <Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)}/> <Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)}/> <Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/> <Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/> <Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />}/> <Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)}/> <Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/> <Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/> <Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/> <Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)}/> <Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)}/> <Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)}/> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)}/> <Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)}/> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)}/> <Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)}/> <Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
{/*<Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/} {/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/> <Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/> <Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/> <Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/> <Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)}/> <Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/> <Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/> <Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/> <Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)}/> <Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)}/> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)}/> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)}/> <Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/> <Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/> <Route exact path="/payments/:paymentName" 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="/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="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/> <Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")} <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>} />} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch> </Switch>
</div> </div>
) );
} }
renderContent() { renderContent() {
if (!Setting.isMobile()) { if (!Setting.isMobile()) {
return ( return (
<div style={{display: 'flex', flex: 'auto',width:"100%",flexDirection: 'column'}}> <div style={{display: "flex", flex: "auto", width:"100%", flexDirection: "column"}}>
<Layout style={{display: 'flex', alignItems: 'stretch'}}> <Layout style={{display: "flex", alignItems: "stretch"}}>
<Header style={{ padding: '0', marginBottom: '3px'}}> <Header style={{padding: "0", marginBottom: "3px"}}>
{
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<div>
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", width: "80%", position: "absolute"}}
>
{
this.renderMenu()
}
</Menu>
{
this.renderAccount()
}
<SelectLanguageBox />
</div>
</Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: "stretch"}}>
<Card className="content-warp-card">
{
this.renderRouter()
}
</Card>
</Layout>
</Layout>
</div>
);
} else {
return(
<div>
<Header style={{padding: "0", marginBottom: "3px"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
@@ -571,66 +614,28 @@ class App extends Component {
</Link> </Link>
) )
} }
<div> <Menu
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
>
{
this.renderMenu()
}
</Menu>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
<Card className="content-warp-card">
{
this.renderRouter()
}
</Card>
</Layout>
</Layout>
</div>
)
} else {
return(
<div>
<Header style={{ padding: '0', marginBottom: '3px'}}>
{
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<Menu
// theme="dark" // theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]} selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px' }} style={{lineHeight: "64px"}}
> >
{ {
this.renderMenu() this.renderMenu()
} }
<div style = {{float: 'right'}}> <div style = {{float: "right"}}>
{ {
this.renderAccount() this.renderAccount()
} }
<SelectLanguageBox/> <SelectLanguageBox />
</div> </div>
</Menu> </Menu>
</Header> </Header>
{ {
this.renderRouter() this.renderRouter()
} }
</div> </div>
) );
} }
} }
@@ -641,14 +646,14 @@ class App extends Component {
return ( return (
<Footer id="footer" style={ <Footer id="footer" style={
{ {
borderTop: '1px solid #e8e8e8', borderTop: "1px solid #e8e8e8",
backgroundColor: 'white', backgroundColor: "white",
textAlign: 'center', textAlign: "center",
} }
}> }>
Made with <span style={{color: 'rgb(255, 255, 255)'}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a> Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
</Footer> </Footer>
) );
} }
isDoorPages() { isDoorPages() {
@@ -665,28 +670,30 @@ class App extends Component {
return ( return (
<div> <div>
<Switch> <Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/> <Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/> <Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} /> <Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} /> <Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
<Route exact path="/callback" component={AuthCallback}/> <Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback}/> <Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/> <Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)}/> <Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)}/> <Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} {...props} />)}/> <Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(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 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>}/>} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch> </Switch>
{ {
this.renderFooter() this.renderFooter()
} }
</div> </div>
) );
} }
return ( return (
@@ -716,7 +723,7 @@ class App extends Component {
this.renderPage() this.renderPage()
} }
</React.Fragment> </React.Fragment>
) );
} }
const organization = this.state.account.organization; const organization = this.state.account.organization;
@@ -730,7 +737,7 @@ class App extends Component {
this.renderPage() this.renderPage()
} }
</React.Fragment> </React.Fragment>
) );
} }
} }

View File

@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import React from 'react'; import React from "react";
import { render } from '@testing-library/react'; import {render} from "@testing-library/react";
import App from './App'; import App from "./App";
test('renders learn react link', () => { // eslint-disable-next-line no-undef
const { getByText } = render(<App />); test("renders learn react link", () => {
const {getByText} = render(<App />);
const linkElement = getByText(/learn react/i); const linkElement = getByText(/learn react/i);
// eslint-disable-next-line no-undef
expect(linkElement).toBeInTheDocument(); expect(linkElement).toBeInTheDocument();
}); });

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, Row, Select, Switch, Upload} from 'antd'; import {Button, Card, Col, Input, Popover, 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";
@@ -30,13 +30,13 @@ import SignupTable from "./SignupTable";
import PromptPage from "./auth/PromptPage"; import PromptPage from "./auth/PromptPage";
import copy from "copy-to-clipboard"; 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";
require('codemirror/theme/material-darker.css'); require("codemirror/theme/material-darker.css");
require("codemirror/mode/htmlmixed/htmlmixed"); require("codemirror/mode/htmlmixed/htmlmixed");
require("codemirror/mode/xml/xml"); require("codemirror/mode/xml/xml");
const { Option } = Select; const {Option} = Select;
class ApplicationEditPage extends React.Component { class ApplicationEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -106,7 +106,7 @@ class ApplicationEditPage extends React.Component {
.then((res) => { .then((res) => {
this.setState({ this.setState({
samlMetadata: res, samlMetadata: res,
}) });
}); });
} }
@@ -144,7 +144,7 @@ class ApplicationEditPage extends React.Component {
} }
}).finally(() => { }).finally(() => {
this.setState({uploading: false}); this.setState({uploading: false});
}) });
} }
renderApplication() { renderApplication() {
@@ -153,262 +153,272 @@ class ApplicationEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("application:New Application") : i18next.t("application:Edit Application")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("application:New Application") : i18next.t("application:Edit Application")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => { <Input value={this.state.application.name} disabled={this.state.application.name === "app-built-in"} onChange={e => {
this.updateApplicationField('name', e.target.value); this.updateApplicationField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.displayName} onChange={e => { <Input value={this.state.application.displayName} onChange={e => {
this.updateApplicationField('displayName', e.target.value); this.updateApplicationField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Logo"), i18next.t("general:Logo - Tooltip"))} : {Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col> </Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}> <Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.application.logo} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.logo} onChange={e => {
this.updateApplicationField('logo', e.target.value); this.updateApplicationField("logo", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.application.logo}> <a target="_blank" rel="noreferrer" href={this.state.application.logo}>
<img src={this.state.application.logo} alt={this.state.application.logo} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.application.logo} alt={this.state.application.logo} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </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:Home"), i18next.t("general:Home - Tooltip"))} : {Setting.getLabel(i18next.t("general:Home"), i18next.t("general:Home - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.homepageUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.homepageUrl} onChange={e => {
this.updateApplicationField('homepageUrl', e.target.value); this.updateApplicationField("homepageUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Description"), i18next.t("general:Description - Tooltip"))} : {Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.description} onChange={e => { <Input value={this.state.application.description} onChange={e => {
this.updateApplicationField('description', e.target.value); this.updateApplicationField("description", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField('organization', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.application.organization} onChange={(value => {this.updateApplicationField("organization", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.clientId} onChange={e => { <Input value={this.state.application.clientId} onChange={e => {
this.updateApplicationField('clientId', e.target.value); this.updateApplicationField("clientId", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.clientSecret} onChange={e => { <Input value={this.state.application.clientSecret} onChange={e => {
this.updateApplicationField('clientSecret', e.target.value); this.updateApplicationField("clientSecret", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Cert"), i18next.t("general:Cert - Tooltip"))} : {Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.application.cert} onChange={(value => {this.updateApplicationField('cert', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.application.cert} onChange={(value => {this.updateApplicationField("cert", value);})}>
{ {
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>) this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} : {Setting.getLabel(i18next.t("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<UrlTable <UrlTable
title={i18next.t("application:Redirect URLs")} title={i18next.t("application:Redirect URLs")}
table={this.state.application.redirectUris} table={this.state.application.redirectUris}
onUpdateTable={(value) => { this.updateApplicationField('redirectUris', value)}} onUpdateTable={(value) => {this.updateApplicationField("redirectUris", value);}}
/> />
</Col> </Col>
</Row> </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("application:Token format"), i18next.t("application:Token format - Tooltip"))} : {Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField('tokenFormat', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}>
{ {
['JWT', 'JWT-Empty'] ["JWT", "JWT-Empty"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} : {Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input style={{width: "150px"}} value={this.state.application.expireInHours} suffix="Hours" onChange={e => { <Input style={{width: "150px"}} value={this.state.application.expireInHours} suffix="Hours" onChange={e => {
this.updateApplicationField('expireInHours', e.target.value); this.updateApplicationField("expireInHours", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} : {Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input style={{width: "150px"}} value={this.state.application.refreshExpireInHours} suffix="Hours" onChange={e => { <Input style={{width: "150px"}} value={this.state.application.refreshExpireInHours} suffix="Hours" onChange={e => {
this.updateApplicationField('refreshExpireInHours', e.target.value); this.updateApplicationField("refreshExpireInHours", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Password ON"), i18next.t("application:Password ON - Tooltip"))} : {Setting.getLabel(i18next.t("application:Password ON"), i18next.t("application:Password ON - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enablePassword} onChange={checked => { <Switch checked={this.state.application.enablePassword} onChange={checked => {
this.updateApplicationField('enablePassword', checked); this.updateApplicationField("enablePassword", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} : {Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enableSignUp} onChange={checked => { <Switch checked={this.state.application.enableSignUp} onChange={checked => {
this.updateApplicationField('enableSignUp', checked); this.updateApplicationField("enableSignUp", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} : {Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enableSigninSession} onChange={checked => { <Switch checked={this.state.application.enableSigninSession} onChange={checked => {
this.updateApplicationField('enableSigninSession', checked); this.updateApplicationField("enableSigninSession", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} : {Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enableCodeSignin} onChange={checked => { <Switch checked={this.state.application.enableCodeSignin} onChange={checked => {
this.updateApplicationField('enableCodeSignin', checked); this.updateApplicationField("enableCodeSignin", checked);
}} /> }} />
</Col> </Col>
</Row> </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()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable WebAuthn signin"), i18next.t("application:Enable WebAuthn signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableWebAuthn} onChange={checked => {
this.updateApplicationField("enableWebAuthn", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:Signup URL"), i18next.t("general:Signup URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.signupUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.signupUrl} onChange={e => {
this.updateApplicationField('signupUrl', e.target.value); this.updateApplicationField("signupUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.signinUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.signinUrl} onChange={e => {
this.updateApplicationField('signinUrl', e.target.value); this.updateApplicationField("signinUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.forgetUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.forgetUrl} onChange={e => {
this.updateApplicationField('forgetUrl', e.target.value); this.updateApplicationField("forgetUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.affiliationUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.application.affiliationUrl} onChange={e => {
this.updateApplicationField('affiliationUrl', e.target.value); this.updateApplicationField("affiliationUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Terms of Use"), i18next.t("provider:Terms of Use - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Terms of Use"), i18next.t("provider:Terms of Use - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => { <Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("termsOfUse", e.target.value); this.updateApplicationField("termsOfUse", e.target.value);
}}/> }} />
<Upload maxCount={1} accept=".html" showUploadList={false} <Upload maxCount={1} accept=".html" showUploadList={false}
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}> beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
<Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button> <Button icon={<UploadOutlined />} loading={this.state.uploading}>{i18next.t("general:Click to Upload")}</Button>
</Upload> </Upload>
</Col> </Col>
</Row> </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("provider:Signup HTML"), i18next.t("provider:Signup HTML - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Signup HTML"), i18next.t("provider:Signup HTML - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -416,7 +426,7 @@ class ApplicationEditPage extends React.Component {
<div style={{width: "900px", height: "300px"}} > <div style={{width: "900px", height: "300px"}} >
<CodeMirror <CodeMirror
value={this.state.application.signupHtml} value={this.state.application.signupHtml}
options={{mode: 'htmlmixed', theme: "material-darker"}} options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => { onBeforeChange={(editor, data, value) => {
this.updateApplicationField("signupHtml", value); this.updateApplicationField("signupHtml", value);
}} }}
@@ -424,13 +434,13 @@ class ApplicationEditPage extends React.Component {
</div> </div>
} title={i18next.t("provider:Signup HTML - Edit")} trigger="click"> } title={i18next.t("provider:Signup HTML - Edit")} trigger="click">
<Input value={this.state.application.signupHtml} style={{marginBottom: "10px"}} onChange={e => { <Input value={this.state.application.signupHtml} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("signupHtml", e.target.value) this.updateApplicationField("signupHtml", e.target.value);
}}/> }} />
</Popover> </Popover>
</Col> </Col>
</Row> </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("provider:Signin HTML"), i18next.t("provider:Signin HTML - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Signin HTML"), i18next.t("provider:Signin HTML - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -438,7 +448,7 @@ class ApplicationEditPage extends React.Component {
<div style={{width: "900px", height: "300px"}} > <div style={{width: "900px", height: "300px"}} >
<CodeMirror <CodeMirror
value={this.state.application.signinHtml} value={this.state.application.signinHtml}
options={{mode: 'htmlmixed', theme: "material-darker"}} options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => { onBeforeChange={(editor, data, value) => {
this.updateApplicationField("signinHtml", value); this.updateApplicationField("signinHtml", value);
}} }}
@@ -446,55 +456,55 @@ class ApplicationEditPage extends React.Component {
</div> </div>
} title={i18next.t("provider:Signin HTML - Edit")} trigger="click"> } title={i18next.t("provider:Signin HTML - Edit")} trigger="click">
<Input value={this.state.application.signinHtml} style={{marginBottom: "10px"}} onChange={e => { <Input value={this.state.application.signinHtml} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("signinHtml", e.target.value) this.updateApplicationField("signinHtml", e.target.value);
}}/> }} />
</Popover> </Popover>
</Col> </Col>
</Row> </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("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} : {Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} <Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.application.grantTypes} value={this.state.application.grantTypes}
onChange={(value => { onChange={(value => {
this.updateApplicationField('grantTypes', value); this.updateApplicationField("grantTypes", value);
})} > })} >
{ {
[ [
{id: "authorization_code", name: "Authorization Code"}, {id: "authorization_code", name: "Authorization Code"},
{id: "password", name: "Password"}, {id: "password", name: "Password"},
{id: "client_credentials", name: "Client Credentials"}, {id: "client_credentials", name: "Client Credentials"},
{id: "token", name: "Token"}, {id: "token", name: "Token"},
{id: "id_token", name: "ID Token"}, {id: "id_token", name: "ID Token"},
{id: "refresh_token", name: "Refresh Token"}, {id: "refresh_token", name: "Refresh Token"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} : {Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.application.enableSamlCompress} onChange={checked => { <Switch checked={this.state.application.enableSamlCompress} onChange={checked => {
this.updateApplicationField('enableSamlCompress', checked); this.updateApplicationField("enableSamlCompress", checked);
}} /> }} />
</Col> </Col>
</Row> </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("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} : {Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
</Col> </Col>
<Col span={22}> <Col span={22}>
<CodeMirror <CodeMirror
value={this.state.samlMetadata} value={this.state.samlMetadata}
options={{mode: 'xml', theme: 'default'}} options={{mode: "xml", theme: "default"}}
onBeforeChange={(editor, data, value) => {}} onBeforeChange={(editor, data, value) => {}}
/> />
<br/> <br />
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => { <Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`); copy(`${window.location.origin}/api/saml/metadata?application=admin/${encodeURIComponent(this.state.applicationName)}`);
Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully")); Setting.showMessage("success", i18next.t("application:SAML metadata URL copied to clipboard successfully"));
@@ -504,8 +514,8 @@ class ApplicationEditPage extends React.Component {
</Button> </Button>
</Col> </Col>
</Row> </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:Providers"), i18next.t("general:Providers - Tooltip"))} : {Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -514,12 +524,12 @@ class ApplicationEditPage extends React.Component {
table={this.state.application.providers} table={this.state.application.providers}
providers={this.state.providers} providers={this.state.providers}
application={this.state.application} application={this.state.application}
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}} onUpdateTable={(value) => {this.updateApplicationField("providers", value);}}
/> />
</Col> </Col>
</Row> </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:Preview"), i18next.t("general:Preview - Tooltip"))} : {Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col> </Col>
{ {
@@ -528,22 +538,22 @@ class ApplicationEditPage extends React.Component {
</Row> </Row>
{ {
!this.state.application.enableSignUp ? null : ( !this.state.application.enableSignUp ? null : (
<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("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} : {Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<SignupTable <SignupTable
title={i18next.t("application:Signup items")} title={i18next.t("application:Signup items")}
table={this.state.application.signupItems} table={this.state.application.signupItems}
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}} onUpdateTable={(value) => {this.updateApplicationField("signupItems", value);}}
/> />
</Col> </Col>
</Row> </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:Preview"), i18next.t("general:Preview - Tooltip"))} : {Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col> </Col>
{ {
@@ -551,13 +561,13 @@ class ApplicationEditPage extends React.Component {
} }
</Row> </Row>
</Card> </Card>
) );
} }
renderSignupSigninPreview() { renderSignupSigninPreview() {
let signUpUrl = `/signup/${this.state.application.name}`; let signUpUrl = `/signup/${this.state.application.name}`;
let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`; let signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'}; let maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
if (!this.state.application.enablePassword) { if (!this.state.application.enablePassword) {
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize"); signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
} }
@@ -572,7 +582,7 @@ 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: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}> <div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
{ {
this.state.application.enablePassword ? ( this.state.application.enablePassword ? (
@@ -592,19 +602,19 @@ 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: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}> <div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} /> <LoginPage type={"login"} mode={"signin"} application={this.state.application} />
<div style={maskStyle}></div> <div style={maskStyle}></div>
</div> </div>
</Col> </Col>
</React.Fragment> </React.Fragment>
) );
} }
renderPromptPreview() { renderPromptPreview() {
let promptUrl = `/prompt/${this.state.application.name}`; let promptUrl = `/prompt/${this.state.application.name}`;
let maskStyle = {position: 'absolute', top: '0px', left: '0px', zIndex: 10, height: '100%', width: '100%', background: 'rgba(0,0,0,0.4)'}; let maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
return ( return (
<Col span={11}> <Col span={11}>
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => { <Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
@@ -614,13 +624,13 @@ 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: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}> <div style={{position: "relative", width: "90%", 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} /> <PromptPage application={this.state.application} account={this.props.account} />
<div style={maskStyle}></div> <div style={maskStyle}></div>
</div> </div>
</Col> </Col>
) );
} }
submitApplicationEdit(willExist) { submitApplicationEdit(willExist) {
@@ -628,19 +638,19 @@ class ApplicationEditPage extends React.Component {
ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application) ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
applicationName: this.state.application.name, applicationName: this.state.application.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/applications`); this.props.history.push("/applications");
} else { } else {
this.props.history.push(`/applications/${this.state.application.name}`); this.props.history.push(`/applications/${this.state.application.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateApplicationField('name', this.state.applicationName); this.updateApplicationField("name", this.state.applicationName);
} }
}) })
.catch(error => { .catch(error => {
@@ -651,7 +661,7 @@ class ApplicationEditPage extends React.Component {
deleteApplication() { deleteApplication() {
ApplicationBackend.deleteApplication(this.state.application) ApplicationBackend.deleteApplication(this.state.application)
.then(() => { .then(() => {
this.props.history.push(`/applications`); this.props.history.push("/applications");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Application failed to delete: ${error}`); Setting.showMessage("error", `Application failed to delete: ${error}`);
@@ -661,15 +671,15 @@ class ApplicationEditPage extends React.Component {
render() { render() {
return ( return (
<div> <div>
{ {
this.state.application !== null ? this.renderApplication() : null this.state.application !== null ? this.renderApplication() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitApplicationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div> </div>
</div>
); );
} }
} }

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 {Button, Col, List, Popconfirm, Row, Table, Tooltip} from 'antd'; import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
import {EditOutlined} from "@ant-design/icons"; import {EditOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -53,15 +53,15 @@ class ApplicationListPage extends BaseListPage {
redirectUris: ["http://localhost:9000/callback"], redirectUris: ["http://localhost:9000/callback"],
tokenFormat: "JWT", tokenFormat: "JWT",
expireInHours: 24 * 7, expireInHours: 24 * 7,
} };
} }
addApplication() { addApplication() {
const newApplication = this.newApplication(); const newApplication = this.newApplication();
ApplicationBackend.addApplication(newApplication) ApplicationBackend.addApplication(newApplication)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"}); this.props.history.push({pathname: `/applications/${newApplication.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Application failed to add: ${error}`); Setting.showMessage("error", `Application failed to add: ${error}`);
@@ -71,12 +71,12 @@ class ApplicationListPage extends BaseListPage {
deleteApplication(i) { deleteApplication(i) {
ApplicationBackend.deleteApplication(this.state.data[i]) ApplicationBackend.deleteApplication(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Application deleted successfully`); Setting.showMessage("success", "Application deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Application failed to delete: ${error}`); Setting.showMessage("error", `Application failed to delete: ${error}`);
@@ -87,25 +87,25 @@ class ApplicationListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/applications/${text}`}> <Link to={`/applications/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -113,45 +113,45 @@ class ApplicationListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: 'Logo', title: "Logo",
dataIndex: 'logo', dataIndex: "logo",
key: 'logo', key: "logo",
width: '200px', width: "200px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={150} /> <img src={text} alt={text} width={150} />
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '150px', width: "150px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Providers"), title: i18next.t("general:Providers"),
dataIndex: 'providers', dataIndex: "providers",
key: 'providers', key: "providers",
...this.getColumnSearchProps('providers'), ...this.getColumnSearchProps("providers"),
// width: '600px', // width: '600px',
render: (text, record, index) => { render: (text, record, index) => {
const providers = text; const providers = text;
@@ -179,11 +179,11 @@ class ApplicationListPage extends BaseListPage {
</Link> </Link>
</div> </div>
</List.Item> </List.Item>
) );
}} }}
/> />
) );
} };
return ( return (
<div> <div>
@@ -200,28 +200,28 @@ class ApplicationListPage extends BaseListPage {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
}, },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete application: ${record.name} ?`} title={`Sure to delete application: ${record.name} ?`}
onConfirm={() => this.deleteApplication(index)} onConfirm={() => this.deleteApplication(index)}
disabled={record.name === "app-built-in"} disabled={record.name === "app-built-in"}
> >
<Button style={{marginBottom: '10px'}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} disabled={record.name === "app-built-in"} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -235,15 +235,15 @@ class ApplicationListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Applications")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Applications")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addApplication.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -252,7 +252,7 @@ class ApplicationListPage extends BaseListPage {
fetch = (params = {}) => { fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText; let field = params.searchedColumn, value = params.searchText;
let sortField = params.sortField, sortOrder = params.sortOrder; let sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({ loading: true }); this.setState({loading: true});
ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -18,121 +18,122 @@ import {SearchOutlined} from "@ant-design/icons";
import Highlighter from "react-highlight-words"; import Highlighter from "react-highlight-words";
class BaseListPage extends React.Component { class BaseListPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
data: [], data: [],
pagination: { pagination: {
current: 1, current: 1,
pageSize: 10, pageSize: 10,
}, },
loading: false, loading: false,
searchText: '', searchText: "",
searchedColumn: '', searchedColumn: "",
}; isAuthorized: true,
} };
}
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
const { pagination } = this.state; const {pagination} = this.state;
this.fetch({ pagination }); this.fetch({pagination});
} }
getColumnSearchProps = dataIndex => ({ getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => ( filterDropdown: ({setSelectedKeys, selectedKeys, confirm, clearFilters}) => (
<div style={{ padding: 8 }}> <div style={{padding: 8}}>
<Input <Input
ref={node => { ref={node => {
this.searchInput = node; this.searchInput = node;
}} }}
placeholder={`Search ${dataIndex}`} placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]} value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])} onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)} onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ marginBottom: 8, display: 'block' }} style={{marginBottom: 8, display: "block"}}
/> />
<Space> <Space>
<Button <Button
type="primary" type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)} onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />} icon={<SearchOutlined />}
size="small" size="small"
style={{ width: 90 }} style={{width: 90}}
> >
Search Search
</Button> </Button>
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}> <Button onClick={() => this.handleReset(clearFilters)} size="small" style={{width: 90}}>
Reset Reset
</Button> </Button>
<Button <Button
type="link" type="link"
size="small" size="small"
onClick={() => { onClick={() => {
confirm({ closeDropdown: false }); confirm({closeDropdown: false});
this.setState({ this.setState({
searchText: selectedKeys[0], searchText: selectedKeys[0],
searchedColumn: dataIndex, searchedColumn: dataIndex,
}); });
}} }}
> >
Filter Filter
</Button> </Button>
</Space> </Space>
</div> </div>
), ),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />, filterIcon: filtered => <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}} />,
onFilter: (value, record) => onFilter: (value, record) =>
record[dataIndex] record[dataIndex]
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
: '', : "",
onFilterDropdownVisibleChange: visible => { onFilterDropdownVisibleChange: visible => {
if (visible) { if (visible) {
setTimeout(() => this.searchInput.select(), 100); setTimeout(() => this.searchInput.select(), 100);
} }
}, },
render: text => render: text =>
this.state.searchedColumn === dataIndex ? ( this.state.searchedColumn === dataIndex ? (
<Highlighter <Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }} highlightStyle={{backgroundColor: "#ffc069", padding: 0}}
searchWords={[this.state.searchText]} searchWords={[this.state.searchText]}
autoEscape autoEscape
textToHighlight={text ? text.toString() : ''} textToHighlight={text ? text.toString() : ""}
/> />
) : ( ) : (
text text
), ),
}); });
handleSearch = (selectedKeys, confirm, dataIndex) => { handleSearch = (selectedKeys, confirm, dataIndex) => {
this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination}); this.fetch({searchText: selectedKeys[0], searchedColumn: dataIndex, pagination: this.state.pagination});
}; };
handleReset = clearFilters => { handleReset = clearFilters => {
clearFilters(); clearFilters();
const { pagination } = this.state; const {pagination} = this.state;
this.fetch({ pagination }); this.fetch({pagination});
}; };
handleTableChange = (pagination, filters, sorter) => { handleTableChange = (pagination, filters, sorter) => {
this.fetch({ this.fetch({
sortField: sorter.field, sortField: sorter.field,
sortOrder: sorter.order, sortOrder: sorter.order,
pagination, pagination,
...filters, ...filters,
searchText: this.state.searchText, searchText: this.state.searchText,
searchedColumn: this.state.searchedColumn, searchedColumn: this.state.searchedColumn,
}); });
}; };
render() { render() {
return ( return (
<div> <div>
{ {
this.renderTable(this.state.data) this.renderTable(this.state.data)
} }
</div> </div>
); );
} }
} }
export default BaseListPage; export default BaseListPage;

View File

@@ -13,15 +13,15 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select} from 'antd'; import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as CertBackend from "./backend/CertBackend"; import * as CertBackend from "./backend/CertBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
const { Option } = Select; const {Option} = Select;
const { TextArea } = Input; const {TextArea} = Input;
class CertEditPage extends React.Component { class CertEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -70,127 +70,127 @@ class CertEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("cert:New Cert") : i18next.t("cert:Edit Cert")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("cert:New Cert") : i18next.t("cert:Edit Cert")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.cert.name} onChange={e => { <Input value={this.state.cert.name} onChange={e => {
this.updateCertField('name', e.target.value); this.updateCertField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.cert.displayName} onChange={e => { <Input value={this.state.cert.displayName} onChange={e => {
this.updateCertField('displayName', e.target.value); this.updateCertField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.scope} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.cert.scope} onChange={(value => {
this.updateCertField('scope', value); this.updateCertField("scope", value);
})}> })}>
{ {
[ [
{id: 'JWT', name: 'JWT'}, {id: "JWT", name: "JWT"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("cert:Type"), i18next.t("cert:Type - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Type"), i18next.t("cert:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.type} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.cert.type} onChange={(value => {
this.updateCertField('type', value); this.updateCertField("type", value);
})}> })}>
{ {
[ [
{id: 'x509', name: 'x509'}, {id: "x509", name: "x509"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.cryptoAlgorithm} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField('cryptoAlgorithm', value); this.updateCertField("cryptoAlgorithm", value);
})}> })}>
{ {
[ [
{id: 'RS256', name: 'RS256'}, {id: "RS256", name: "RS256"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.cert.bitSize} onChange={value => { <InputNumber value={this.state.cert.bitSize} onChange={value => {
this.updateCertField('bitSize', value); this.updateCertField("bitSize", value);
}} /> }} />
</Col> </Col>
</Row> </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("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.cert.expireInYears} onChange={value => { <InputNumber value={this.state.cert.expireInYears} onChange={value => {
this.updateCertField('expireInYears', value); this.updateCertField("expireInYears", value);
}} /> }} />
</Col> </Col>
</Row> </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("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col> </Col>
<Col span={9} > <Col span={9} >
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => { <Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.publicKey); copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}} }}
> >
{i18next.t("cert:Copy public key")} {i18next.t("cert:Copy certificate")}
</Button> </Button>
<Button type="primary" onClick={() => { <Button type="primary" onClick={() => {
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"}); const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem"); FileSaver.saveAs(blob, "token_jwt_key.pem");
}} }}
> >
{i18next.t("cert:Download public key")} {i18next.t("cert:Download certificate")}
</Button> </Button>
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => { <TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.certificate} onChange={e => {
this.updateCertField('publicKey', e.target.value); this.updateCertField("certificate", e.target.value);
}} /> }} />
</Col> </Col>
<Col span={1} /> <Col span={1} />
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
</Col> </Col>
<Col span={9} > <Col span={9} >
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => { <Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.privateKey); copy(this.state.cert.privateKey);
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
}} }}
@@ -205,12 +205,12 @@ class CertEditPage extends React.Component {
{i18next.t("cert:Download private key")} {i18next.t("cert:Download private key")}
</Button> </Button>
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.privateKey} onChange={e => { <TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.privateKey} onChange={e => {
this.updateCertField('privateKey', e.target.value); this.updateCertField("privateKey", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitCertEdit(willExist) { submitCertEdit(willExist) {
@@ -218,19 +218,19 @@ class CertEditPage extends React.Component {
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert) CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
certName: this.state.cert.name, certName: this.state.cert.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/certs`); this.props.history.push("/certs");
} else { } else {
this.props.history.push(`/certs/${this.state.cert.name}`); this.props.history.push(`/certs/${this.state.cert.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateCertField('name', this.state.certName); this.updateCertField("name", this.state.certName);
} }
}) })
.catch(error => { .catch(error => {
@@ -241,7 +241,7 @@ class CertEditPage extends React.Component {
deleteCert() { deleteCert() {
CertBackend.deleteCert(this.state.cert) CertBackend.deleteCert(this.state.cert)
.then(() => { .then(() => {
this.props.history.push(`/certs`); this.props.history.push("/certs");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Cert failed to delete: ${error}`); Setting.showMessage("error", `Cert failed to delete: ${error}`);
@@ -254,10 +254,10 @@ class CertEditPage extends React.Component {
{ {
this.state.cert !== null ? this.renderCert() : null this.state.cert !== null ? this.renderCert() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitCertEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Table} from 'antd'; import {Button, Popconfirm, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as CertBackend from "./backend/CertBackend"; import * as CertBackend from "./backend/CertBackend";
@@ -34,17 +34,17 @@ class CertListPage extends BaseListPage {
cryptoAlgorithm: "RS256", cryptoAlgorithm: "RS256",
bitSize: 4096, bitSize: 4096,
expireInYears: 20, expireInYears: 20,
publicKey: "", certificate: "",
privateKey: "", privateKey: "",
} };
} }
addCert() { addCert() {
const newCert = this.newCert(); const newCert = this.newCert();
CertBackend.addCert(newCert) CertBackend.addCert(newCert)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"}); this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Cert failed to add: ${error}`); Setting.showMessage("error", `Cert failed to add: ${error}`);
@@ -54,12 +54,12 @@ class CertListPage extends BaseListPage {
deleteCert(i) { deleteCert(i) {
CertBackend.deleteCert(this.state.data[i]) CertBackend.deleteCert(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Cert deleted successfully`); Setting.showMessage("success", "Cert deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Cert failed to delete: ${error}`); Setting.showMessage("error", `Cert failed to delete: ${error}`);
@@ -70,25 +70,25 @@ class CertListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '120px', width: "120px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/certs/${text}`}> <Link to={`/certs/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '180px', width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -96,79 +96,79 @@ class CertListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("cert:Scope"), title: i18next.t("cert:Scope"),
dataIndex: 'scope', dataIndex: "scope",
key: 'scope', key: "scope",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'JWT', value: 'JWT'}, {text: "JWT", value: "JWT"},
], ],
width: '110px', width: "110px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("cert:Type"), title: i18next.t("cert:Type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'x509', value: 'x509'}, {text: "x509", value: "x509"},
], ],
width: '110px', width: "110px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("cert:Crypto algorithm"), title: i18next.t("cert:Crypto algorithm"),
dataIndex: 'cryptoAlgorithm', dataIndex: "cryptoAlgorithm",
key: 'cryptoAlgorithm', key: "cryptoAlgorithm",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'RS256', value: 'RS256'}, {text: "RS256", value: "RS256"},
], ],
width: '190px', width: "190px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("cert:Bit size"), title: i18next.t("cert:Bit size"),
dataIndex: 'bitSize', dataIndex: "bitSize",
key: 'bitSize', key: "bitSize",
width: '130px', width: "130px",
sorter: true, sorter: true,
...this.getColumnSearchProps('bitSize'), ...this.getColumnSearchProps("bitSize"),
}, },
{ {
title: i18next.t("cert:Expire in years"), title: i18next.t("cert:Expire in years"),
dataIndex: 'expireInYears', dataIndex: "expireInYears",
key: 'expireInYears', key: "expireInYears",
width: '170px', width: "170px",
sorter: true, sorter: true,
...this.getColumnSearchProps('expireInYears'), ...this.getColumnSearchProps("expireInYears"),
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete cert: ${record.name} ?`} title={`Sure to delete cert: ${record.name} ?`}
onConfirm={() => this.deleteCert(index)} onConfirm={() => this.deleteCert(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -182,15 +182,15 @@ class CertListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={certs} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={certs} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Certs")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Certs")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addCert.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addCert.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -206,7 +206,7 @@ class CertListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
CertBackend.getCerts("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) CertBackend.getCerts("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -16,122 +16,122 @@ import React, {useState} from "react";
import Cropper from "react-cropper"; import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css"; import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {Button, Row, Col, Modal} from 'antd'; import {Button, Col, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import * as ResourceBackend from "./backend/ResourceBackend"; import * as ResourceBackend from "./backend/ResourceBackend";
export const CropperDiv = (props) => { export const CropperDiv = (props) => {
const [image, setImage] = useState(""); const [image, setImage] = useState("");
const [cropper, setCropper] = useState(); const [cropper, setCropper] = useState();
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false); const [confirmLoading, setConfirmLoading] = React.useState(false);
const {title} = props; const {title} = props;
const {user} = props; const {user} = props;
const {buttonText} = props; const {buttonText} = props;
let uploadButton; let uploadButton;
const onChange = (e) => { const onChange = (e) => {
e.preventDefault(); e.preventDefault();
let files; let files;
if (e.dataTransfer) { if (e.dataTransfer) {
files = e.dataTransfer.files; files = e.dataTransfer.files;
} else if (e.target) { } else if (e.target) {
files = e.target.files; files = e.target.files;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
setImage(reader.result); setImage(reader.result);
};
if (!(files[0] instanceof Blob)) {
return;
}
reader.readAsDataURL(files[0]);
}; };
if (!(files[0] instanceof Blob)) {
return;
}
reader.readAsDataURL(files[0]);
};
const uploadAvatar = () => { const uploadAvatar = () => {
cropper.getCroppedCanvas().toBlob(blob => { cropper.getCroppedCanvas().toBlob(blob => {
if (blob === null) { if (blob === null) {
Setting.showMessage("error", "You must select a picture first!"); Setting.showMessage("error", "You must select a picture first!");
return false; return false;
} }
// Setting.showMessage("success", "uploading..."); // Setting.showMessage("success", "uploading...");
const extension = image.substring(image.indexOf('/') + 1, image.indexOf(';base64')); const extension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64"));
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`; const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob) ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
window.location.href = "/account"; window.location.href = "/account";
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
});
return true;
}); });
return true;
});
};
const showModal = () => {
setVisible(true);
};
const handleOk = () => {
setConfirmLoading(true);
if (!uploadAvatar()) {
setConfirmLoading(false);
} }
};
const showModal = () => { const handleCancel = () => {
setVisible(true); console.log("Clicked cancel button");
}; setVisible(false);
};
const handleOk = () => { const selectFile = () => {
setConfirmLoading(true); uploadButton.click();
if (!uploadAvatar()) { };
setConfirmLoading(false);
return (
<div>
<Button type="default" onClick={showModal}>
{buttonText}
</Button>
<Modal
maskClosable={false}
title={title}
visible={visible}
okText={i18next.t("user:Upload a photo")}
confirmLoading={confirmLoading}
onCancel={handleCancel}
width={600}
footer={
[<Button block type="primary" onClick={handleOk}>{i18next.t("user:Set new profile picture")}</Button>]
} }
}; >
<Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}>
const handleCancel = () => { <Row style={{width: "100%", marginBottom: "20px"}}>
console.log('Clicked cancel button'); <input style={{display: "none"}} ref={input => uploadButton = input} type="file" accept="image/*" onChange={onChange} />
setVisible(false); <Button block onClick={selectFile}>{i18next.t("user:Select a photo...")}</Button>
}; </Row>
<Cropper
const selectFile = () => { style={{height: "100%"}}
uploadButton.click(); initialAspectRatio={1}
} preview=".img-preview"
src={image}
return ( viewMode={1}
<div> guides={true}
<Button type="default" onClick={showModal}> minCropBoxHeight={10}
{buttonText} minCropBoxWidth={10}
</Button> background={false}
<Modal responsive={true}
maskClosable={false} autoCropArea={1}
title={title} checkOrientation={false}
visible={visible} onInitialized={(instance) => {
okText={i18next.t("user:Upload a photo")} setCropper(instance);
confirmLoading={confirmLoading} }}
onCancel={handleCancel} />
width={600} </Col>
footer={ </Modal>
[<Button block type="primary" onClick={handleOk}>{i18next.t("user:Set new profile picture")}</Button>] </div>
} );
> };
<Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}>
<Row style={{width: "100%", marginBottom: "20px"}}>
<input style={{display: "none"}} ref={input => uploadButton = input} type="file" accept="image/*" onChange={onChange}/>
<Button block onClick={selectFile}>{i18next.t("user:Select a photo...")}</Button>
</Row>
<Cropper
style={{height: "100%"}}
initialAspectRatio={1}
preview=".img-preview"
src={image}
viewMode={1}
guides={true}
minCropBoxHeight={10}
minCropBoxWidth={10}
background={false}
responsive={true}
autoCropArea={1}
checkOrientation={false}
onInitialized={(instance) => {
setCropper(instance);
}}
/>
</Col>
</Modal>
</div>
)
}
export default CropperDiv; export default CropperDiv;

View File

@@ -43,11 +43,11 @@ class LdapEditPage extends React.Component {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({ this.setState({
ldap: res.data ldap: res.data
}) });
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}) });
} }
getOrganizations() { getOrganizations() {
@@ -73,7 +73,7 @@ class LdapEditPage extends React.Component {
color: "#faad14", color: "#faad14",
marginLeft: "20px" marginLeft: "20px"
}}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span> }}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span>
) );
} }
} }
@@ -91,12 +91,12 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} <Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)}
value={this.state.ldap.owner} onChange={(value => { value={this.state.ldap.owner} onChange={(value => {
this.updateLdapField("owner", value); this.updateLdapField("owner", value);
})}> })}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} this.state.organizations.map((organization, index) => <Option key={index}
value={organization.name}>{organization.name}</Option>) value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
@@ -106,7 +106,7 @@ class LdapEditPage extends React.Component {
{Setting.getLabel(i18next.t("ldap:ID"), i18next.t("general:ID - Tooltip"))} : {Setting.getLabel(i18next.t("ldap:ID"), i18next.t("general:ID - Tooltip"))} :
</Col> </Col>
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.id} disabled={true}/> <Input value={this.state.ldap.id} disabled={true} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -116,7 +116,7 @@ class LdapEditPage extends React.Component {
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.serverName} onChange={e => { <Input value={this.state.ldap.serverName} onChange={e => {
this.updateLdapField("serverName", e.target.value); this.updateLdapField("serverName", e.target.value);
}}/> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -126,7 +126,7 @@ class LdapEditPage extends React.Component {
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.host} onChange={e => { <Input value={this.state.ldap.host} onChange={e => {
this.updateLdapField("host", e.target.value); this.updateLdapField("host", e.target.value);
}}/> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -135,9 +135,9 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<InputNumber min={0} max={65535} formatter={value => value.replace(/\$\s?|(,*)/g, "")} <InputNumber min={0} max={65535} formatter={value => value.replace(/\$\s?|(,*)/g, "")}
value={this.state.ldap.port} onChange={value => { value={this.state.ldap.port} onChange={value => {
this.updateLdapField("port", value); this.updateLdapField("port", value);
}}/> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -147,7 +147,7 @@ class LdapEditPage extends React.Component {
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.baseDn} onChange={e => { <Input value={this.state.ldap.baseDn} onChange={e => {
this.updateLdapField("baseDn", e.target.value); this.updateLdapField("baseDn", e.target.value);
}}/> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -157,7 +157,7 @@ class LdapEditPage extends React.Component {
<Col span={21}> <Col span={21}>
<Input value={this.state.ldap.admin} onChange={e => { <Input value={this.state.ldap.admin} onChange={e => {
this.updateLdapField("admin", e.target.value); this.updateLdapField("admin", e.target.value);
}}/> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}}> <Row style={{marginTop: "20px"}}>
@@ -166,7 +166,7 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<Input.Password <Input.Password
iconRender={visible => (visible ? <EyeTwoTone/> : <EyeInvisibleOutlined/>)} value={this.state.ldap.passwd} iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.passwd}
onChange={e => { onChange={e => {
this.updateLdapField("passwd", e.target.value); this.updateLdapField("passwd", e.target.value);
}} }}
@@ -179,24 +179,24 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={21}> <Col span={21}>
<InputNumber min={0} formatter={value => value.replace(/\$\s?|(,*)/g, "")} disabled={false} <InputNumber min={0} formatter={value => value.replace(/\$\s?|(,*)/g, "")} disabled={false}
value={this.state.ldap.autoSync} onChange={value => { value={this.state.ldap.autoSync} onChange={value => {
this.updateLdapField("autoSync", value); this.updateLdapField("autoSync", value);
}}/><span>&nbsp;mins</span> }} /><span>&nbsp;mins</span>
{this.renderAutoSyncWarn()} {this.renderAutoSyncWarn()}
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitLdapEdit() { submitLdapEdit() {
LddpBackend.updateLdap(this.state.ldap) LddpBackend.updateLdap(this.state.ldap)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", `Update LDAP server success`); Setting.showMessage("success", "Update LDAP server success");
this.setState((prevState) => { this.setState((prevState) => {
prevState.ldap = res.data2; prevState.ldap = res.data2;
}) });
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
@@ -225,7 +225,7 @@ class LdapEditPage extends React.Component {
</Col> </Col>
<Col span={18}> <Col span={18}>
<Button type="primary" size="large" <Button type="primary" size="large"
onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button> onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@@ -28,7 +28,7 @@ class LdapListPage extends React.Component {
} }
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getLdaps() this.getLdaps();
} }
getLdaps() { getLdaps() {
@@ -43,7 +43,7 @@ class LdapListPage extends React.Component {
this.setState((prevState) => { this.setState((prevState) => {
prevState.ldaps = ldapsData; prevState.ldaps = ldapsData;
return prevState; return prevState;
}) });
}); });
} }
@@ -64,7 +64,7 @@ class LdapListPage extends React.Component {
<Link to={`/ldaps/${record.id}`}> <Link to={`/ldaps/${record.id}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
@@ -78,7 +78,7 @@ class LdapListPage extends React.Component {
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
@@ -88,7 +88,7 @@ class LdapListPage extends React.Component {
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.host.localeCompare(b.host), sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => { render: (text, record, index) => {
return `${text}:${record.port}` return `${text}:${record.port}`;
} }
}, },
{ {
@@ -113,7 +113,7 @@ class LdapListPage extends React.Component {
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync), sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
render: (text, record, index) => { render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : ( return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>) <span style={{color: "#52c41a"}}>{text + " mins"}</span>);
} }
}, },
{ {
@@ -123,7 +123,7 @@ class LdapListPage extends React.Component {
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync), sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => { render: (text, record, index) => {
return text return text;
} }
}, },
{ {
@@ -135,19 +135,19 @@ class LdapListPage extends React.Component {
return ( return (
<div> <div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
type="primary" type="primary"
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button> onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button> onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete LDAP Config: ${record.serverName} ?`} title={`Sure to delete LDAP Config: ${record.serverName} ?`}
onConfirm={() => this.deleteLdap(index)} onConfirm={() => this.deleteLdap(index)}
> >
<Button style={{marginBottom: "10px"}} <Button style={{marginBottom: "10px"}}
type="danger">{i18next.t("general:Delete")}</Button> type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -155,17 +155,17 @@ class LdapListPage extends React.Component {
return ( return (
<div> <div>
<Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered <Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered
pagination={{pageSize: 100}} pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
<span>{i18next.t("general:LDAPs")}</span> <span>{i18next.t("general:LDAPs")}</span>
<Button type="primary" size="small" style={{marginLeft: "10px"}} <Button type="primary" size="small" style={{marginLeft: "10px"}}
onClick={() => { onClick={() => {
this.addLdap() this.addLdap();
}}>{i18next.t("general:Add")}</Button> }}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={ldaps === null} loading={ldaps === null}
/> />
</div> </div>
); );

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Row, Table, Popconfirm} from "antd"; import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend"; import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next"; import i18next from "i18next";
@@ -31,14 +31,14 @@ class LdapSyncPage extends React.Component {
} }
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getLdap() this.getLdap();
} }
syncUsers() { syncUsers() {
let selectedUsers = this.state.selectedUsers; let selectedUsers = this.state.selectedUsers;
if (selectedUsers === null || selectedUsers.length === 0) { if (selectedUsers === null || selectedUsers.length === 0) {
Setting.showMessage("error", "Please select al least 1 user first"); Setting.showMessage("error", "Please select al least 1 user first");
return return;
} }
LdapBackend.syncUsers(this.state.ldap.owner, this.state.ldap.id, selectedUsers) LdapBackend.syncUsers(this.state.ldap.owner, this.state.ldap.id, selectedUsers)
@@ -62,14 +62,14 @@ class LdapSyncPage extends React.Component {
if (failed && failed.length > 0) { if (failed && failed.length > 0) {
failed.forEach(elem => { failed.forEach(elem => {
failedUser.push(elem.cn); failedUser.push(elem.cn);
}) });
Setting.showMessage("error", `Sync [${failedUser}] failed`) Setting.showMessage("error", `Sync [${failedUser}] failed`);
} }
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
})) }));
} }
getLdap() { getLdap() {
@@ -79,7 +79,7 @@ class LdapSyncPage extends React.Component {
this.setState((prevState) => { this.setState((prevState) => {
prevState.ldap = res.data; prevState.ldap = res.data;
return prevState; return prevState;
}) });
this.getLdapUser(res.data); this.getLdapUser(res.data);
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
@@ -95,28 +95,28 @@ class LdapSyncPage extends React.Component {
this.setState((prevState) => { this.setState((prevState) => {
prevState.users = res.data.users; prevState.users = res.data.users;
return prevState; return prevState;
}) });
this.getExistUsers(ldap.owner, res.data.users); this.getExistUsers(ldap.owner, res.data.users);
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}) });
} }
getExistUsers(owner, users) { getExistUsers(owner, users) {
let uuidArray = []; let uuidArray = [];
users.forEach(elem => { users.forEach(elem => {
uuidArray.push(elem.uuid); uuidArray.push(elem.uuid);
}) });
LdapBackend.checkLdapUsersExist(owner, uuidArray) LdapBackend.checkLdapUsersExist(owner, uuidArray)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState(prevState => { this.setState(prevState => {
prevState.existUuids = res.data?.length > 0 ? res.data : []; prevState.existUuids = res.data?.length > 0 ? res.data : [];
return prevState; return prevState;
}) });
} }
}) });
} }
buildValArray(data, key) { buildValArray(data, key) {
@@ -137,7 +137,7 @@ class LdapSyncPage extends React.Component {
let filterArray = []; let filterArray = [];
if (data !== null && data.length > 0) { if (data !== null && data.length > 0) {
let valArray = this.buildValArray(data, key) let valArray = this.buildValArray(data, key);
valArray.forEach(elem => { valArray.forEach(elem => {
filterArray.push({ filterArray.push({
text: elem, text: elem,
@@ -163,7 +163,7 @@ class LdapSyncPage extends React.Component {
width: "200px", width: "200px",
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber), sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
render: (text, record, index) => { render: (text, record, index) => {
return `${text} / ${record.uid}` return `${text} / ${record.uid}`;
}, },
}, },
{ {
@@ -202,7 +202,7 @@ class LdapSyncPage extends React.Component {
this.setState(prevState => { this.setState(prevState => {
prevState.selectedUsers = selectedRows; prevState.selectedUsers = selectedRows;
return prevState; return prevState;
}) });
}, },
getCheckboxProps: record => ({ getCheckboxProps: record => ({
disabled: this.state.existUuids.indexOf(record.uuid) !== -1, disabled: this.state.existUuids.indexOf(record.uuid) !== -1,
@@ -212,20 +212,20 @@ class LdapSyncPage extends React.Component {
return ( return (
<div> <div>
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered <Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}} pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
title={() => ( title={() => (
<div> <div>
<span>{this.state.ldap?.serverName}</span> <span>{this.state.ldap?.serverName}</span>
<Popconfirm placement={"right"} <Popconfirm placement={"right"}
title={`Please confirm to sync selected users`} title={"Please confirm to sync selected users"}
onConfirm={() => this.syncUsers()} onConfirm={() => this.syncUsers()}
> >
<Button type="primary" size="small" <Button type="primary" size="small"
style={{marginLeft: "10px"}}>{i18next.t("ldap:Sync")}</Button> style={{marginLeft: "10px"}}>{i18next.t("ldap:Sync")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
)} )}
loading={users === null} loading={users === null}
/> />
</div> </div>
); );

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Popconfirm, Row, Table} from 'antd'; import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import * as LdapBackend from "./backend/LdapBackend"; import * as LdapBackend from "./backend/LdapBackend";
@@ -49,24 +49,24 @@ class LdapTable extends React.Component {
baseDn: "ou=People,dc=example,dc=com", baseDn: "ou=People,dc=example,dc=com",
autosync: 0, autosync: 0,
lastSync: "" lastSync: ""
} };
} }
addRow(table) { addRow(table) {
const newLdap = this.newLdap(); const newLdap = this.newLdap();
LdapBackend.addLdap(newLdap) LdapBackend.addLdap(newLdap)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", `Add LDAP server success`); Setting.showMessage("success", "Add LDAP server success");
if (table === undefined) { if (table === undefined) {
table = []; table = [];
}
table = Setting.addRow(table, res.data2);
this.updateTable(table);
} else {
Setting.showMessage("error", res.msg);
} }
table = Setting.addRow(table, res.data2);
this.updateTable(table);
} else {
Setting.showMessage("error", res.msg);
} }
}
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Add LDAP server failed: ${error}`); Setting.showMessage("error", `Add LDAP server failed: ${error}`);
@@ -76,14 +76,14 @@ class LdapTable extends React.Component {
deleteRow(table, i) { deleteRow(table, i) {
LdapBackend.deleteLdap(table[i]) LdapBackend.deleteLdap(table[i])
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", `Delete LDAP server success`); Setting.showMessage("success", "Delete LDAP server success");
table = Setting.deleteRow(table, i); table = Setting.deleteRow(table, i);
this.updateTable(table); this.updateTable(table);
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
}
} }
}
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Delete LDAP server failed: ${error}`); Setting.showMessage("error", `Delete LDAP server failed: ${error}`);
@@ -103,7 +103,7 @@ class LdapTable extends React.Component {
<Link to={`/ldaps/${record.id}`}> <Link to={`/ldaps/${record.id}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
@@ -113,7 +113,7 @@ class LdapTable extends React.Component {
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.host.localeCompare(b.host), sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => { render: (text, record, index) => {
return `${text}:${record.port}` return `${text}:${record.port}`;
} }
}, },
{ {
@@ -131,7 +131,7 @@ class LdapTable extends React.Component {
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync), sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
render: (text, record, index) => { render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : ( return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>) <span style={{color: "#52c41a"}}>{text + " mins"}</span>);
} }
}, },
{ {
@@ -141,7 +141,7 @@ class LdapTable extends React.Component {
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync), sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => { render: (text, record, index) => {
return text return text;
} }
}, },
{ {
@@ -153,32 +153,32 @@ class LdapTable extends React.Component {
return ( return (
<div> <div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
type="primary" type="primary"
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button> onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button> onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete LDAP Config: ${record.serverName} ?`} title={`Sure to delete LDAP Config: ${record.serverName} ?`}
onConfirm={() => this.deleteRow(table, index)} onConfirm={() => this.deleteRow(table, index)}
> >
<Button style={{marginBottom: "10px"}} <Button style={{marginBottom: "10px"}}
type="danger">{i18next.t("general:Delete")}</Button> type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
return ( return (
<Table scroll={{x: 'max-content'}} rowKey="id" columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table scroll={{x: "max-content"}} rowKey="id" 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;
<Button style={{marginRight: "5px"}} type="primary" size="small" <Button style={{marginRight: "5px"}} type="primary" size="small"
onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button> onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
/> />
); );
} }
@@ -186,7 +186,7 @@ class LdapTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}}> <Row style={{marginTop: "20px"}}>
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -194,7 +194,7 @@ class LdapTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@@ -13,14 +13,14 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import TextArea from "antd/es/input/TextArea"; import TextArea from "antd/es/input/TextArea";
const { Option } = Select; const {Option} = Select;
class ModelEditPage extends React.Component { class ModelEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -94,64 +94,64 @@ class ModelEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("model:New Model") : i18next.t("model:Edit Model")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.model.owner} onChange={(value => {this.updateModelField('owner', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.model.owner} onChange={(value => {this.updateModelField("owner", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.model.name} onChange={e => { <Input value={this.state.model.name} onChange={e => {
this.updateModelField('name', e.target.value); this.updateModelField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.model.displayName} onChange={e => { <Input value={this.state.model.displayName} onChange={e => {
this.updateModelField('displayName', e.target.value); this.updateModelField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("model:Model text"), i18next.t("model:Model text - Tooltip"))} : {Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
</Col> </Col>
<Col span={22}> <Col span={22}>
<TextArea rows={10} value={this.state.model.modelText} onChange={e => { <TextArea rows={10} value={this.state.model.modelText} onChange={e => {
this.updateModelField('modelText', e.target.value); this.updateModelField("modelText", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.model.isEnabled} onChange={checked => { <Switch checked={this.state.model.isEnabled} onChange={checked => {
this.updateModelField('isEnabled', checked); this.updateModelField("isEnabled", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitModelEdit(willExist) { submitModelEdit(willExist) {
@@ -159,19 +159,19 @@ class ModelEditPage extends React.Component {
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model) ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
modelName: this.state.model.name, modelName: this.state.model.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/models`); this.props.history.push("/models");
} else { } else {
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`); this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateModelField('name', this.state.modelName); this.updateModelField("name", this.state.modelName);
} }
}) })
.catch(error => { .catch(error => {
@@ -182,7 +182,7 @@ class ModelEditPage extends React.Component {
deleteModel() { deleteModel() {
ModelBackend.deleteModel(this.state.model) ModelBackend.deleteModel(this.state.model)
.then(() => { .then(() => {
this.props.history.push(`/models`); this.props.history.push("/models");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Model failed to delete: ${error}`); Setting.showMessage("error", `Model failed to delete: ${error}`);
@@ -195,10 +195,10 @@ class ModelEditPage extends React.Component {
{ {
this.state.model !== null ? this.renderModel() : null this.state.model !== null ? this.renderModel() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitModelEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
@@ -31,15 +31,15 @@ class ModelListPage extends BaseListPage {
displayName: `New Model - ${randomName}`, displayName: `New Model - ${randomName}`,
modelText: "", modelText: "",
isEnabled: true, isEnabled: true,
} };
} }
addModel() { addModel() {
const newModel = this.newModel(); const newModel = this.newModel();
ModelBackend.addModel(newModel) ModelBackend.addModel(newModel)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"}); this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Model failed to add: ${error}`); Setting.showMessage("error", `Model failed to add: ${error}`);
@@ -49,12 +49,12 @@ class ModelListPage extends BaseListPage {
deleteModel(i) { deleteModel(i) {
ModelBackend.deleteModel(this.state.data[i]) ModelBackend.deleteModel(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Model deleted successfully`); Setting.showMessage("success", "Model deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Model failed to delete: ${error}`); Setting.showMessage("error", `Model failed to delete: ${error}`);
@@ -65,40 +65,40 @@ class ModelListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'owner', dataIndex: "owner",
key: 'owner', key: "owner",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('owner'), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/models/${text}`}> <Link to={`/models/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -106,43 +106,43 @@ class ModelListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '200px', width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
dataIndex: 'isEnabled', dataIndex: "isEnabled",
key: 'isEnabled', key: "isEnabled",
width: '120px', width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text}/> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete model: ${record.name} ?`} title={`Sure to delete model: ${record.name} ?`}
onConfirm={() => this.deleteModel(index)} onConfirm={() => this.deleteModel(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -156,17 +156,17 @@ class ModelListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered <Table scroll={{x: "max-content"}} columns={columns} dataSource={models} rowKey="name" size="middle" bordered
pagination={paginationProps} pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Models")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Models")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" <Button type="primary" size="small"
onClick={this.addModel.bind(this)}>{i18next.t("general:Add")}</Button> onClick={this.addModel.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as LdapBackend from "./backend/LdapBackend"; import * as LdapBackend from "./backend/LdapBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -22,7 +22,7 @@ import {LinkOutlined} from "@ant-design/icons";
import LdapTable from "./LdapTable"; import LdapTable from "./LdapTable";
import AccountTable from "./AccountTable"; import AccountTable from "./AccountTable";
const { Option } = Select; const {Option} = Select;
class OrganizationEditPage extends React.Component { class OrganizationEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -53,7 +53,7 @@ class OrganizationEditPage extends React.Component {
getLdaps() { getLdaps() {
LdapBackend.getLdaps(this.state.organizationName) LdapBackend.getLdaps(this.state.organizationName)
.then(res => { .then(res => {
let resdata = [] let resdata = [];
if (res.status === "ok") { if (res.status === "ok") {
if (res.data !== null) { if (res.data !== null) {
resdata = res.data; resdata = res.data;
@@ -61,8 +61,8 @@ class OrganizationEditPage extends React.Component {
} }
this.setState({ this.setState({
ldaps: resdata ldaps: resdata
}) });
}) });
} }
parseOrganizationField(key, value) { parseOrganizationField(key, value) {
@@ -88,183 +88,183 @@ class OrganizationEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("organization:New Organization") : i18next.t("organization:Edit Organization")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("organization:New Organization") : i18next.t("organization:Edit Organization")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitOrganizationEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitOrganizationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.name} disabled={this.state.organization.name === "built-in"} onChange={e => { <Input value={this.state.organization.name} disabled={this.state.organization.name === "built-in"} onChange={e => {
this.updateOrganizationField('name', e.target.value); this.updateOrganizationField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.displayName} onChange={e => { <Input value={this.state.organization.displayName} onChange={e => {
this.updateOrganizationField('displayName', e.target.value); this.updateOrganizationField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Favicon"), i18next.t("general:Favicon - Tooltip"))} : {Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.favicon} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.organization.favicon} onChange={e => {
this.updateOrganizationField('favicon', e.target.value); this.updateOrganizationField("favicon", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.organization.favicon}> <a target="_blank" rel="noreferrer" href={this.state.organization.favicon}>
<img src={this.state.organization.favicon} alt={this.state.organization.favicon} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.organization.favicon} alt={this.state.organization.favicon} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </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("organization:Website URL"), i18next.t("organization:Website URL - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Website URL"), i18next.t("organization:Website URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.websiteUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.organization.websiteUrl} onChange={e => {
this.updateOrganizationField('websiteUrl', e.target.value); this.updateOrganizationField("websiteUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Password type"), i18next.t("general:Password type - Tooltip"))} : {Setting.getLabel(i18next.t("general:Password type"), i18next.t("general:Password type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}>
{ {
['plain', 'salt', 'md5-salt', 'bcrypt', 'pbkdf2-salt', 'argon2id'] ["plain", "salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Password salt"), i18next.t("general:Password salt - Tooltip"))} : {Setting.getLabel(i18next.t("general:Password salt"), i18next.t("general:Password salt - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.passwordSalt} onChange={e => { <Input value={this.state.organization.passwordSalt} onChange={e => {
this.updateOrganizationField('passwordSalt', e.target.value); this.updateOrganizationField("passwordSalt", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} : {Setting.getLabel(i18next.t("general:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => { <Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => {
this.updateOrganizationField('phonePrefix', e.target.value); this.updateOrganizationField("phonePrefix", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} : {Setting.getLabel(i18next.t("general:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.defaultAvatar} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.organization.defaultAvatar} onChange={e => {
this.updateOrganizationField('defaultAvatar', e.target.value); this.updateOrganizationField("defaultAvatar", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.organization.defaultAvatar}> <a target="_blank" rel="noreferrer" href={this.state.organization.defaultAvatar}>
<img src={this.state.organization.defaultAvatar} alt={this.state.organization.defaultAvatar} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.organization.defaultAvatar} alt={this.state.organization.defaultAvatar} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </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("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.organization.tags} onChange={(value => {this.updateOrganizationField('tags', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.organization.tags} onChange={(value => {this.updateOrganizationField("tags", value);})}>
{ {
this.state.organization.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>) this.state.organization.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Master password"), i18next.t("general:Master password - Tooltip"))} : {Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.organization.masterPassword} onChange={e => { <Input value={this.state.organization.masterPassword} onChange={e => {
this.updateOrganizationField('masterPassword', e.target.value); this.updateOrganizationField("masterPassword", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.organization.enableSoftDeletion} onChange={checked => { <Switch checked={this.state.organization.enableSoftDeletion} onChange={checked => {
this.updateOrganizationField('enableSoftDeletion', checked); this.updateOrganizationField("enableSoftDeletion", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Is profile public"), i18next.t("organization:Is profile public - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Is profile public"), i18next.t("organization:Is profile public - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.organization.isProfilePublic} onChange={checked => { <Switch checked={this.state.organization.isProfilePublic} onChange={checked => {
this.updateOrganizationField('isProfilePublic', checked); this.updateOrganizationField("isProfilePublic", checked);
}} /> }} />
</Col> </Col>
</Row> </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("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<AccountTable <AccountTable
title={i18next.t("organization:Account items")} title={i18next.t("organization:Account items")}
table={this.state.organization.accountItems} table={this.state.organization.accountItems}
onUpdateTable={(value) => { this.updateOrganizationField('accountItems', value)}} onUpdateTable={(value) => {this.updateOrganizationField("accountItems", value);}}
/> />
</Col> </Col>
</Row> </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"))} :
</Col> </Col>
<Col span={22}> <Col span={22}>
@@ -273,12 +273,13 @@ class OrganizationEditPage extends React.Component {
table={this.state.ldaps} table={this.state.ldaps}
organizationName={this.state.organizationName} organizationName={this.state.organizationName}
onUpdateTable={(value) => { onUpdateTable={(value) => {
this.setState({ldaps: value}) }} this.setState({ldaps: value});
}}
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitOrganizationEdit(willExist) { submitOrganizationEdit(willExist) {
@@ -286,19 +287,19 @@ class OrganizationEditPage extends React.Component {
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization) OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
organizationName: this.state.organization.name, organizationName: this.state.organization.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/organizations`); this.props.history.push("/organizations");
} else { } else {
this.props.history.push(`/organizations/${this.state.organization.name}`); this.props.history.push(`/organizations/${this.state.organization.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateOrganizationField('name', this.state.organizationName); this.updateOrganizationField("name", this.state.organizationName);
} }
}) })
.catch(error => { .catch(error => {
@@ -309,7 +310,7 @@ class OrganizationEditPage extends React.Component {
deleteOrganization() { deleteOrganization() {
OrganizationBackend.deleteOrganization(this.state.organization) OrganizationBackend.deleteOrganization(this.state.organization)
.then(() => { .then(() => {
this.props.history.push(`/organizations`); this.props.history.push("/organizations");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`); Setting.showMessage("error", `Failed to connect to server: ${error}`);
@@ -322,10 +323,10 @@ class OrganizationEditPage extends React.Component {
{ {
this.state.organization !== null ? this.renderOrganization() : null this.state.organization !== null ? this.renderOrganization() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitOrganizationEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitOrganizationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Result, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"}, {name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"}, {name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"}, {name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"}, {name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
@@ -64,15 +66,15 @@ class OrganizationListPage extends BaseListPage {
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"}, {name: "Is deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
], ],
} };
} }
addOrganization() { addOrganization() {
const newOrganization = this.newOrganization(); const newOrganization = this.newOrganization();
OrganizationBackend.addOrganization(newOrganization) OrganizationBackend.addOrganization(newOrganization)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"}); this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Organization failed to add: ${error}`); Setting.showMessage("error", `Organization failed to add: ${error}`);
@@ -82,12 +84,12 @@ class OrganizationListPage extends BaseListPage {
deleteOrganization(i) { deleteOrganization(i) {
OrganizationBackend.deleteOrganization(this.state.data[i]) OrganizationBackend.deleteOrganization(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Organization deleted successfully`); Setting.showMessage("success", "Organization deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Organization failed to delete: ${error}`); Setting.showMessage("error", `Organization failed to delete: ${error}`);
@@ -98,25 +100,25 @@ class OrganizationListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '120px', width: "120px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -124,106 +126,106 @@ class OrganizationListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("organization:Favicon"), title: i18next.t("organization:Favicon"),
dataIndex: 'favicon', dataIndex: "favicon",
key: 'favicon', key: "favicon",
width: '50px', width: "50px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={40} /> <img src={text} alt={text} width={40} />
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("organization:Website URL"), title: i18next.t("organization:Website URL"),
dataIndex: 'websiteUrl', dataIndex: "websiteUrl",
key: 'websiteUrl', key: "websiteUrl",
width: '300px', width: "300px",
sorter: true, sorter: true,
...this.getColumnSearchProps('websiteUrl'), ...this.getColumnSearchProps("websiteUrl"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
{text} {text}
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Password type"), title: i18next.t("general:Password type"),
dataIndex: 'passwordType', dataIndex: "passwordType",
key: 'passwordType', key: "passwordType",
width: '150px', width: "150px",
sorter: true, sorter: true,
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'plain', value: 'plain'}, {text: "plain", value: "plain"},
{text: 'salt', value: 'salt'}, {text: "salt", value: "salt"},
{text: 'md5-salt', value: 'md5-salt'}, {text: "md5-salt", value: "md5-salt"},
], ],
}, },
{ {
title: i18next.t("general:Password salt"), title: i18next.t("general:Password salt"),
dataIndex: 'passwordSalt', dataIndex: "passwordSalt",
key: 'passwordSalt', key: "passwordSalt",
width: '150px', width: "150px",
sorter: true, sorter: true,
...this.getColumnSearchProps('passwordSalt'), ...this.getColumnSearchProps("passwordSalt"),
}, },
{ {
title: i18next.t("organization:Default avatar"), title: i18next.t("organization:Default avatar"),
dataIndex: 'defaultAvatar', dataIndex: "defaultAvatar",
key: 'defaultAvatar', key: "defaultAvatar",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={40} /> <img src={text} alt={text} width={40} />
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("organization:Soft deletion"), title: i18next.t("organization:Soft deletion"),
dataIndex: 'enableSoftDeletion', dataIndex: "enableSoftDeletion",
key: 'enableSoftDeletion', key: "enableSoftDeletion",
width: '140px', width: "140px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '240px', width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete organization: ${record.name} ?`} title={`Sure to delete organization: ${record.name} ?`}
onConfirm={() => this.deleteOrganization(index)} onConfirm={() => this.deleteOrganization(index)}
disabled={record.name === "built-in"} disabled={record.name === "built-in"}
> >
<Button style={{marginBottom: '10px'}} disabled={record.name === "built-in"} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} disabled={record.name === "built-in"} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -235,17 +237,28 @@ class OrganizationListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Organizations")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Organizations")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -258,7 +271,7 @@ class OrganizationListPage extends BaseListPage {
field = "passwordType"; field = "passwordType";
value = params.passwordType; value = params.passwordType;
} }
this.setState({ loading: true }); this.setState({loading: true});
OrganizationBackend.getOrganizations("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) OrganizationBackend.getOrganizations("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
@@ -272,6 +285,13 @@ class OrganizationListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Modal, Row, Input,} from "antd"; import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
@@ -50,17 +50,16 @@ export const PasswordModal = (props) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", i18next.t("user:Password Set")); Setting.showMessage("success", i18next.t("user:Password Set"));
setVisible(false); setVisible(false);
} } else {Setting.showMessage("error", i18next.t(`user:${res.msg}`));}
else Setting.showMessage("error", i18next.t(`user:${res.msg}`)); });
}) };
}
let hasOldPassword = user.password !== ""; let hasOldPassword = user.password !== "";
return ( return (
<Row> <Row>
<Button type="default" disabled={props.disabled} onClick={showModal}> <Button type="default" disabled={props.disabled} onClick={showModal}>
{ hasOldPassword ? i18next.t("user:Modify password...") : i18next.t("user:Set password...")} {hasOldPassword ? i18next.t("user:Modify password...") : i18next.t("user:Set password...")}
</Button> </Button>
<Modal <Modal
maskClosable={false} maskClosable={false}
@@ -74,21 +73,21 @@ export const PasswordModal = (props) => {
width={600} width={600}
> >
<Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}> <Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}>
{ (hasOldPassword && !Setting.isAdminUser(account)) ? ( {(hasOldPassword && !Setting.isAdminUser(account)) ? (
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>
<Input.Password addonBefore={i18next.t("user:Old Password")} placeholder={i18next.t("user:input password")} onChange={(e) => setOldPassword(e.target.value)}/> <Input.Password addonBefore={i18next.t("user:Old Password")} placeholder={i18next.t("user:input password")} onChange={(e) => setOldPassword(e.target.value)} />
</Row> </Row>
) : null} ) : null}
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>
<Input.Password addonBefore={i18next.t("user:New Password")} placeholder={i18next.t("user:input password")} onChange={(e) => setNewPassword(e.target.value)}/> <Input.Password addonBefore={i18next.t("user:New Password")} placeholder={i18next.t("user:input password")} onChange={(e) => setNewPassword(e.target.value)} />
</Row> </Row>
<Row style={{width: "100%", marginBottom: "20px"}}> <Row style={{width: "100%", marginBottom: "20px"}}>
<Input.Password addonBefore={i18next.t("user:Re-enter New")} placeholder={i18next.t("user:input password")} onChange={(e) => setRePassword(e.target.value)}/> <Input.Password addonBefore={i18next.t("user:Re-enter New")} placeholder={i18next.t("user:input password")} onChange={(e) => setRePassword(e.target.value)} />
</Row> </Row>
</Col> </Col>
</Modal> </Modal>
</Row> </Row>
) );
} };
export default PasswordModal; export default PasswordModal;

View File

@@ -13,13 +13,13 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Descriptions, Input, Modal, Row, Select} from 'antd'; import {Button, Card, Col, Descriptions, Input, Modal, Row, Select} from "antd";
import {InfoCircleTwoTone} from "@ant-design/icons"; import {InfoCircleTwoTone} from "@ant-design/icons";
import * as PaymentBackend from "./backend/PaymentBackend"; import * as PaymentBackend from "./backend/PaymentBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
const { Option } = Select; const {Option} = Select;
class PaymentEditPage extends React.Component { class PaymentEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -79,7 +79,7 @@ class PaymentEditPage extends React.Component {
isInvoiceLoading: false, isInvoiceLoading: false,
}); });
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully invoiced`); Setting.showMessage("success", "Successfully invoiced");
Setting.openLinkSafe(res.data); Setting.openLinkSafe(res.data);
this.getPayment(); this.getPayment();
} else { } else {
@@ -117,17 +117,17 @@ class PaymentEditPage extends React.Component {
{" " + i18next.t("payment:Confirm your invoice information")} {" " + i18next.t("payment:Confirm your invoice information")}
</div> </div>
} }
visible={this.state.isModalVisible} visible={this.state.isModalVisible}
onOk={handleIssueInvoice} onOk={handleIssueInvoice}
onCancel={handleCancel} onCancel={handleCancel}
okText={i18next.t("payment:Issue Invoice")} okText={i18next.t("payment:Issue Invoice")}
cancelText={i18next.t("general:Cancel")}> cancelText={i18next.t("general:Cancel")}>
<p> <p>
{ {
i18next.t("payment:Please carefully check your invoice information. Once the invoice is issued, it cannot be withdrawn or modified.") i18next.t("payment:Please carefully check your invoice information. Once the invoice is issued, it cannot be withdrawn or modified.")
} }
<br/> <br />
<br/> <br />
<Descriptions size={"small"} bordered> <Descriptions size={"small"} bordered>
<Descriptions.Item label={i18next.t("payment:Person name")} span={3}>{this.state.payment?.personName}</Descriptions.Item> <Descriptions.Item label={i18next.t("payment:Person name")} span={3}>{this.state.payment?.personName}</Descriptions.Item>
<Descriptions.Item label={i18next.t("payment:Person ID card")} span={3}>{this.state.payment?.personIdCard}</Descriptions.Item> <Descriptions.Item label={i18next.t("payment:Person ID card")} span={3}>{this.state.payment?.personIdCard}</Descriptions.Item>
@@ -140,7 +140,7 @@ class PaymentEditPage extends React.Component {
</Descriptions> </Descriptions>
</p> </p>
</Modal> </Modal>
) );
} }
renderPayment() { renderPayment() {
@@ -149,12 +149,12 @@ class PaymentEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("payment:New Payment") : i18next.t("payment:Edit Payment")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("payment:New Payment") : i18next.t("payment:Edit Payment")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -163,8 +163,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -173,18 +173,18 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={true} value={this.state.payment.displayName} onChange={e => { <Input disabled={true} value={this.state.payment.displayName} onChange={e => {
this.updatePaymentField('displayName', e.target.value); this.updatePaymentField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Provider"), i18next.t("general:Provider - Tooltip"))} : {Setting.getLabel(i18next.t("general:Provider"), i18next.t("general:Provider - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -193,8 +193,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Type"), i18next.t("payment:Type - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -203,8 +203,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Product"), i18next.t("payment:Product - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -213,8 +213,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Price"), i18next.t("payment:Price - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -223,8 +223,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Currency"), i18next.t("payment:Currency - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -233,8 +233,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:State"), i18next.t("payment:State - Tooltip"))} : {Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -243,8 +243,8 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Message"), i18next.t("payment:Message - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Message"), i18next.t("payment:Message - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -253,113 +253,113 @@ class PaymentEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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("payment:Person name"), i18next.t("payment:Person name - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Person name"), i18next.t("payment:Person name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personName} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personName} onChange={e => {
this.updatePaymentField('personName', e.target.value); this.updatePaymentField("personName", e.target.value);
if (this.state.payment.invoiceType === "Individual") { if (this.state.payment.invoiceType === "Individual") {
this.updatePaymentField('invoiceTitle', e.target.value); this.updatePaymentField("invoiceTitle", e.target.value);
this.updatePaymentField('invoiceTaxId', ""); this.updatePaymentField("invoiceTaxId", "");
} }
}} /> }} />
</Col> </Col>
</Row> </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("payment:Person ID card"), i18next.t("payment:Person ID card - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Person ID card"), i18next.t("payment:Person ID card - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personIdCard} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personIdCard} onChange={e => {
this.updatePaymentField('personIdCard', e.target.value); this.updatePaymentField("personIdCard", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Person Email"), i18next.t("payment:Person Email - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Person Email"), i18next.t("payment:Person Email - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personEmail} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personEmail} onChange={e => {
this.updatePaymentField('personEmail', e.target.value); this.updatePaymentField("personEmail", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Person phone"), i18next.t("payment:Person phone - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Person phone"), i18next.t("payment:Person phone - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personPhone} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.personPhone} onChange={e => {
this.updatePaymentField('personPhone', e.target.value); this.updatePaymentField("personPhone", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Invoice type"), i18next.t("payment:Invoice type - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice type"), i18next.t("payment:Invoice type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select disabled={this.state.payment.invoiceUrl !== ""} virtual={false} style={{width: '100%'}} value={this.state.payment.invoiceType} onChange={(value => { <Select disabled={this.state.payment.invoiceUrl !== ""} virtual={false} style={{width: "100%"}} value={this.state.payment.invoiceType} onChange={(value => {
this.updatePaymentField('invoiceType', value); this.updatePaymentField("invoiceType", value);
if (value === "Individual") { if (value === "Individual") {
this.updatePaymentField('invoiceTitle', this.state.payment.personName); this.updatePaymentField("invoiceTitle", this.state.payment.personName);
this.updatePaymentField('invoiceTaxId', ""); this.updatePaymentField("invoiceTaxId", "");
} }
})}> })}>
{ {
[ [
{id: 'Individual', name: i18next.t("payment:Individual")}, {id: "Individual", name: i18next.t("payment:Individual")},
{id: 'Organization', name: i18next.t("payment:Organization")}, {id: "Organization", name: i18next.t("payment:Organization")},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("payment:Invoice title"), i18next.t("payment:Invoice title - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice title"), i18next.t("payment:Invoice title - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTitle} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTitle} onChange={e => {
this.updatePaymentField('invoiceTitle', e.target.value); this.updatePaymentField("invoiceTitle", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Invoice tax ID"), i18next.t("payment:Invoice tax ID - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice tax ID"), i18next.t("payment:Invoice tax ID - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTaxId} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== "" || this.state.payment.invoiceType === "Individual"} value={this.state.payment.invoiceTaxId} onChange={e => {
this.updatePaymentField('invoiceTaxId', e.target.value); this.updatePaymentField("invoiceTaxId", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Invoice remark"), i18next.t("payment:Invoice remark - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice remark"), i18next.t("payment:Invoice remark - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.invoiceRemark} onChange={e => { <Input disabled={this.state.payment.invoiceUrl !== ""} value={this.state.payment.invoiceRemark} onChange={e => {
this.updatePaymentField('invoiceRemark', e.target.value); this.updatePaymentField("invoiceRemark", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("payment:Invoice URL"), i18next.t("payment:Invoice URL - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice URL"), i18next.t("payment:Invoice URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={true} value={this.state.payment.invoiceUrl} onChange={e => { <Input disabled={true} value={this.state.payment.invoiceUrl} onChange={e => {
this.updatePaymentField('invoiceUrl', e.target.value); this.updatePaymentField("invoiceUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row id={"invoice-area"} style={{marginTop: '20px'}} > <Row id={"invoice-area"} 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("payment:Invoice actions"), i18next.t("payment:Invoice actions - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Invoice actions"), i18next.t("payment:Invoice actions - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -384,7 +384,7 @@ class PaymentEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
checkError() { checkError() {
@@ -444,19 +444,19 @@ class PaymentEditPage extends React.Component {
PaymentBackend.updatePayment(this.state.payment.owner, this.state.paymentName, payment) PaymentBackend.updatePayment(this.state.payment.owner, this.state.paymentName, payment)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
paymentName: this.state.payment.name, paymentName: this.state.payment.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/payments`); this.props.history.push("/payments");
} else { } else {
this.props.history.push(`/payments/${this.state.payment.name}`); this.props.history.push(`/payments/${this.state.payment.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updatePaymentField('name', this.state.paymentName); this.updatePaymentField("name", this.state.paymentName);
} }
}) })
.catch(error => { .catch(error => {
@@ -467,7 +467,7 @@ class PaymentEditPage extends React.Component {
deletePayment() { deletePayment() {
PaymentBackend.deletePayment(this.state.payment) PaymentBackend.deletePayment(this.state.payment)
.then(() => { .then(() => {
this.props.history.push(`/payments`); this.props.history.push("/payments");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Payment failed to delete: ${error}`); Setting.showMessage("error", `Payment failed to delete: ${error}`);
@@ -483,10 +483,10 @@ class PaymentEditPage extends React.Component {
{ {
this.renderModal() this.renderModal()
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitPaymentEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Table} from 'antd'; import {Button, Popconfirm, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as PaymentBackend from "./backend/PaymentBackend"; import * as PaymentBackend from "./backend/PaymentBackend";
@@ -44,15 +44,15 @@ class PaymentListPage extends BaseListPage {
returnUrl: "https://door.casdoor.com/payments", returnUrl: "https://door.casdoor.com/payments",
state: "Paid", state: "Paid",
message: "", message: "",
} };
} }
addPayment() { addPayment() {
const newPayment = this.newPayment(); const newPayment = this.newPayment();
PaymentBackend.addPayment(newPayment) PaymentBackend.addPayment(newPayment)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/payments/${newPayment.name}`, mode: "add"}); this.props.history.push({pathname: `/payments/${newPayment.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Payment failed to add: ${error}`); Setting.showMessage("error", `Payment failed to add: ${error}`);
@@ -62,12 +62,12 @@ class PaymentListPage extends BaseListPage {
deletePayment(i) { deletePayment(i) {
PaymentBackend.deletePayment(this.state.data[i]) PaymentBackend.deletePayment(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Payment deleted successfully`); Setting.showMessage("success", "Payment deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Payment failed to delete: ${error}`); Setting.showMessage("error", `Payment failed to delete: ${error}`);
@@ -78,55 +78,55 @@ class PaymentListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
dataIndex: 'user', dataIndex: "user",
key: 'user', key: "user",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('user'), ...this.getColumnSearchProps("user"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/users/${record.organization}/${text}`}> <Link to={`/users/${record.organization}/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '180px', width: "180px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/payments/${text}`}> <Link to={`/payments/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -142,28 +142,28 @@ class PaymentListPage extends BaseListPage {
// }, // },
{ {
title: i18next.t("general:Provider"), title: i18next.t("general:Provider"),
dataIndex: 'provider', dataIndex: "provider",
key: 'provider', key: "provider",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('provider'), ...this.getColumnSearchProps("provider"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/providers/${text}`}> <Link to={`/providers/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("payment:Type"), title: i18next.t("payment:Type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
width: '140px', width: "140px",
align: 'center', align: "center",
filterMultiple: false, filterMultiple: false,
filters: Setting.getProviderTypeOptions('Payment').map((o) => {return {text:o.id, value:o.name}}), filters: Setting.getProviderTypeOptions("Payment").map((o) => {return {text:o.id, value:o.name};}),
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
record.category = "Payment"; record.category = "Payment";
@@ -172,55 +172,55 @@ class PaymentListPage extends BaseListPage {
}, },
{ {
title: i18next.t("payment:Product"), title: i18next.t("payment:Product"),
dataIndex: 'productDisplayName', dataIndex: "productDisplayName",
key: 'productDisplayName', key: "productDisplayName",
// width: '160px', // width: '160px',
sorter: true, sorter: true,
...this.getColumnSearchProps('productDisplayName'), ...this.getColumnSearchProps("productDisplayName"),
}, },
{ {
title: i18next.t("payment:Price"), title: i18next.t("payment:Price"),
dataIndex: 'price', dataIndex: "price",
key: 'price', key: "price",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('price'), ...this.getColumnSearchProps("price"),
}, },
{ {
title: i18next.t("payment:Currency"), title: i18next.t("payment:Currency"),
dataIndex: 'currency', dataIndex: "currency",
key: 'currency', key: "currency",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('currency'), ...this.getColumnSearchProps("currency"),
}, },
{ {
title: i18next.t("payment:State"), title: i18next.t("payment:State"),
dataIndex: 'state', dataIndex: "state",
key: 'state', key: "state",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('state'), ...this.getColumnSearchProps("state"),
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '240px', width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete payment: ${record.name} ?`} title={`Sure to delete payment: ${record.name} ?`}
onConfirm={() => this.deletePayment(index)} onConfirm={() => this.deletePayment(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -234,15 +234,15 @@ class PaymentListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={payments} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={payments} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Payments")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Payments")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addPayment.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addPayment.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -255,7 +255,7 @@ class PaymentListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
PaymentBackend.getPayments("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) PaymentBackend.getPayments("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Result, Spin} from 'antd'; import {Button, Result, Spin} from "antd";
import * as PaymentBackend from "./backend/PaymentBackend"; import * as PaymentBackend from "./backend/PaymentBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -71,7 +71,7 @@ class PaymentResultPage extends React.Component {
]} ]}
/> />
</div> </div>
) );
} else if (payment.state === "Created") { } else if (payment.state === "Created") {
return ( return (
<div> <div>
@@ -87,7 +87,7 @@ class PaymentResultPage extends React.Component {
]} ]}
/> />
</div> </div>
) );
} else { } else {
return ( return (
<div> <div>
@@ -107,7 +107,7 @@ class PaymentResultPage extends React.Component {
]} ]}
/> />
</div> </div>
) );
} }
} }
} }

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as PermissionBackend from "./backend/PermissionBackend"; import * as PermissionBackend from "./backend/PermissionBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
@@ -21,8 +21,9 @@ import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import * as RoleBackend from "./backend/RoleBackend"; import * as RoleBackend from "./backend/RoleBackend";
import * as ModelBackend from "./backend/ModelBackend"; import * as ModelBackend from "./backend/ModelBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend";
const { Option } = Select; const {Option} = Select;
class PermissionEditPage extends React.Component { class PermissionEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -36,6 +37,7 @@ class PermissionEditPage extends React.Component {
users: [], users: [],
roles: [], roles: [],
models: [], models: [],
resources: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
}; };
} }
@@ -55,6 +57,7 @@ class PermissionEditPage extends React.Component {
this.getUsers(permission.owner); this.getUsers(permission.owner);
this.getRoles(permission.owner); this.getRoles(permission.owner);
this.getModels(permission.owner); this.getModels(permission.owner);
this.getResources(permission.owner);
}); });
} }
@@ -94,6 +97,15 @@ class PermissionEditPage extends React.Component {
}); });
} }
getResources(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
resources: (res.msg === undefined) ? res : [],
});
});
}
parsePermissionField(key, value) { parsePermissionField(key, value) {
if ([""].includes(key)) { if ([""].includes(key)) {
value = Setting.myParseInt(value); value = Setting.myParseInt(value);
@@ -117,20 +129,22 @@ class PermissionEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("permission:New Permission") : i18next.t("permission:Edit Permission")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("permission:New Permission") : i18next.t("permission:Edit Permission")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.owner} onChange={(owner => { <Select virtual={false} style={{width: "100%"}} value={this.state.permission.owner} onChange={(owner => {
this.updatePermissionField('owner', owner); this.updatePermissionField("owner", owner);
this.getUsers(owner); this.getUsers(owner);
this.getRoles(owner); this.getRoles(owner);
this.getModels(owner);
this.getResources(owner);
})}> })}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
@@ -138,33 +152,33 @@ class PermissionEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.permission.name} onChange={e => { <Input value={this.state.permission.name} onChange={e => {
this.updatePermissionField('name', e.target.value); this.updatePermissionField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.permission.displayName} onChange={e => { <Input value={this.state.permission.displayName} onChange={e => {
this.updatePermissionField('displayName', e.target.value); this.updatePermissionField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Model"), i18next.t("general:Model - Tooltip"))} : {Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.model} onChange={(model => { <Select virtual={false} style={{width: "100%"}} value={this.state.permission.model} onChange={(model => {
this.updatePermissionField('model', model); this.updatePermissionField("model", model);
})}> })}>
{ {
this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>) this.state.models.map((model, index) => <Option key={index} value={model.name}>{model.name}</Option>)
@@ -172,93 +186,105 @@ class PermissionEditPage extends React.Component {
</Select> </Select>
</Col> </Col>
</Row> </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("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.users} onChange={(value => {this.updatePermissionField('users', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.users} onChange={(value => {this.updatePermissionField("users", value);})}>
{ {
this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>) this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField('roles', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField("roles", value);})}>
{ {
this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>) this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("permission:Resource type"), i18next.t("permission:Resource type - Tooltip"))} : {Setting.getLabel(i18next.t("permission:Resource type"), i18next.t("permission:Resource type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.resourceType} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.permission.resourceType} onChange={(value => {
this.updatePermissionField('resourceType', value); this.updatePermissionField("resourceType", value);
})}> })}>
{ {
[ [
{id: 'Application', name: 'Application'}, {id: "Application", name: "Application"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("permission:Resources"), i18next.t("permission:Resources - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.resources} onChange={(value => {this.updatePermissionField("resources", value);})}>
{
this.state.resources.map((resource, index) => <Option key={index} value={`${resource.name}`}>{`${resource.name}`}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Actions"), i18next.t("permission:Actions - Tooltip"))} : {Setting.getLabel(i18next.t("permission:Actions"), i18next.t("permission:Actions - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.actions} onChange={(value => { <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.actions} onChange={(value => {
this.updatePermissionField('actions', value); this.updatePermissionField("actions", value);
})}> })}>
{ {
[ [
{id: 'Read', name: 'Read'}, {id: "Read", name: "Read"},
{id: 'Write', name: 'Write'}, {id: "Write", name: "Write"},
{id: 'Admin', name: 'Admin'}, {id: "Admin", name: "Admin"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("permission:Effect"), i18next.t("permission:Effect - Tooltip"))} : {Setting.getLabel(i18next.t("permission:Effect"), i18next.t("permission:Effect - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.effect} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.permission.effect} onChange={(value => {
this.updatePermissionField('effect', value); this.updatePermissionField("effect", value);
})}> })}>
{ {
[ [
{id: 'Allow', name: 'Allow'}, {id: "Allow", name: "Allow"},
{id: 'Deny', name: 'Deny'}, {id: "Deny", name: "Deny"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.permission.isEnabled} onChange={checked => { <Switch checked={this.state.permission.isEnabled} onChange={checked => {
this.updatePermissionField('isEnabled', checked); this.updatePermissionField("isEnabled", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitPermissionEdit(willExist) { submitPermissionEdit(willExist) {
@@ -266,19 +292,19 @@ class PermissionEditPage extends React.Component {
PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission) PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
permissionName: this.state.permission.name, permissionName: this.state.permission.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/permissions`); this.props.history.push("/permissions");
} else { } else {
this.props.history.push(`/permissions/${this.state.permission.owner}/${this.state.permission.name}`); this.props.history.push(`/permissions/${this.state.permission.owner}/${this.state.permission.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updatePermissionField('name', this.state.permissionName); this.updatePermissionField("name", this.state.permissionName);
} }
}) })
.catch(error => { .catch(error => {
@@ -289,7 +315,7 @@ class PermissionEditPage extends React.Component {
deletePermission() { deletePermission() {
PermissionBackend.deletePermission(this.state.permission) PermissionBackend.deletePermission(this.state.permission)
.then(() => { .then(() => {
this.props.history.push(`/permissions`); this.props.history.push("/permissions");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Permission failed to delete: ${error}`); Setting.showMessage("error", `Permission failed to delete: ${error}`);
@@ -302,10 +328,10 @@ class PermissionEditPage extends React.Component {
{ {
this.state.permission !== null ? this.renderPermission() : null this.state.permission !== null ? this.renderPermission() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitPermissionEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as PermissionBackend from "./backend/PermissionBackend"; import * as PermissionBackend from "./backend/PermissionBackend";
@@ -33,18 +33,18 @@ class PermissionListPage extends BaseListPage {
roles: [], roles: [],
resourceType: "Application", resourceType: "Application",
resources: ["app-built-in"], resources: ["app-built-in"],
action: "Read", actions: ["Read"],
effect: "Allow", effect: "Allow",
isEnabled: true, isEnabled: true,
} };
} }
addPermission() { addPermission() {
const newPermission = this.newPermission(); const newPermission = this.newPermission();
PermissionBackend.addPermission(newPermission) PermissionBackend.addPermission(newPermission)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/permissions/${newPermission.owner}/${newPermission.name}`, mode: "add"}); this.props.history.push({pathname: `/permissions/${newPermission.owner}/${newPermission.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Permission failed to add: ${error}`); Setting.showMessage("error", `Permission failed to add: ${error}`);
@@ -54,12 +54,12 @@ class PermissionListPage extends BaseListPage {
deletePermission(i) { deletePermission(i) {
PermissionBackend.deletePermission(this.state.data[i]) PermissionBackend.deletePermission(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Permission deleted successfully`); Setting.showMessage("success", "Permission deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Permission failed to delete: ${error}`); Setting.showMessage("error", `Permission failed to delete: ${error}`);
@@ -70,40 +70,40 @@ class PermissionListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'owner', dataIndex: "owner",
key: 'owner', key: "owner",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('owner'), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/permissions/${record.owner}/${text}`}> <Link to={`/permissions/${record.owner}/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -111,109 +111,109 @@ class PermissionListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '160px', width: "160px",
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("role:Sub users"), title: i18next.t("role:Sub users"),
dataIndex: 'users', dataIndex: "users",
key: 'users', key: "users",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('users'), ...this.getColumnSearchProps("users"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("role:Sub roles"), title: i18next.t("role:Sub roles"),
dataIndex: 'roles', dataIndex: "roles",
key: 'roles', key: "roles",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('roles'), ...this.getColumnSearchProps("roles"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("permission:Resource type"), title: i18next.t("permission:Resource type"),
dataIndex: 'resourceType', dataIndex: "resourceType",
key: 'resourceType', key: "resourceType",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'Application', value: 'Application'}, {text: "Application", value: "Application"},
], ],
width: '170px', width: "170px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("permission:Resources"), title: i18next.t("permission:Resources"),
dataIndex: 'resources', dataIndex: "resources",
key: 'resources', key: "resources",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('resources'), ...this.getColumnSearchProps("resources"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("permission:Actions"), title: i18next.t("permission:Actions"),
dataIndex: 'actions', dataIndex: "actions",
key: 'actions', key: "actions",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('actions'), ...this.getColumnSearchProps("actions"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("permission:Effect"), title: i18next.t("permission:Effect"),
dataIndex: 'effect', dataIndex: "effect",
key: 'effect', key: "effect",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'Allow', value: 'Allow'}, {text: "Allow", value: "Allow"},
{text: 'Deny', value: 'Deny'}, {text: "Deny", value: "Deny"},
], ],
width: '120px', width: "120px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
dataIndex: 'isEnabled', dataIndex: "isEnabled",
key: 'isEnabled', key: "isEnabled",
width: '120px', width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete permission: ${record.name} ?`} title={`Sure to delete permission: ${record.name} ?`}
onConfirm={() => this.deletePermission(index)} onConfirm={() => this.deletePermission(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -227,15 +227,15 @@ class PermissionListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={permissions} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={permissions} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Permissions")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Permissions")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addPermission.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -248,7 +248,7 @@ class PermissionListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
PermissionBackend.getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) PermissionBackend.getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -94,7 +94,7 @@ class ProductBuyPage extends React.Component {
let providerMap = {}; let providerMap = {};
this.state.providers.forEach(provider => { this.state.providers.forEach(provider => {
providerMap[provider.name] = provider; providerMap[provider.name] = provider;
}) });
return product.providers.map(providerName => providerMap[providerName]); return product.providers.map(providerName => providerMap[providerName]);
} }
@@ -153,7 +153,7 @@ class ProductBuyPage extends React.Component {
text text
} }
</Button> </Button>
) );
} }
renderProviderButton(provider, product) { renderProviderButton(provider, product) {
@@ -165,7 +165,7 @@ class ProductBuyPage extends React.Component {
} }
</span> </span>
</span> </span>
) );
} }
renderPay(product) { renderPay(product) {
@@ -183,7 +183,7 @@ class ProductBuyPage extends React.Component {
const providers = this.getProviders(product); const providers = this.getProviders(product);
return providers.map(provider => { return providers.map(provider => {
return this.renderProviderButton(provider, product); return this.renderProviderButton(provider, product);
}) });
} }
render() { render() {
@@ -198,22 +198,22 @@ class ProductBuyPage extends React.Component {
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} > <Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
<Descriptions title={i18next.t("product:Buy Product")} bordered> <Descriptions title={i18next.t("product:Buy Product")} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}> <Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 28}}> <span style={{fontSize: 28}}>
{product?.displayName} {product?.displayName}
</span> </span>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Image")} span={3}> <Descriptions.Item label={i18next.t("product:Image")} span={3}>
<img src={product?.image} alt={product?.name} height={90} style={{marginBottom: '20px'}}/> <img src={product?.image} alt={product?.name} height={90} style={{marginBottom: "20px"}} />
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}> <Descriptions.Item label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}> <span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{ {
this.getPrice(product) this.getPrice(product)
} }
</span> </span>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
@@ -225,7 +225,7 @@ class ProductBuyPage extends React.Component {
</Descriptions> </Descriptions>
</Spin> </Spin>
</div> </div>
) );
} }
} }

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} from 'antd'; import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as ProductBackend from "./backend/ProductBackend"; import * as ProductBackend from "./backend/ProductBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -21,7 +21,7 @@ import {LinkOutlined} from "@ant-design/icons";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import ProductBuyPage from "./ProductBuyPage"; import ProductBuyPage from "./ProductBuyPage";
const { Option } = Select; const {Option} = Select;
class ProductEditPage extends React.Component { class ProductEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -82,165 +82,165 @@ class ProductEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("product:New Product") : i18next.t("product:Edit Product")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("product:New Product") : i18next.t("product:Edit Product")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.product.name} onChange={e => { <Input value={this.state.product.name} onChange={e => {
this.updateProductField('name', e.target.value); this.updateProductField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.product.displayName} onChange={e => { <Input value={this.state.product.displayName} onChange={e => {
this.updateProductField('displayName', e.target.value); this.updateProductField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} : {Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
</Col> </Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}> <Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.product.image} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.product.image} onChange={e => {
this.updateProductField('image', e.target.value); this.updateProductField("image", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.product.image}> <a target="_blank" rel="noreferrer" href={this.state.product.image}>
<img src={this.state.product.image} alt={this.state.product.image} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.product.image} alt={this.state.product.image} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Tag"), i18next.t("product:Tag - Tooltip"))} : {Setting.getLabel(i18next.t("product:Tag"), i18next.t("product:Tag - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.product.tag} onChange={e => { <Input value={this.state.product.tag} onChange={e => {
this.updateProductField('tag', e.target.value); this.updateProductField("tag", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Detail"), i18next.t("product:Detail - Tooltip"))} : {Setting.getLabel(i18next.t("product:Detail"), i18next.t("product:Detail - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.product.detail} onChange={e => { <Input value={this.state.product.detail} onChange={e => {
this.updateProductField('detail', e.target.value); this.updateProductField("detail", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} : {Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.product.currency} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} onChange={(value => {
this.updateProductField('currency', value); this.updateProductField("currency", value);
})}> })}>
{ {
[ [
{id: 'USD', name: 'USD'}, {id: "USD", name: "USD"},
{id: 'CNY', name: 'CNY'}, {id: "CNY", name: "CNY"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} : {Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.product.price} onChange={value => { <InputNumber value={this.state.product.price} onChange={value => {
this.updateProductField('price', value); this.updateProductField("price", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} : {Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.product.quantity} onChange={value => { <InputNumber value={this.state.product.quantity} onChange={value => {
this.updateProductField('quantity', value); this.updateProductField("quantity", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} : {Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.product.sold} onChange={value => { <InputNumber value={this.state.product.sold} onChange={value => {
this.updateProductField('sold', value); this.updateProductField("sold", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} : {Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.product.providers} onChange={(value => {this.updateProductField('providers', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.product.providers} onChange={(value => {this.updateProductField("providers", value);})}>
{ {
this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>) this.state.providers.map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} : {Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.product.returnUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.product.returnUrl} onChange={e => {
this.updateProductField('returnUrl', e.target.value); this.updateProductField("returnUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:State"), i18next.t("general:State - Tooltip"))} : {Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.product.state} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.product.state} onChange={(value => {
this.updateProductField('state', value); this.updateProductField("state", value);
})}> })}>
{ {
[ [
{id: 'Published', name: 'Published'}, {id: "Published", name: "Published"},
{id: 'Draft', name: 'Draft'}, {id: "Draft", name: "Draft"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Preview"), i18next.t("general:Preview - Tooltip"))} : {Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col> </Col>
{ {
@@ -248,7 +248,7 @@ class ProductEditPage extends React.Component {
} }
</Row> </Row>
</Card> </Card>
) );
} }
renderPreview() { renderPreview() {
@@ -258,13 +258,13 @@ class ProductEditPage extends React.Component {
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={buyUrl}> <a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={buyUrl}>
<Button type="primary">{i18next.t("product:Test buy page..")}</Button> <Button type="primary">{i18next.t("product:Test buy page..")}</Button>
</a> </a>
<br/> <br />
<br/> <br />
<div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}> <div style={{width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
<ProductBuyPage product={this.state.product} /> <ProductBuyPage product={this.state.product} />
</div> </div>
</Col> </Col>
) );
} }
submitProductEdit(willExist) { submitProductEdit(willExist) {
@@ -272,19 +272,19 @@ class ProductEditPage extends React.Component {
ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product) ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
productName: this.state.product.name, productName: this.state.product.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/products`); this.props.history.push("/products");
} else { } else {
this.props.history.push(`/products/${this.state.product.name}`); this.props.history.push(`/products/${this.state.product.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateProductField('name', this.state.productName); this.updateProductField("name", this.state.productName);
} }
}) })
.catch(error => { .catch(error => {
@@ -295,7 +295,7 @@ class ProductEditPage extends React.Component {
deleteProduct() { deleteProduct() {
ProductBackend.deleteProduct(this.state.product) ProductBackend.deleteProduct(this.state.product)
.then(() => { .then(() => {
this.props.history.push(`/products`); this.props.history.push("/products");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Product failed to delete: ${error}`); Setting.showMessage("error", `Product failed to delete: ${error}`);
@@ -308,10 +308,10 @@ class ProductEditPage extends React.Component {
{ {
this.state.product !== null ? this.renderProduct() : null this.state.product !== null ? this.renderProduct() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitProductEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Col, List, Popconfirm, Row, Table, Tooltip} from 'antd'; import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProductBackend from "./backend/ProductBackend"; import * as ProductBackend from "./backend/ProductBackend";
@@ -38,15 +38,15 @@ class ProductListPage extends BaseListPage {
sold: 10, sold: 10,
providers: [], providers: [],
state: "Published", state: "Published",
} };
} }
addProduct() { addProduct() {
const newProduct = this.newProduct(); const newProduct = this.newProduct();
ProductBackend.addProduct(newProduct) ProductBackend.addProduct(newProduct)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"}); this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Product failed to add: ${error}`); Setting.showMessage("error", `Product failed to add: ${error}`);
@@ -56,12 +56,12 @@ class ProductListPage extends BaseListPage {
deleteProduct(i) { deleteProduct(i) {
ProductBackend.deleteProduct(this.state.data[i]) ProductBackend.deleteProduct(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Product deleted successfully`); Setting.showMessage("success", "Product deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Product failed to delete: ${error}`); Setting.showMessage("error", `Product failed to delete: ${error}`);
@@ -72,25 +72,25 @@ class ProductListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '140px', width: "140px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/products/${text}`}> <Link to={`/products/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -98,79 +98,79 @@ class ProductListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '170px', width: "170px",
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("product:Image"), title: i18next.t("product:Image"),
dataIndex: 'image', dataIndex: "image",
key: 'image', key: "image",
width: '170px', width: "170px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={150} /> <img src={text} alt={text} width={150} />
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("product:Tag"), title: i18next.t("product:Tag"),
dataIndex: 'tag', dataIndex: "tag",
key: 'tag', key: "tag",
width: '160px', width: "160px",
sorter: true, sorter: true,
...this.getColumnSearchProps('tag'), ...this.getColumnSearchProps("tag"),
}, },
{ {
title: i18next.t("product:Currency"), title: i18next.t("product:Currency"),
dataIndex: 'currency', dataIndex: "currency",
key: 'currency', key: "currency",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('currency'), ...this.getColumnSearchProps("currency"),
}, },
{ {
title: i18next.t("product:Price"), title: i18next.t("product:Price"),
dataIndex: 'price', dataIndex: "price",
key: 'price', key: "price",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('price'), ...this.getColumnSearchProps("price"),
}, },
{ {
title: i18next.t("product:Quantity"), title: i18next.t("product:Quantity"),
dataIndex: 'quantity', dataIndex: "quantity",
key: 'quantity', key: "quantity",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('quantity'), ...this.getColumnSearchProps("quantity"),
}, },
{ {
title: i18next.t("product:Sold"), title: i18next.t("product:Sold"),
dataIndex: 'sold', dataIndex: "sold",
key: 'sold', key: "sold",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('sold'), ...this.getColumnSearchProps("sold"),
}, },
{ {
title: i18next.t("general:State"), title: i18next.t("general:State"),
dataIndex: 'state', dataIndex: "state",
key: 'state', key: "state",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('state'), ...this.getColumnSearchProps("state"),
}, },
{ {
title: i18next.t("product:Payment providers"), title: i18next.t("product:Payment providers"),
dataIndex: 'providers', dataIndex: "providers",
key: 'providers', key: "providers",
width: '500px', width: "500px",
...this.getColumnSearchProps('providers'), ...this.getColumnSearchProps("providers"),
render: (text, record, index) => { render: (text, record, index) => {
const providers = text; const providers = text;
if (providers.length === 0) { if (providers.length === 0) {
@@ -197,11 +197,11 @@ class ProductListPage extends BaseListPage {
</Link> </Link>
</div> </div>
</List.Item> </List.Item>
) );
}} }}
/> />
) );
} };
return ( return (
<div> <div>
@@ -218,28 +218,28 @@ class ProductListPage extends BaseListPage {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
}, },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '230px', width: "230px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete product: ${record.name} ?`} title={`Sure to delete product: ${record.name} ?`}
onConfirm={() => this.deleteProduct(index)} onConfirm={() => this.deleteProduct(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -253,15 +253,15 @@ class ProductListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={products} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={products} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Products")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addProduct.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addProduct.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -274,7 +274,7 @@ class ProductListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
ProductBackend.getProducts("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ProductBackend.getProducts("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,18 +13,18 @@
// 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, Row, Select, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import { authConfig } from "./auth/Auth"; import {authConfig} from "./auth/Auth";
import * as ProviderEditTestEmail from "./TestEmailWidget"; import * as ProviderEditTestEmail from "./TestEmailWidget";
import copy from 'copy-to-clipboard'; import copy from "copy-to-clipboard";
import { CaptchaPreview } from "./common/CaptchaPreview"; import {CaptchaPreview} from "./common/CaptchaPreview";
const { Option } = Select; const {Option} = Select;
const { TextArea } = Input; const {TextArea} = Input;
class ProviderEditPage extends React.Component { class ProviderEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -70,47 +70,47 @@ class ProviderEditPage extends React.Component {
getClientIdLabel() { getClientIdLabel() {
switch (this.state.provider.category) { switch (this.state.provider.category) {
case "Email": case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip")); return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS": case "SMS":
if (this.state.provider.type === "Volc Engine SMS") { if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip")); return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") { } else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip")); return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} else { } else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip")); return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
} }
} }
getClientSecretLabel() { getClientSecretLabel() {
switch (this.state.provider.category) { switch (this.state.provider.category) {
case "Email": case "Email":
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip")); return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
case "SMS": case "SMS":
if (this.state.provider.type === "Volc Engine SMS") { if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip")); return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") { } else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip")); return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} else { } else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip")); return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
} }
} }
@@ -122,7 +122,7 @@ class ProviderEditPage extends React.Component {
} else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") { } else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") {
text = i18next.t("provider:Agent ID"); text = i18next.t("provider:Agent ID");
tooltip = i18next.t("provider:Agent ID - Tooltip"); tooltip = i18next.t("provider:Agent ID - Tooltip");
} else if (this.state.provider.type === "Infoflow"){ } else if (this.state.provider.type === "Infoflow") {
text = i18next.t("provider:Agent ID"); text = i18next.t("provider:Agent ID");
tooltip = i18next.t("provider:Agent ID - Tooltip"); tooltip = i18next.t("provider:Agent ID - Tooltip");
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") { } else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
@@ -135,13 +135,13 @@ class ProviderEditPage extends React.Component {
return null; return null;
} }
return <Row style={{marginTop: '20px'}} > return <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(text, tooltip)} : {Setting.getLabel(text, tooltip)} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.appId} onChange={e => { <Input value={this.state.provider.appId} onChange={e => {
this.updateProviderField('appId', e.target.value); this.updateProviderField("appId", e.target.value);
}} /> }} />
</Col> </Col>
</Row>; </Row>;
@@ -164,83 +164,83 @@ class ProviderEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("provider:New Provider") : i18next.t("provider:Edit Provider")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("provider:New Provider") : i18next.t("provider:Edit Provider")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitProviderEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitProviderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.name} onChange={e => { <Input value={this.state.provider.name} onChange={e => {
this.updateProviderField('name', e.target.value); this.updateProviderField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.displayName} onChange={e => { <Input value={this.state.provider.displayName} onChange={e => {
this.updateProviderField('displayName', e.target.value); this.updateProviderField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Category"), i18next.t("provider:Category - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.category} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.provider.category} onChange={(value => {
this.updateProviderField('category', value); this.updateProviderField("category", value);
if (value === "OAuth") { if (value === "OAuth") {
this.updateProviderField('type', 'GitHub'); this.updateProviderField("type", "GitHub");
} else if (value === "Email") { } else if (value === "Email") {
this.updateProviderField('type', 'Default'); this.updateProviderField("type", "Default");
this.updateProviderField('title', 'Casdoor Verification Code'); this.updateProviderField("title", "Casdoor Verification Code");
this.updateProviderField('content', 'You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.'); this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
} else if (value === "SMS") { } else if (value === "SMS") {
this.updateProviderField('type', 'Aliyun SMS'); this.updateProviderField("type", "Aliyun SMS");
} else if (value === "Storage") { } else if (value === "Storage") {
this.updateProviderField('type', 'Local File System'); this.updateProviderField("type", "Local File System");
this.updateProviderField('domain', Setting.getFullServerUrl()); this.updateProviderField("domain", Setting.getFullServerUrl());
} else if (value === "SAML") { } else if (value === "SAML") {
this.updateProviderField('type', 'Aliyun IDaaS'); this.updateProviderField("type", "Aliyun IDaaS");
} else if (value === "Captcha") { } else if (value === "Captcha") {
this.updateProviderField('type', 'Default'); this.updateProviderField("type", "Default");
} }
})}> })}>
{ {
[ [
{id: 'OAuth', name: 'OAuth'}, {id: "OAuth", name: "OAuth"},
{id: 'Email', name: 'Email'}, {id: "Email", name: "Email"},
{id: 'SMS', name: 'SMS'}, {id: "SMS", name: "SMS"},
{id: 'Storage', name: 'Storage'}, {id: "Storage", name: "Storage"},
{id: 'SAML', name: 'SAML'}, {id: "SAML", name: "SAML"},
{id: 'Payment', name: 'Payment'}, {id: "Payment", name: "Payment"},
{id: 'Captcha', name: 'Captcha'}, {id: "Captcha", name: "Captcha"},
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>) ].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("provider:Type"), i18next.t("provider:Type - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.provider.type} onChange={(value => {
this.updateProviderField('type', value); this.updateProviderField("type", value);
if (value === "Local File System") { if (value === "Local File System") {
this.updateProviderField('domain', Setting.getFullServerUrl()); this.updateProviderField("domain", Setting.getFullServerUrl());
} }
if (value === "Custom") { if (value === "Custom") {
this.updateProviderField('customAuthUrl', 'https://door.casdoor.com/login/oauth/authorize'); this.updateProviderField("customAuthUrl", "https://door.casdoor.com/login/oauth/authorize");
this.updateProviderField('customScope', 'openid profile email'); this.updateProviderField("customScope", "openid profile email");
this.updateProviderField('customTokenUrl', 'https://door.casdoor.com/api/login/oauth/access_token'); this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
this.updateProviderField('customUserInfoUrl', 'https://door.casdoor.com/api/userinfo'); this.updateProviderField("customUserInfoUrl", "https://door.casdoor.com/api/userinfo");
} }
})}> })}>
{ {
@@ -252,13 +252,13 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider.type !== "WeCom" && this.state.provider.type !== "Infoflow" && this.state.provider.type !== "Aliyun Captcha" ? null : ( this.state.provider.type !== "WeCom" && this.state.provider.type !== "Infoflow" && this.state.provider.type !== "Aliyun Captcha" ? null : (
<React.Fragment> <React.Fragment>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Sub type"), i18next.t("provider:Sub type - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Sub type"), i18next.t("provider:Sub type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.subType} onChange={value => { <Select virtual={false} style={{width: "100%"}} value={this.state.provider.subType} onChange={value => {
this.updateProviderField('subType', value); this.updateProviderField("subType", value);
}}> }}>
{ {
Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>) Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
@@ -268,13 +268,13 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
{ {
this.state.provider.type !== "WeCom" ? null : ( this.state.provider.type !== "WeCom" ? null : (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.method} onChange={value => { <Select virtual={false} style={{width: "100%"}} value={this.state.provider.method} onChange={value => {
this.updateProviderField('method', value); this.updateProviderField("method", value);
}}> }}>
{ {
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>) [{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
@@ -289,68 +289,68 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider.type !== "Custom" ? null : ( this.state.provider.type !== "Custom" ? null : (
<React.Fragment> <React.Fragment>
<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("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))} {Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.customAuthUrl} onChange={e => { <Input value={this.state.provider.customAuthUrl} onChange={e => {
this.updateProviderField('customAuthUrl', e.target.value); this.updateProviderField("customAuthUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Scope"), i18next.t("provider:Scope - Tooltip"))} {Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.customScope} onChange={e => { <Input value={this.state.provider.customScope} onChange={e => {
this.updateProviderField('customScope', e.target.value); this.updateProviderField("customScope", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))} {Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.customTokenUrl} onChange={e => { <Input value={this.state.provider.customTokenUrl} onChange={e => {
this.updateProviderField('customTokenUrl', e.target.value); this.updateProviderField("customTokenUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))} {Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.customUserInfoUrl} onChange={e => { <Input value={this.state.provider.customUserInfoUrl} onChange={e => {
this.updateProviderField('customUserInfoUrl', e.target.value); this.updateProviderField("customUserInfoUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Favicon"), i18next.t("general:Favicon - Tooltip"))} : {Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} : {Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.customLogo} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.provider.customLogo} onChange={e => {
this.updateProviderField('customLogo', e.target.value); this.updateProviderField("customLogo", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.provider.customLogo}> <a target="_blank" rel="noreferrer" href={this.state.provider.customLogo}>
<img src={this.state.provider.customLogo} alt={this.state.provider.customLogo} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.provider.customLogo} alt={this.state.provider.customLogo} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
@@ -362,23 +362,23 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : ( this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
<React.Fragment> <React.Fragment>
<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}>
{this.getClientIdLabel()} {this.getClientIdLabel()}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => { <Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField('clientId', e.target.value); this.updateProviderField("clientId", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{this.getClientSecretLabel()} {this.getClientSecretLabel()}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientSecret} onChange={e => { <Input value={this.state.provider.clientSecret} onChange={e => {
this.updateProviderField('clientSecret', e.target.value); this.updateProviderField("clientSecret", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
@@ -388,27 +388,27 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" ? null : ( this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" ? null : (
<React.Fragment> <React.Fragment>
<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}>
{this.state.provider.type === "Aliyun Captcha" {this.state.provider.type === "Aliyun Captcha"
? Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip")) ? Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"))
: Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))} : Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientId2} onChange={e => { <Input value={this.state.provider.clientId2} onChange={e => {
this.updateProviderField('clientId2', e.target.value); this.updateProviderField("clientId2", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{this.state.provider.type === "Aliyun Captcha" {this.state.provider.type === "Aliyun Captcha"
? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip")) ? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"))
: Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))} : Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.clientSecret2} onChange={e => { <Input value={this.state.provider.clientSecret2} onChange={e => {
this.updateProviderField('clientSecret2', e.target.value); this.updateProviderField("clientSecret2", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
@@ -417,68 +417,68 @@ class ProviderEditPage extends React.Component {
} }
{ {
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : ( this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField('domain', e.target.value);
}} />
</Col>
</Row>
)
}
{this.state.provider.category === "Storage" ? (
<div>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField('endpoint', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField('intranetEndpoint', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField('bucket', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.domain} onChange={e => { <Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField('domain', e.target.value); this.updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
)
}
{this.state.provider.category === "Storage" ? (
<div>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField("domain", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
{this.state.provider.type === "AWS S3" || this.state.provider.type === "Tencent Cloud COS" ? ( {this.state.provider.type === "AWS S3" || this.state.provider.type === "Tencent Cloud COS" ? (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.regionId} onChange={e => { <Input value={this.state.provider.regionId} onChange={e => {
this.updateProviderField('regionId', e.target.value); this.updateProviderField("regionId", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
@@ -488,160 +488,160 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider.category === "Email" ? ( this.state.provider.category === "Email" ? (
<React.Fragment> <React.Fragment>
<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("provider:Host"), i18next.t("provider:Host - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.host} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => {
this.updateProviderField('host', e.target.value); this.updateProviderField("host", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Port"), i18next.t("provider:Port - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.provider.port} onChange={value => { <InputNumber value={this.state.provider.port} onChange={value => {
this.updateProviderField('port', value); this.updateProviderField("port", value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Email Title"), i18next.t("provider:Email Title - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Email Title"), i18next.t("provider:Email Title - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.title} onChange={e => { <Input value={this.state.provider.title} onChange={e => {
this.updateProviderField('title', e.target.value); this.updateProviderField("title", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<TextArea autoSize={{minRows: 1, maxRows: 100}} value={this.state.provider.content} onChange={e => { <TextArea autoSize={{minRows: 1, maxRows: 100}} value={this.state.provider.content} onChange={e => {
this.updateProviderField('content', e.target.value); this.updateProviderField("content", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
</Col> </Col>
<Col span={4} > <Col span={4} >
<Input value={this.state.testEmail} <Input value={this.state.testEmail}
placeHolder = {i18next.t("user:Input your email")} placeHolder = {i18next.t("user:Input your email")}
onChange={e => { onChange={e => {
this.setState({testEmail: e.target.value}) this.setState({testEmail: e.target.value});
}} /> }} />
</Col> </Col>
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary" <Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} > onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
{i18next.t("provider:Test Connection")} {i18next.t("provider:Test Connection")}
</Button> </Button>
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary" <Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidEmail(this.state.testEmail)} disabled={!Setting.isValidEmail(this.state.testEmail)}
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} > onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
{i18next.t("provider:Send Test Email")} {i18next.t("provider:Send Test Email")}
</Button> </Button>
</Row> </Row>
</React.Fragment> </React.Fragment>
) : this.state.provider.category === "SMS" ? ( ) : this.state.provider.category === "SMS" ? (
<React.Fragment> <React.Fragment>
<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("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.signName} onChange={e => { <Input value={this.state.provider.signName} onChange={e => {
this.updateProviderField('signName', e.target.value); this.updateProviderField("signName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Template Code"), i18next.t("provider:Template Code - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Template Code"), i18next.t("provider:Template Code - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => { <Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField('templateCode', e.target.value); this.updateProviderField("templateCode", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
</React.Fragment> </React.Fragment>
) : this.state.provider.category === "SAML" ? ( ) : this.state.provider.category === "SAML" ? (
<React.Fragment> <React.Fragment>
<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("provider:Sign request"), i18next.t("provider:Sign request - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Sign request"), i18next.t("provider:Sign request - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Switch checked={this.state.provider.enableSignAuthnRequest} onChange={checked => { <Switch checked={this.state.provider.enableSignAuthnRequest} onChange={checked => {
this.updateProviderField('enableSignAuthnRequest', checked); this.updateProviderField("enableSignAuthnRequest", checked);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
</Col> </Col>
<Col span={22}> <Col span={22}>
<TextArea rows={4} value={this.state.provider.metadata} onChange={e => { <TextArea rows={4} value={this.state.provider.metadata} onChange={e => {
this.updateProviderField('metadata', e.target.value); this.updateProviderField("metadata", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}}> <Row style={{marginTop: "20px"}}>
<Col style={{marginTop: '5px'}} span={2}></Col> <Col style={{marginTop: "5px"}} span={2}></Col>
<Col span={2}> <Col span={2}>
<Button type="primary" onClick={() => { <Button type="primary" onClick={() => {
try { try {
this.loadSamlConfiguration(); this.loadSamlConfiguration();
Setting.showMessage("success", i18next.t("provider:Parse Metadata successfully")); Setting.showMessage("success", i18next.t("provider:Parse Metadata successfully"));
} catch (err) { } catch (err) {
Setting.showMessage("error", i18next.t("provider:Can not parse Metadata")); Setting.showMessage("error", i18next.t("provider:Can not parse Metadata"));
} }
}}> }}>
{i18next.t("provider:Parse")} {i18next.t("provider:Parse")}
</Button> </Button>
</Col> </Col>
</Row> </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("provider:Endpoint"), i18next.t("provider:SAML 2.0 Endpoint (HTTP)"))} : {Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:SAML 2.0 Endpoint (HTTP)"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => { <Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField('endpoint', e.target.value); this.updateProviderField("endpoint", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:IdP"), i18next.t("provider:IdP public key"))} : {Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.idP} onChange={e => { <Input value={this.state.provider.idP} onChange={e => {
this.updateProviderField('idP', e.target.value); this.updateProviderField("idP", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Issuer URL"), i18next.t("provider:Issuer URL - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Issuer URL"), i18next.t("provider:Issuer URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.provider.issuerUrl} onChange={e => { <Input value={this.state.provider.issuerUrl} onChange={e => {
this.updateProviderField('issuerUrl', e.target.value); this.updateProviderField("issuerUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:SP ACS URL"), i18next.t("provider:SP ACS URL - Tooltip"))} : {Setting.getLabel(i18next.t("provider:SP ACS URL"), i18next.t("provider:SP ACS URL - Tooltip"))} :
</Col> </Col>
<Col span={21} > <Col span={21} >
@@ -656,8 +656,8 @@ class ProviderEditPage extends React.Component {
</Button> </Button>
</Col> </Col>
</Row> </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("provider:SP Entity ID"), i18next.t("provider:SP ACS URL - Tooltip"))} : {Setting.getLabel(i18next.t("provider:SP Entity ID"), i18next.t("provider:SP ACS URL - Tooltip"))} :
</Col> </Col>
<Col span={21} > <Col span={21} >
@@ -676,20 +676,20 @@ class ProviderEditPage extends React.Component {
) : null ) : null
} }
{this.getAppIdRow()} {this.getAppIdRow()}
<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("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.provider.providerUrl} onChange={e => {
this.updateProviderField('providerUrl', e.target.value); this.updateProviderField("providerUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
{ {
this.state.provider.category !== "Captcha" ? null : ( this.state.provider.category !== "Captcha" ? null : (
<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:Preview"), i18next.t("general:Preview - Tooltip"))} : {Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -711,7 +711,7 @@ class ProviderEditPage extends React.Component {
) )
} }
</Card> </Card>
) );
} }
submitProviderEdit(willExist) { submitProviderEdit(willExist) {
@@ -719,19 +719,19 @@ class ProviderEditPage extends React.Component {
ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider) ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
providerName: this.state.provider.name, providerName: this.state.provider.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/providers`); this.props.history.push("/providers");
} else { } else {
this.props.history.push(`/providers/${this.state.provider.name}`); this.props.history.push(`/providers/${this.state.provider.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateProviderField('name', this.state.providerName); this.updateProviderField("name", this.state.providerName);
} }
}) })
.catch(error => { .catch(error => {
@@ -742,7 +742,7 @@ class ProviderEditPage extends React.Component {
deleteProvider() { deleteProvider() {
ProviderBackend.deleteProvider(this.state.provider) ProviderBackend.deleteProvider(this.state.provider)
.then(() => { .then(() => {
this.props.history.push(`/providers`); this.props.history.push("/providers");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Provider failed to delete: ${error}`); Setting.showMessage("error", `Provider failed to delete: ${error}`);
@@ -755,10 +755,10 @@ class ProviderEditPage extends React.Component {
{ {
this.state.provider !== null ? this.renderProvider() : null this.state.provider !== null ? this.renderProvider() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitProviderEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitProviderEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Table} from 'antd'; import {Button, Popconfirm, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
@@ -39,15 +39,15 @@ class ProviderListPage extends BaseListPage {
host: "", host: "",
port: 0, port: 0,
providerUrl: "https://github.com/organizations/xxx/settings/applications/1234567", providerUrl: "https://github.com/organizations/xxx/settings/applications/1234567",
} };
} }
addProvider() { addProvider() {
const newProvider = this.newProvider(); const newProvider = this.newProvider();
ProviderBackend.addProvider(newProvider) ProviderBackend.addProvider(newProvider)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/providers/${newProvider.name}`, mode: "add"}); this.props.history.push({pathname: `/providers/${newProvider.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Provider failed to add: ${error}`); Setting.showMessage("error", `Provider failed to add: ${error}`);
@@ -57,12 +57,12 @@ class ProviderListPage extends BaseListPage {
deleteProvider(i) { deleteProvider(i) {
ProviderBackend.deleteProvider(this.state.data[i]) ProviderBackend.deleteProvider(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Provider deleted successfully`); Setting.showMessage("success", "Provider deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Provider failed to delete: ${error}`); Setting.showMessage("error", `Provider failed to delete: ${error}`);
@@ -73,25 +73,25 @@ class ProviderListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '120px', width: "120px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/providers/${text}`}> <Link to={`/providers/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '180px', width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -99,41 +99,41 @@ class ProviderListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("provider:Category"), title: i18next.t("provider:Category"),
dataIndex: 'category', dataIndex: "category",
key: 'category', key: "category",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'OAuth', value: 'OAuth'}, {text: "OAuth", value: "OAuth"},
{text: 'Email', value: 'Email'}, {text: "Email", value: "Email"},
{text: 'SMS', value: 'SMS'}, {text: "SMS", value: "SMS"},
{text: 'Storage', value: 'Storage'}, {text: "Storage", value: "Storage"},
{text: 'SAML', value: 'SAML'}, {text: "SAML", value: "SAML"},
], ],
width: '110px', width: "110px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("provider:Type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
width: '110px', width: "110px",
align: 'center', align: "center",
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'OAuth', value: 'OAuth', children: Setting.getProviderTypeOptions('OAuth').map((o) => {return {text:o.id, value:o.name}})}, {text: "OAuth", value: "OAuth", children: Setting.getProviderTypeOptions("OAuth").map((o) => {return {text:o.id, value:o.name};})},
{text: 'Email', value: 'Email', children: Setting.getProviderTypeOptions('Email').map((o) => {return {text:o.id, value:o.name}})}, {text: "Email", value: "Email", children: Setting.getProviderTypeOptions("Email").map((o) => {return {text:o.id, value:o.name};})},
{text: 'SMS', value: 'SMS', children: Setting.getProviderTypeOptions('SMS').map((o) => {return {text:o.id, value:o.name}})}, {text: "SMS", value: "SMS", children: Setting.getProviderTypeOptions("SMS").map((o) => {return {text:o.id, value:o.name};})},
{text: 'Storage', value: 'Storage', children: Setting.getProviderTypeOptions('Storage').map((o) => {return {text:o.id, value:o.name}})}, {text: "Storage", value: "Storage", children: Setting.getProviderTypeOptions("Storage").map((o) => {return {text:o.id, value:o.name};})},
{text: 'SAML', value: 'SAML', children: Setting.getProviderTypeOptions('SAML').map((o) => {return {text:o.id, value:o.name}})}, {text: "SAML", value: "SAML", children: Setting.getProviderTypeOptions("SAML").map((o) => {return {text:o.id, value:o.name};})},
{text: 'Captcha', value: 'Captcha', children: Setting.getProviderTypeOptions('Captcha').map((o) => {return {text:o.id, value:o.name}})}, {text: "Captcha", value: "Captcha", children: Setting.getProviderTypeOptions("Captcha").map((o) => {return {text:o.id, value:o.name};})},
], ],
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
@@ -142,22 +142,22 @@ class ProviderListPage extends BaseListPage {
}, },
{ {
title: i18next.t("provider:Client ID"), title: i18next.t("provider:Client ID"),
dataIndex: 'clientId', dataIndex: "clientId",
key: 'clientId', key: "clientId",
width: '100px', width: "100px",
sorter: true, sorter: true,
...this.getColumnSearchProps('clientId'), ...this.getColumnSearchProps("clientId"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getShortText(text); return Setting.getShortText(text);
} }
}, },
{ {
title: i18next.t("provider:Provider URL"), title: i18next.t("provider:Provider URL"),
dataIndex: 'providerUrl', dataIndex: "providerUrl",
key: 'providerUrl', key: "providerUrl",
width: '150px', width: "150px",
sorter: true, sorter: true,
...this.getColumnSearchProps('providerUrl'), ...this.getColumnSearchProps("providerUrl"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
@@ -165,27 +165,27 @@ class ProviderListPage extends BaseListPage {
Setting.getShortText(text) Setting.getShortText(text)
} }
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete provider: ${record.name} ?`} title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)} onConfirm={() => this.deleteProvider(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -199,15 +199,15 @@ class ProviderListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Providers")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Providers")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addProvider.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -223,7 +223,7 @@ class ProviderListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
ProviderBackend.getProviders("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ProviderBackend.getProviders("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,13 +13,13 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd'; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import * as Provider from "./auth/Provider"; import * as Provider from "./auth/Provider";
const { Option } = Select; const {Option} = Select;
class ProviderTable extends React.Component { class ProviderTable extends React.Component {
constructor(props) { constructor(props) {
@@ -66,29 +66,29 @@ class ProviderTable extends React.Component {
let columns = [ let columns = [
{ {
title: i18next.t("provider:Name"), title: i18next.t("provider:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Select virtual={false} style={{width: '100%'}} <Select virtual={false} style={{width: "100%"}}
value={text} value={text}
onChange={value => { onChange={value => {
this.updateField(table, index, 'name', value); this.updateField(table, index, "name", value);
const provider = Setting.getArrayItem(this.props.providers, "name", value); const provider = Setting.getArrayItem(this.props.providers, "name", value);
this.updateField(table, index, 'provider', provider); this.updateField(table, index, "provider", provider);
}} > }} >
{ {
Setting.getDeduplicatedArray(this.props.providers, table, "name").map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>) Setting.getDeduplicatedArray(this.props.providers, table, "name").map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("provider:Category"), title: i18next.t("provider:Category"),
dataIndex: 'category', dataIndex: "category",
key: 'category', key: "category",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name); const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return provider?.category; return provider?.category;
@@ -96,9 +96,9 @@ class ProviderTable extends React.Component {
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("provider:Type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
width: '80px', width: "80px",
render: (text, record, index) => { render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name); const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return Provider.getProviderLogoWidget(provider); return Provider.getProviderLogoWidget(provider);
@@ -106,9 +106,9 @@ class ProviderTable extends React.Component {
}, },
{ {
title: i18next.t("provider:canSignUp"), title: i18next.t("provider:canSignUp"),
dataIndex: 'canSignUp', dataIndex: "canSignUp",
key: 'canSignUp', key: "canSignUp",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.provider?.category !== "OAuth") { if (record.provider?.category !== "OAuth") {
return null; return null;
@@ -116,16 +116,16 @@ class ProviderTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'canSignUp', checked); this.updateField(table, index, "canSignUp", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("provider:canSignIn"), title: i18next.t("provider:canSignIn"),
dataIndex: 'canSignIn', dataIndex: "canSignIn",
key: 'canSignIn', key: "canSignIn",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.provider?.category !== "OAuth") { if (record.provider?.category !== "OAuth") {
return null; return null;
@@ -133,16 +133,16 @@ class ProviderTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'canSignIn', checked); this.updateField(table, index, "canSignIn", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("provider:canUnlink"), title: i18next.t("provider:canUnlink"),
dataIndex: 'canUnlink', dataIndex: "canUnlink",
key: 'canUnlink', key: "canUnlink",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.provider?.category !== "OAuth") { if (record.provider?.category !== "OAuth") {
return null; return null;
@@ -150,16 +150,16 @@ class ProviderTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'canUnlink', checked); this.updateField(table, index, "canUnlink", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:prompted"),
dataIndex: 'prompted', dataIndex: "prompted",
key: 'prompted', key: "prompted",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.provider?.category !== "OAuth") { if (record.provider?.category !== "OAuth") {
return null; return null;
@@ -167,9 +167,9 @@ class ProviderTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'prompted', checked); this.updateField(table, index, "prompted", checked);
}} /> }} />
) );
} }
}, },
// { // {
@@ -195,8 +195,8 @@ class ProviderTable extends React.Component {
// }, // },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -220,13 +220,13 @@ class ProviderTable 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="name" 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;
<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>
)} )}
/> />
); );
} }
@@ -234,7 +234,7 @@ class ProviderTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -242,7 +242,7 @@ class ProviderTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

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 {Switch, Table} from 'antd'; import {Switch, Table} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as RecordBackend from "./backend/RecordBackend"; import * as RecordBackend from "./backend/RecordBackend";
import i18next from "i18next"; import i18next from "i18next";
@@ -24,8 +24,8 @@ import BaseListPage from "./BaseListPage";
class RecordListPage extends BaseListPage { class RecordListPage extends BaseListPage {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.state.pagination.pageSize = 20; this.state.pagination.pageSize = 20;
const { pagination } = this.state; const {pagination} = this.state;
this.fetch({ pagination }); this.fetch({pagination});
} }
newRecord() { newRecord() {
@@ -40,47 +40,47 @@ class RecordListPage extends BaseListPage {
requestUri: "/api/get-account", requestUri: "/api/get-account",
action: "login", action: "login",
isTriggered: false, isTriggered: false,
} };
} }
renderTable(records) { renderTable(records) {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '320px', width: "320px",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
}, },
{ {
title: i18next.t("general:ID"), title: i18next.t("general:ID"),
dataIndex: 'id', dataIndex: "id",
key: 'id', key: "id",
width: '90px', width: "90px",
sorter: true, sorter: true,
...this.getColumnSearchProps('id'), ...this.getColumnSearchProps("id"),
}, },
{ {
title: i18next.t("general:Client IP"), title: i18next.t("general:Client IP"),
dataIndex: 'clientIp', dataIndex: "clientIp",
key: 'clientIp', key: "clientIp",
width: '150px', width: "150px",
sorter: true, sorter: true,
...this.getColumnSearchProps('clientIp'), ...this.getColumnSearchProps("clientIp"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={`https://db-ip.com/${text}`}> <a target="_blank" rel="noreferrer" href={`https://db-ip.com/${text}`}>
{text} {text}
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Timestamp"), title: i18next.t("general:Timestamp"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '180px', width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -88,68 +88,68 @@ class RecordListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '110px', width: "110px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
dataIndex: 'user', dataIndex: "user",
key: 'user', key: "user",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('user'), ...this.getColumnSearchProps("user"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/users/${record.organization}/${record.user}`}> <Link to={`/users/${record.organization}/${record.user}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Method"), title: i18next.t("general:Method"),
dataIndex: 'method', dataIndex: "method",
key: 'method', key: "method",
width: '110px', width: "110px",
sorter: true, sorter: true,
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'GET', value: 'GET'}, {text: "GET", value: "GET"},
{text: 'HEAD', value: 'HEAD'}, {text: "HEAD", value: "HEAD"},
{text: 'POST', value: 'POST'}, {text: "POST", value: "POST"},
{text: 'PUT', value: 'PUT'}, {text: "PUT", value: "PUT"},
{text: 'DELETE', value: 'DELETE'}, {text: "DELETE", value: "DELETE"},
{text: 'CONNECT', value: 'CONNECT'}, {text: "CONNECT", value: "CONNECT"},
{text: 'OPTIONS', value: 'OPTIONS'}, {text: "OPTIONS", value: "OPTIONS"},
{text: 'TRACE', value: 'TRACE'}, {text: "TRACE", value: "TRACE"},
{text: 'PATCH', value: 'PATCH'}, {text: "PATCH", value: "PATCH"},
], ],
}, },
{ {
title: i18next.t("general:Request URI"), title: i18next.t("general:Request URI"),
dataIndex: 'requestUri', dataIndex: "requestUri",
key: 'requestUri', key: "requestUri",
// width: '300px', // width: '300px',
sorter: true, sorter: true,
...this.getColumnSearchProps('requestUri'), ...this.getColumnSearchProps("requestUri"),
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: 'action', dataIndex: "action",
key: 'action', key: "action",
width: '200px', width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps('action'), ...this.getColumnSearchProps("action"),
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return text; return text;
@@ -157,9 +157,9 @@ class RecordListPage extends BaseListPage {
}, },
{ {
title: i18next.t("record:Is Triggered"), title: i18next.t("record:Is Triggered"),
dataIndex: 'isTriggered', dataIndex: "isTriggered",
key: 'isTriggered', key: "isTriggered",
width: '140px', width: "140px",
sorter: true, sorter: true,
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
@@ -169,7 +169,7 @@ class RecordListPage extends BaseListPage {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
]; ];
@@ -184,14 +184,14 @@ class RecordListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={records} rowKey="id" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={records} rowKey="id" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Records")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Records")}&nbsp;&nbsp;&nbsp;&nbsp;
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -204,7 +204,7 @@ class RecordListPage extends BaseListPage {
field = "method"; field = "method";
value = params.method; value = params.method;
} }
this.setState({ loading: true }); this.setState({loading: true});
RecordBackend.getRecords(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) RecordBackend.getRecords(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Modal, Row, Input,} from "antd"; import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import React from "react"; import React from "react";
import * as Setting from "./Setting" import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend" import * as UserBackend from "./backend/UserBackend";
import {CountDownInput} from "./common/CountDownInput"; import {CountDownInput} from "./common/CountDownInput";
import {MailOutlined, PhoneOutlined} from "@ant-design/icons"; import {MailOutlined, PhoneOutlined} from "@ant-design/icons";
@@ -53,12 +53,11 @@ export const ResetModal = (props) => {
Setting.showMessage("error", i18next.t("user:" + res.msg)); Setting.showMessage("error", i18next.t("user:" + res.msg));
setConfirmLoading(false); setConfirmLoading(false);
} }
}) });
} };
let placeHolder = ""; let placeHolder = "";
if (destType === "email") placeHolder = i18next.t("user:Input your email"); if (destType === "email") {placeHolder = i18next.t("user:Input your email");} else if (destType === "phone") {placeHolder = i18next.t("user:Input your phone number");}
else if (destType === "phone") placeHolder = i18next.t("user:Input your phone number");
return ( return (
<Row> <Row>
@@ -95,7 +94,7 @@ export const ResetModal = (props) => {
</Col> </Col>
</Modal> </Modal>
</Row> </Row>
) );
} };
export default ResetModal; export default ResetModal;

View File

@@ -13,9 +13,9 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Popconfirm, Table, Upload} from 'antd'; import {Button, Popconfirm, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import copy from 'copy-to-clipboard'; import copy from "copy-to-clipboard";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ResourceBackend from "./backend/ResourceBackend"; import * as ResourceBackend from "./backend/ResourceBackend";
import i18next from "i18next"; import i18next from "i18next";
@@ -33,8 +33,8 @@ class ResourceListPage extends BaseListPage {
pageSize: 10, pageSize: 10,
}, },
loading: false, loading: false,
searchText: '', searchText: "",
searchedColumn: '', searchedColumn: "",
fileList: [], fileList: [],
uploading: false, uploading: false,
}; };
@@ -43,12 +43,12 @@ class ResourceListPage extends BaseListPage {
deleteResource(i) { deleteResource(i) {
ResourceBackend.deleteResource(this.state.data[i]) ResourceBackend.deleteResource(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Resource deleted successfully`); Setting.showMessage("success", "Resource deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Resource failed to delete: ${error}`); Setting.showMessage("error", `Resource failed to delete: ${error}`);
@@ -68,90 +68,90 @@ class ResourceListPage extends BaseListPage {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}).finally(() => { }).finally(() => {
this.setState({uploading: false}); this.setState({uploading: false});
}) });
} }
renderUpload() { renderUpload() {
return ( return (
<Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false} <Upload maxCount={1} accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.csv,.xls,.xlsx" showUploadList={false}
beforeUpload={file => {return false}} onChange={info => {this.handleUpload(info)}}> beforeUpload={file => {return false;}} onChange={info => {this.handleUpload(info);}}>
<Button icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small"> <Button icon={<UploadOutlined />} loading={this.state.uploading} type="primary" size="small">
{i18next.t("resource:Upload a file...")} {i18next.t("resource:Upload a file...")}
</Button> </Button>
</Upload> </Upload>
) );
} }
renderTable(resources) { renderTable(resources) {
const columns = [ const columns = [
{ {
title: i18next.t("general:Provider"), title: i18next.t("general:Provider"),
dataIndex: 'provider', dataIndex: "provider",
key: 'provider', key: "provider",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('provider'), ...this.getColumnSearchProps("provider"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/providers/${text}`}> <Link to={`/providers/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("resource:Application"), title: i18next.t("resource:Application"),
dataIndex: 'application', dataIndex: "application",
key: 'application', key: "application",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('application'), ...this.getColumnSearchProps("application"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/applications/${text}`}> <Link to={`/applications/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("resource:User"), title: i18next.t("resource:User"),
dataIndex: 'user', dataIndex: "user",
key: 'user', key: "user",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('user'), ...this.getColumnSearchProps("user"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/users/${record.owner}/${record.user}`}> <Link to={`/users/${record.owner}/${record.user}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("resource:Parent"), title: i18next.t("resource:Parent"),
dataIndex: 'parent', dataIndex: "parent",
key: 'parent', key: "parent",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('parent'), ...this.getColumnSearchProps("parent"),
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '150px', width: "150px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -159,11 +159,11 @@ class ResourceListPage extends BaseListPage {
}, },
{ {
title: i18next.t("resource:Tag"), title: i18next.t("resource:Tag"),
dataIndex: 'tag', dataIndex: "tag",
key: 'tag', key: "tag",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('tag'), ...this.getColumnSearchProps("tag"),
}, },
// { // {
// title: i18next.t("resource:File name"), // title: i18next.t("resource:File name"),
@@ -174,25 +174,25 @@ class ResourceListPage extends BaseListPage {
// }, // },
{ {
title: i18next.t("resource:Type"), title: i18next.t("resource:Type"),
dataIndex: 'fileType', dataIndex: "fileType",
key: 'fileType', key: "fileType",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('fileType'), ...this.getColumnSearchProps("fileType"),
}, },
{ {
title: i18next.t("resource:Format"), title: i18next.t("resource:Format"),
dataIndex: 'fileFormat', dataIndex: "fileFormat",
key: 'fileFormat', key: "fileFormat",
width: '80px', width: "80px",
sorter: true, sorter: true,
...this.getColumnSearchProps('fileFormat'), ...this.getColumnSearchProps("fileFormat"),
}, },
{ {
title: i18next.t("resource:File size"), title: i18next.t("resource:File size"),
dataIndex: 'fileSize', dataIndex: "fileSize",
key: 'fileSize', key: "fileSize",
width: '100px', width: "100px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFriendlyFileSize(text); return Setting.getFriendlyFileSize(text);
@@ -200,16 +200,16 @@ class ResourceListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Preview"), title: i18next.t("general:Preview"),
dataIndex: 'preview', dataIndex: "preview",
key: 'preview', key: "preview",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.fileType === "image") { if (record.fileType === "image") {
return ( return (
<a target="_blank" rel="noreferrer" href={record.url}> <a target="_blank" rel="noreferrer" href={record.url}>
<img src={record.url} alt={record.name} width={100} /> <img src={record.url} alt={record.name} width={100} />
</a> </a>
) );
} else if (record.fileType === "video") { } else if (record.fileType === "video") {
return ( return (
<div> <div>
@@ -217,15 +217,15 @@ class ResourceListPage extends BaseListPage {
<source src={text} type="video/mp4" /> <source src={text} type="video/mp4" />
</video> </video>
</div> </div>
) );
} }
} }
}, },
{ {
title: i18next.t("general:URL"), title: i18next.t("general:URL"),
dataIndex: 'url', dataIndex: "url",
key: 'url', key: "url",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -237,19 +237,19 @@ class ResourceListPage extends BaseListPage {
{i18next.t("resource:Copy Link")} {i18next.t("resource:Copy Link")}
</Button> </Button>
</div> </div>
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '70px', width: "70px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
{/*<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/resources/${record.name}`)}>{i18next.t("general:Edit")}</Button>*/} {/* <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/resources/${record.name}`)}>{i18next.t("general:Edit")}</Button>*/}
<Popconfirm <Popconfirm
title={`Sure to delete resource: ${record.name} ?`} title={`Sure to delete resource: ${record.name} ?`}
onConfirm={() => this.deleteResource(index)} onConfirm={() => this.deleteResource(index)}
@@ -259,7 +259,7 @@ class ResourceListPage extends BaseListPage {
<Button type="danger">{i18next.t("general:Delete")}</Button> <Button type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -273,18 +273,18 @@ class ResourceListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Resources")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Resources")}&nbsp;&nbsp;&nbsp;&nbsp;
{/*<Button type="primary" size="small" onClick={this.addResource.bind(this)}>{i18next.t("general:Add")}</Button>*/} {/* <Button type="primary" size="small" onClick={this.addResource.bind(this)}>{i18next.t("general:Add")}</Button>*/}
{ {
this.renderUpload() this.renderUpload()
} }
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -293,7 +293,7 @@ class ResourceListPage extends BaseListPage {
fetch = (params = {}) => { fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText; let field = params.searchedColumn, value = params.searchText;
let sortField = params.sortField, sortOrder = params.sortOrder; let sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({ loading: true }); this.setState({loading: true});
ResourceBackend.getResources(this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) ResourceBackend.getResources(this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,14 +13,14 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as RoleBackend from "./backend/RoleBackend"; import * as RoleBackend from "./backend/RoleBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
const { Option } = Select; const {Option} = Select;
class RoleEditPage extends React.Component { class RoleEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -104,78 +104,78 @@ class RoleEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("role:New Role") : i18next.t("role:Edit Role")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("role:New Role") : i18next.t("role:Edit Role")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.role.owner} onChange={(value => {this.updateRoleField('owner', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.role.owner} onChange={(value => {this.updateRoleField("owner", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.role.name} onChange={e => { <Input value={this.state.role.name} onChange={e => {
this.updateRoleField('name', e.target.value); this.updateRoleField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.role.displayName} onChange={e => { <Input value={this.state.role.displayName} onChange={e => {
this.updateRoleField('displayName', e.target.value); this.updateRoleField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.role.users} onChange={(value => {this.updateRoleField('users', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.role.users} onChange={(value => {this.updateRoleField("users", value);})}>
{ {
this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>) this.state.users.map((user, index) => <Option key={index} value={`${user.owner}/${user.name}`}>{`${user.owner}/${user.name}`}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.role.roles} onChange={(value => {this.updateRoleField('roles', value);})}> <Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.role.roles} onChange={(value => {this.updateRoleField("roles", value);})}>
{ {
this.state.roles.filter(role => (role.owner !== this.state.role.owner || role.name !== this.state.role.name)).map((role, index) => <Option key={index} value={`${role.owner}/${role.name}`}>{`${role.owner}/${role.name}`}</Option>) this.state.roles.filter(role => (role.owner !== this.state.role.owner || role.name !== this.state.role.name)).map((role, index) => <Option key={index} value={`${role.owner}/${role.name}`}>{`${role.owner}/${role.name}`}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.role.isEnabled} onChange={checked => { <Switch checked={this.state.role.isEnabled} onChange={checked => {
this.updateRoleField('isEnabled', checked); this.updateRoleField("isEnabled", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitRoleEdit(willExist) { submitRoleEdit(willExist) {
@@ -183,19 +183,19 @@ class RoleEditPage extends React.Component {
RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role) RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
roleName: this.state.role.name, roleName: this.state.role.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/roles`); this.props.history.push("/roles");
} else { } else {
this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`); this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateRoleField('name', this.state.roleName); this.updateRoleField("name", this.state.roleName);
} }
}) })
.catch(error => { .catch(error => {
@@ -206,7 +206,7 @@ class RoleEditPage extends React.Component {
deleteRole() { deleteRole() {
RoleBackend.deleteRole(this.state.role) RoleBackend.deleteRole(this.state.role)
.then(() => { .then(() => {
this.props.history.push(`/roles`); this.props.history.push("/roles");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Role failed to delete: ${error}`); Setting.showMessage("error", `Role failed to delete: ${error}`);
@@ -219,10 +219,10 @@ class RoleEditPage extends React.Component {
{ {
this.state.role !== null ? this.renderRole() : null this.state.role !== null ? this.renderRole() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitRoleEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as RoleBackend from "./backend/RoleBackend"; import * as RoleBackend from "./backend/RoleBackend";
@@ -32,15 +32,15 @@ class RoleListPage extends BaseListPage {
users: [], users: [],
roles: [], roles: [],
isEnabled: true, isEnabled: true,
} };
} }
addRole() { addRole() {
const newRole = this.newRole(); const newRole = this.newRole();
RoleBackend.addRole(newRole) RoleBackend.addRole(newRole)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/roles/${newRole.owner}/${newRole.name}`, mode: "add"}); this.props.history.push({pathname: `/roles/${newRole.owner}/${newRole.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Role failed to add: ${error}`); Setting.showMessage("error", `Role failed to add: ${error}`);
@@ -50,12 +50,12 @@ class RoleListPage extends BaseListPage {
deleteRole(i) { deleteRole(i) {
RoleBackend.deleteRole(this.state.data[i]) RoleBackend.deleteRole(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Role deleted successfully`); Setting.showMessage("success", "Role deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Role failed to delete: ${error}`); Setting.showMessage("error", `Role failed to delete: ${error}`);
@@ -66,40 +66,40 @@ class RoleListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'owner', dataIndex: "owner",
key: 'owner', key: "owner",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('owner'), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/roles/${text}`}> <Link to={`/roles/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -107,64 +107,64 @@ class RoleListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '200px', width: "200px",
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("role:Sub users"), title: i18next.t("role:Sub users"),
dataIndex: 'users', dataIndex: "users",
key: 'users', key: "users",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('users'), ...this.getColumnSearchProps("users"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("role:Sub roles"), title: i18next.t("role:Sub roles"),
dataIndex: 'roles', dataIndex: "roles",
key: 'roles', key: "roles",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('roles'), ...this.getColumnSearchProps("roles"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
dataIndex: 'isEnabled', dataIndex: "isEnabled",
key: 'isEnabled', key: "isEnabled",
width: '120px', width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete role: ${record.name} ?`} title={`Sure to delete role: ${record.name} ?`}
onConfirm={() => this.deleteRole(index)} onConfirm={() => this.deleteRole(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -178,15 +178,15 @@ class RoleListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={roles} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={roles} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Roles")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Roles")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addRole.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addRole.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -199,7 +199,7 @@ class RoleListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -14,12 +14,12 @@
import React from "react"; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import { Menu, Dropdown} from "antd"; import {Dropdown, Menu} from "antd";
import { createFromIconfontCN } from '@ant-design/icons'; import {createFromIconfontCN} from "@ant-design/icons";
import './App.less'; import "./App.less";
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2680620_ffij16fkwdg.js', scriptUrl: "//at.alicdn.com/t/font_2680620_ffij16fkwdg.js",
}); });
class SelectLanguageBox extends React.Component { class SelectLanguageBox extends React.Component {

View File

@@ -14,48 +14,48 @@
import React from "react"; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import { Select } from "antd"; import {Select} from "antd";
const { Option } = Select; const {Option} = Select;
class SelectRegionBox extends React.Component { class SelectRegionBox extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
value: "", value: "",
};
}
onChange(e) {
this.props.onChange(e);
this.setState({value: e})
}; };
}
render() { onChange(e) {
return ( this.props.onChange(e);
<Select virtual={false} this.setState({value: e});
showSearch }
optionFilterProp="label"
style={{width: '100%'}} render() {
defaultValue={this.props.defaultValue || undefined} return (
placeholder="Please select country/region" <Select virtual={false}
onChange={(value => {this.onChange(value);})} showSearch
filterOption={(input, option) => optionFilterProp="label"
option.label.indexOf(input) >= 0 style={{width: "100%"}}
} defaultValue={this.props.defaultValue || undefined}
> placeholder="Please select country/region"
{ onChange={(value => {this.onChange(value);})}
Setting.CountryRegionData.map((item, index) => ( filterOption={(input, option) =>
<Option key={index} value={item.code} label={item.code} > option.label.indexOf(input) >= 0
<img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}}/> }
{`${item.name} (${item.code})`} >
</Option> {
)) Setting.CountryRegionData.map((item, index) => (
} <Option key={index} value={item.code} label={item.code} >
</Select> <img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}} />
) {`${item.name} (${item.code})`}
}; </Option>
))
}
</Select>
);
}
} }
export default SelectRegionBox; export default SelectRegionBox;

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {message, Tag, Tooltip} from "antd"; import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react"; import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
@@ -135,14 +135,13 @@ export function getCountryRegionData() {
var countries = require("i18n-iso-countries"); var countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json")); countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
var data = countries.getNames(language, {select: "official"}); var data = countries.getNames(language, {select: "official"});
var result = [] var result = [];
for (var i in data) for (var i in data) {result.push({code:i, name:data[i]});}
result.push({code:i, name:data[i]}) return result;
return result
} }
export function initServerUrl() { export function initServerUrl() {
//const hostname = window.location.hostname; // const hostname = window.location.hostname;
// if (hostname === "localhost") { // if (hostname === "localhost") {
// ServerUrl = `http://${hostname}:8000`; // ServerUrl = `http://${hostname}:8000`;
// } // }
@@ -170,8 +169,8 @@ export function isProviderVisible(providerItem) {
return false; return false;
} }
if (providerItem.provider.type === "WeChatMiniProgram"){ if (providerItem.provider.type === "WeChatMiniProgram") {
return false return false;
} }
return true; return true;
@@ -325,16 +324,16 @@ export function myParseInt(i) {
export function openLink(link) { export function openLink(link) {
// this.props.history.push(link); // this.props.history.push(link);
const w = window.open('about:blank'); const w = window.open("about:blank");
w.location.href = link; w.location.href = link;
} }
export function openLinkSafe(link) { export function openLinkSafe(link) {
// Javascript window.open issue in safari // Javascript window.open issue in safari
// https://stackoverflow.com/questions/45569893/javascript-window-open-issue-in-safari // https://stackoverflow.com/questions/45569893/javascript-window-open-issue-in-safari
let a = document.createElement('a'); let a = document.createElement("a");
a.href = link; a.href = link;
a.setAttribute('target', '_blank'); a.setAttribute("target", "_blank");
a.click(); a.click();
} }
@@ -399,11 +398,9 @@ export function trim(str, ch) {
let start = 0; let start = 0;
let end = str.length; let end = str.length;
while(start < end && str[start] === ch) while(start < end && str[start] === ch) {++start;}
++start;
while(end > start && str[end - 1] === ch) while(end > start && str[end - 1] === ch) {--end;}
--end;
return (start > 0 || end < str.length) ? str.substring(start, end) : str; return (start > 0 || end < str.length) ? str.substring(start, end) : str;
} }
@@ -418,8 +415,8 @@ export function getFormattedDate(date) {
return null; return null;
} }
date = date.replace('T', ' '); date = date.replace("T", " ");
date = date.replace('+08:00', ' '); date = date.replace("+08:00", " ");
return date; return date;
} }
@@ -428,7 +425,7 @@ export function getFormattedDateShort(date) {
} }
export function getShortName(s) { export function getShortName(s) {
return s.split('/').slice(-1)[0]; return s.split("/").slice(-1)[0];
} }
export function getShortText(s, maxLength=35) { export function getShortText(s, maxLength=35) {
@@ -441,14 +438,14 @@ export function getShortText(s, maxLength=35) {
export function getFriendlyFileSize(size) { export function getFriendlyFileSize(size) {
if (size < 1024) { if (size < 1024) {
return size + ' B'; return size + " B";
} }
let i = Math.floor(Math.log(size) / Math.log(1024)); let i = Math.floor(Math.log(size) / Math.log(1024));
let num = (size / Math.pow(1024, i)); let num = (size / Math.pow(1024, i));
let round = Math.round(num); let round = Math.round(num);
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round; num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round;
return `${num} ${'KMGTPEZY'[i-1]}B`; return `${num} ${"KMGTPEZY"[i-1]}B`;
} }
function getRandomInt(s) { function getRandomInt(s) {
@@ -465,7 +462,7 @@ function getRandomInt(s) {
} }
export function getAvatarColor(s) { export function getAvatarColor(s) {
const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; const colorList = ["#f56a00", "#7265e6", "#ffbf00", "#00a2ae"];
let random = getRandomInt(s); let random = getRandomInt(s);
if (random < 0) { if (random < 0) {
random = -random; random = -random;
@@ -515,20 +512,19 @@ export function changeMomentLanguage(language) {
export function getClickable(text) { export function getClickable(text) {
return ( return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a onClick={() => { <a onClick={() => {
copy(text); copy(text);
showMessage("success", `Copied to clipboard`); showMessage("success", "Copied to clipboard");
}}> }}>
{text} {text}
</a> </a>
) );
} }
export function getProviderLogoURL(provider) { export function getProviderLogoURL(provider) {
if (provider.category === "OAuth") { if (provider.category === "OAuth") {
if (provider.type === "Custom") { if (provider.type === "Custom") {
return provider.customLogo; return provider.customLogo;
} }
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`; return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else { } else {
@@ -537,88 +533,88 @@ export function getProviderLogoURL(provider) {
} }
export function getProviderLogo(provider) { export function getProviderLogo(provider) {
const idp = provider.type.toLowerCase().trim().split(' ')[0]; const idp = provider.type.toLowerCase().trim().split(" ")[0];
const url = getProviderLogoURL(provider); const url = getProviderLogoURL(provider);
return ( return (
<img width={30} height={30} src={url} alt={idp} /> <img width={30} height={30} src={url} alt={idp} />
) );
} }
export function getProviderTypeOptions(category) { export function getProviderTypeOptions(category) {
if (category === "OAuth") { if (category === "OAuth") {
return ( return (
[ [
{id: 'Google', name: 'Google'}, {id: "Google", name: "Google"},
{id: 'GitHub', name: 'GitHub'}, {id: "GitHub", name: "GitHub"},
{id: 'QQ', name: 'QQ'}, {id: "QQ", name: "QQ"},
{id: 'WeChat', name: 'WeChat'}, {id: "WeChat", name: "WeChat"},
{id: 'WeChatMiniProgram', name: 'WeChat Mini Program'}, {id: "WeChatMiniProgram", name: "WeChat Mini Program"},
{id: 'Facebook', name: 'Facebook'}, {id: "Facebook", name: "Facebook"},
{id: 'DingTalk', name: 'DingTalk'}, {id: "DingTalk", name: "DingTalk"},
{id: 'Weibo', name: 'Weibo'}, {id: "Weibo", name: "Weibo"},
{id: 'Gitee', name: 'Gitee'}, {id: "Gitee", name: "Gitee"},
{id: 'LinkedIn', name: 'LinkedIn'}, {id: "LinkedIn", name: "LinkedIn"},
{id: 'WeCom', name: 'WeCom'}, {id: "WeCom", name: "WeCom"},
{id: 'Lark', name: 'Lark'}, {id: "Lark", name: "Lark"},
{id: 'GitLab', name: 'GitLab'}, {id: "GitLab", name: "GitLab"},
{id: 'Adfs', name: 'Adfs'}, {id: "Adfs", name: "Adfs"},
{id: 'Baidu', name: 'Baidu'}, {id: "Baidu", name: "Baidu"},
{id: 'Alipay', name: 'Alipay'}, {id: "Alipay", name: "Alipay"},
{id: 'Casdoor', name: 'Casdoor'}, {id: "Casdoor", name: "Casdoor"},
{id: 'Infoflow', name: 'Infoflow'}, {id: "Infoflow", name: "Infoflow"},
{id: 'Apple', name: 'Apple'}, {id: "Apple", name: "Apple"},
{id: 'AzureAD', name: 'AzureAD'}, {id: "AzureAD", name: "AzureAD"},
{id: 'Slack', name: 'Slack'}, {id: "Slack", name: "Slack"},
{id: 'Steam', name: 'Steam'}, {id: "Steam", name: "Steam"},
{id: 'Bilibili', name: 'Bilibili'}, {id: "Bilibili", name: "Bilibili"},
{id: 'Okta', name: 'Okta'}, {id: "Okta", name: "Okta"},
{id: 'Douyin', name: 'Douyin'}, {id: "Douyin", name: "Douyin"},
{id: 'Custom', name: 'Custom'}, {id: "Custom", name: "Custom"},
] ]
); );
} else if (category === "Email") { } else if (category === "Email") {
return ( return (
[ [
{id: 'Default', name: 'Default'}, {id: "Default", name: "Default"},
] ]
); );
} else if (category === "SMS") { } else if (category === "SMS") {
return ( return (
[ [
{id: 'Aliyun SMS', name: 'Aliyun SMS'}, {id: "Aliyun SMS", name: "Aliyun SMS"},
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'}, {id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'}, {id: "Volc Engine SMS", name: "Volc Engine SMS"},
{id: 'Huawei Cloud SMS', name: 'Huawei Cloud SMS'}, {id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"},
] ]
); );
} else if (category === "Storage") { } else if (category === "Storage") {
return ( return (
[ [
{id: 'Local File System', name: 'Local File System'}, {id: "Local File System", name: "Local File System"},
{id: 'AWS S3', name: 'AWS S3'}, {id: "AWS S3", name: "AWS S3"},
{id: 'Aliyun OSS', name: 'Aliyun OSS'}, {id: "Aliyun OSS", name: "Aliyun OSS"},
{id: 'Tencent Cloud COS', name: 'Tencent Cloud COS'}, {id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: 'Azure Blob', name: 'Azure Blob'} {id: "Azure Blob", name: "Azure Blob"}
] ]
); );
} else if (category === "SAML") { } else if (category === "SAML") {
return ([ return ([
{id: 'Aliyun IDaaS', name: 'Aliyun IDaaS'}, {id: "Aliyun IDaaS", name: "Aliyun IDaaS"},
{id: 'Keycloak', name: 'Keycloak'}, {id: "Keycloak", name: "Keycloak"},
]); ]);
} else if (category === "Payment") { } else if (category === "Payment") {
return ([ return ([
{id: 'Alipay', name: 'Alipay'}, {id: "Alipay", name: "Alipay"},
{id: 'WeChat Pay', name: 'WeChat Pay'}, {id: "WeChat Pay", name: "WeChat Pay"},
{id: 'PayPal', name: 'PayPal'}, {id: "PayPal", name: "PayPal"},
{id: 'GC', name: 'GC'}, {id: "GC", name: "GC"},
]); ]);
} else if (category === "Captcha") { } else if (category === "Captcha") {
return ([ return ([
{id: 'Default', name: 'Default'}, {id: "Default", name: "Default"},
{id: 'reCAPTCHA', name: 'reCAPTCHA'}, {id: "reCAPTCHA", name: "reCAPTCHA"},
{id: 'hCaptcha', name: 'hCaptcha'}, {id: "hCaptcha", name: "hCaptcha"},
{id: 'Aliyun Captcha', name: 'Aliyun Captcha'}, {id: "Aliyun Captcha", name: "Aliyun Captcha"},
]); ]);
} else { } else {
return []; return [];
@@ -629,14 +625,14 @@ export function getProviderSubTypeOptions(type) {
if (type === "WeCom" || type === "Infoflow") { if (type === "WeCom" || type === "Infoflow") {
return ( return (
[ [
{id: 'Internal', name: 'Internal'}, {id: "Internal", name: "Internal"},
{id: 'Third-party', name: 'Third-party'}, {id: "Third-party", name: "Third-party"},
] ]
); );
} else if (type === "Aliyun Captcha") { } else if (type === "Aliyun Captcha") {
return [ return [
{id: 'nc', name: 'Sliding Validation'}, {id: "nc", name: "Sliding Validation"},
{id: 'ic', name: 'Intelligent Validation'}, {id: "ic", name: "Intelligent Validation"},
]; ];
} else { } else {
return []; return [];
@@ -651,12 +647,12 @@ export function renderLogo(application) {
if (application.homepageUrl !== "") { if (application.homepageUrl !== "") {
return ( return (
<a target="_blank" rel="noreferrer" href={application.homepageUrl}> <a target="_blank" rel="noreferrer" href={application.homepageUrl}>
<img width={250} src={application.logo} alt={application.displayName} style={{marginBottom: '30px'}}/> <img width={250} src={application.logo} alt={application.displayName} style={{marginBottom: "30px"}} />
</a> </a>
) );
} else { } else {
return ( return (
<img width={250} src={application.logo} alt={application.displayName} style={{marginBottom: '30px'}}/> <img width={250} src={application.logo} alt={application.displayName} style={{marginBottom: "30px"}} />
); );
} }
} }
@@ -666,8 +662,8 @@ export function goToLogin(ths, application) {
return; return;
} }
if (!application.enablePassword && window.location.pathname.includes("/signup/oauth/authorize")) { if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
const link = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize"); const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
goToLink(link); goToLink(link);
return; return;
} }
@@ -689,7 +685,7 @@ export function goToSignup(ths, application) {
} }
if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) { if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) {
const link = window.location.href.replace("/login/oauth/authorize", "/signup/oauth/authorize"); const link = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
goToLink(link); goToLink(link);
return; return;
} }
@@ -731,13 +727,13 @@ export function renderHelmet(application) {
<title>{application.organizationObj.displayName}</title> <title>{application.organizationObj.displayName}</title>
<link rel="icon" href={application.organizationObj.favicon} /> <link rel="icon" href={application.organizationObj.favicon} />
</Helmet> </Helmet>
) );
} }
export function getLabel(text, tooltip) { export function getLabel(text, tooltip) {
return ( return (
<React.Fragment> <React.Fragment>
<span style={{ marginRight: 4 }}>{text}</span> <span style={{marginRight: 4}}>{text}</span>
<Tooltip placement="top" title={tooltip}> <Tooltip placement="top" title={tooltip}>
<QuestionCircleTwoTone twoToneColor="rgb(45,120,213)" /> <QuestionCircleTwoTone twoToneColor="rgb(45,120,213)" />
</Tooltip> </Tooltip>
@@ -761,11 +757,11 @@ function maskString(s) {
} }
export function getMaskedPhone(s) { export function getMaskedPhone(s) {
return s.replace(/(\d{3})\d*(\d{4})/,'$1****$2'); return s.replace(/(\d{3})\d*(\d{4})/, "$1****$2");
} }
export function getMaskedEmail(email) { export function getMaskedEmail(email) {
if (email === "") return; if (email === "") {return;}
const tokens = email.split("@"); const tokens = email.split("@");
let username = tokens[0]; let username = tokens[0];
username = maskString(username); username = maskString(username);
@@ -802,7 +798,7 @@ export function getTagColor(s) {
export function getTags(tags) { export function getTags(tags) {
let res = []; let res = [];
if (!tags) return res; if (!tags) {return res;}
tags.forEach((tag, i) => { tags.forEach((tag, i) => {
res.push( res.push(
<Tag color={getTagColor(tag)}> <Tag color={getTagColor(tag)}>
@@ -848,91 +844,91 @@ export function scrollToDiv(divId) {
export function getSyncerTableColumns(syncer) { export function getSyncerTableColumns(syncer) {
switch (syncer.type) { switch (syncer.type) {
case "Keycloak": case "Keycloak":
return [ return [
{ {
"name":"ID", "name":"ID",
"type":"string", "type":"string",
"casdoorName":"Id", "casdoorName":"Id",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"USERNAME", "name":"USERNAME",
"type":"string", "type":"string",
"casdoorName":"Name", "casdoorName":"Name",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"LAST_NAME+FIRST_NAME", "name":"LAST_NAME+FIRST_NAME",
"type":"string", "type":"string",
"casdoorName":"DisplayName", "casdoorName":"DisplayName",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"EMAIL", "name":"EMAIL",
"type":"string", "type":"string",
"casdoorName":"Email", "casdoorName":"Email",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"EMAIL_VERIFIED", "name":"EMAIL_VERIFIED",
"type":"boolean", "type":"boolean",
"casdoorName":"EmailVerified", "casdoorName":"EmailVerified",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"FIRST_NAME", "name":"FIRST_NAME",
"type":"string", "type":"string",
"casdoorName":"FirstName", "casdoorName":"FirstName",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"LAST_NAME", "name":"LAST_NAME",
"type":"string", "type":"string",
"casdoorName":"LastName", "casdoorName":"LastName",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"CREATED_TIMESTAMP", "name":"CREATED_TIMESTAMP",
"type":"string", "type":"string",
"casdoorName":"CreatedTime", "casdoorName":"CreatedTime",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
}, },
{ {
"name":"ENABLED", "name":"ENABLED",
"type":"boolean", "type":"boolean",
"casdoorName":"IsForbidden", "casdoorName":"IsForbidden",
"isHashed":true, "isHashed":true,
"values":[ "values":[
] ]
} }
] ];
default: default:
return [] return [];
} }
} }

View File

@@ -13,12 +13,12 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd'; import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
const { Option } = Select; const {Option} = Select;
class SignupTable extends React.Component { class SignupTable extends React.Component {
constructor(props) { constructor(props) {
@@ -65,8 +65,8 @@ class SignupTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("provider:Name"), title: i18next.t("provider:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
render: (text, record, index) => { render: (text, record, index) => {
const items = [ const items = [
{name: "Username", displayName: i18next.t("signup:Username")}, {name: "Username", displayName: i18next.t("signup:Username")},
@@ -91,23 +91,23 @@ class SignupTable extends React.Component {
}; };
return ( return (
<Select virtual={false} style={{width: '100%'}} <Select virtual={false} style={{width: "100%"}}
value={getItemDisplayName(text)} value={getItemDisplayName(text)}
onChange={value => { onChange={value => {
this.updateField(table, index, 'name', value); this.updateField(table, index, "name", value);
}} > }} >
{ {
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>) Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("provider:visible"),
dataIndex: 'visible', dataIndex: "visible",
key: 'visible', key: "visible",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.name === "ID") { if (record.name === "ID") {
return null; return null;
@@ -115,21 +115,21 @@ class SignupTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'visible', checked); this.updateField(table, index, "visible", checked);
if (!checked) { if (!checked) {
this.updateField(table, index, 'required', false); this.updateField(table, index, "required", false);
} else { } else {
this.updateField(table, index, 'required', true); this.updateField(table, index, "required", true);
} }
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("provider:required"), title: i18next.t("provider:required"),
dataIndex: 'required', dataIndex: "required",
key: 'required', key: "required",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (!record.visible) { if (!record.visible) {
return null; return null;
@@ -137,16 +137,16 @@ class SignupTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'required', checked); this.updateField(table, index, "required", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:prompted"),
dataIndex: 'prompted', dataIndex: "prompted",
key: 'prompted', key: "prompted",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
if (record.name === "ID") { if (record.name === "ID") {
return null; return null;
@@ -158,33 +158,33 @@ class SignupTable extends React.Component {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'prompted', checked); this.updateField(table, index, "prompted", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("application:rule"), title: i18next.t("application:rule"),
dataIndex: 'rule', dataIndex: "rule",
key: 'rule', key: "rule",
width: '155px', width: "155px",
render: (text, record, index) => { render: (text, record, index) => {
let options = []; let options = [];
if (record.name === "ID") { if (record.name === "ID") {
options = [ options = [
{id: 'Random', name: 'Random'}, {id: "Random", name: "Random"},
{id: 'Incremental', name: 'Incremental'}, {id: "Incremental", name: "Incremental"},
]; ];
} else if (record.name === "Display name") { } else if (record.name === "Display name") {
options = [ options = [
{id: 'None', name: 'None'}, {id: "None", name: "None"},
{id: 'Real name', name: 'Real name'}, {id: "Real name", name: "Real name"},
{id: 'First, last', name: 'First, last'}, {id: "First, last", name: "First, last"},
]; ];
} else if (record.name === "Email") { } else if (record.name === "Email") {
options = [ options = [
{id: 'Normal', name: 'Normal'}, {id: "Normal", name: "Normal"},
{id: 'No verification', name: 'No verification'}, {id: "No verification", name: "No verification"},
]; ];
} }
@@ -193,20 +193,20 @@ class SignupTable extends React.Component {
} }
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
this.updateField(table, index, 'rule', value); this.updateField(table, index, "rule", value);
})}> })}>
{ {
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -226,13 +226,13 @@ class SignupTable 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="name" 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;
<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>
)} )}
/> />
); );
} }
@@ -240,7 +240,7 @@ class SignupTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -248,7 +248,7 @@ class SignupTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

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, Row, Select, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import * as SyncerBackend from "./backend/SyncerBackend"; import * as SyncerBackend from "./backend/SyncerBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -21,12 +21,12 @@ import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import SyncerTableColumnTable from "./SyncerTableColumnTable"; import SyncerTableColumnTable from "./SyncerTableColumnTable";
import {Controlled as CodeMirror} from 'react-codemirror2'; import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
require('codemirror/theme/material-darker.css'); require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
const { Option } = Select; const {Option} = Select;
class SyncerEditPage extends React.Component { class SyncerEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -86,39 +86,39 @@ class SyncerEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("syncer:New Syncer") : i18next.t("syncer:Edit Syncer")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("syncer:New Syncer") : i18next.t("syncer:Edit Syncer")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitSyncerEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitSyncerEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.organization} onChange={(value => {this.updateSyncerField('organization', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.syncer.organization} onChange={(value => {this.updateSyncerField("organization", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.name} onChange={e => { <Input value={this.state.syncer.name} onChange={e => {
this.updateSyncerField('name', e.target.value); this.updateSyncerField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Type"), i18next.t("provider:Type - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.syncer.type} onChange={(value => {
this.updateSyncerField('type', value); this.updateSyncerField("type", value);
let syncer = this.state.syncer; let syncer = this.state.syncer;
syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer); syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table; syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
@@ -127,152 +127,152 @@ class SyncerEditPage extends React.Component {
}); });
})}> })}>
{ {
['Database', 'LDAP', 'Keycloak'] ["Database", "LDAP", "Keycloak"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("provider:Host"), i18next.t("provider:Host - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.host} onChange={e => { <Input value={this.state.syncer.host} onChange={e => {
this.updateSyncerField('host', e.target.value); this.updateSyncerField("host", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("provider:Port"), i18next.t("provider:Port - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.syncer.port} onChange={value => { <InputNumber value={this.state.syncer.port} onChange={value => {
this.updateSyncerField('port', value); this.updateSyncerField("port", value);
}} /> }} />
</Col> </Col>
</Row> </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:User"), i18next.t("general:User - Tooltip"))} : {Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.user} onChange={e => { <Input value={this.state.syncer.user} onChange={e => {
this.updateSyncerField('user', e.target.value); this.updateSyncerField("user", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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:Password"), i18next.t("general:Password - Tooltip"))} : {Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.password} onChange={e => { <Input value={this.state.syncer.password} onChange={e => {
this.updateSyncerField('password', e.target.value); this.updateSyncerField("password", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.databaseType} onChange={(value => {this.updateSyncerField('databaseType', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.syncer.databaseType} onChange={(value => {this.updateSyncerField("databaseType", value);})}>
{ {
[ [
{id: 'mysql', name: 'MySQL'}, {id: "mysql", name: "MySQL"},
{id: 'postgres', name: 'PostgreSQL'}, {id: "postgres", name: "PostgreSQL"},
{id: 'mssql', name: 'SQL Server'}, {id: "mssql", name: "SQL Server"},
{id: 'oracle', name: 'Oracle'}, {id: "oracle", name: "Oracle"},
{id: 'sqlite3', name: 'Sqlite 3'}, {id: "sqlite3", name: "Sqlite 3"},
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>) ].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.database} onChange={e => { <Input value={this.state.syncer.database} onChange={e => {
this.updateSyncerField('database', e.target.value); this.updateSyncerField("database", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.table} <Input value={this.state.syncer.table}
disabled={this.state.syncer.type === "Keycloak"} onChange={e => { disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
this.updateSyncerField('table', e.target.value); this.updateSyncerField("table", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Table primary key"), i18next.t("syncer:Table primary key - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Table primary key"), i18next.t("syncer:Table primary key - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.tablePrimaryKey} onChange={e => { <Input value={this.state.syncer.tablePrimaryKey} onChange={e => {
this.updateSyncerField('tablePrimaryKey', e.target.value); this.updateSyncerField("tablePrimaryKey", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Table columns"), i18next.t("syncer:Table columns - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Table columns"), i18next.t("syncer:Table columns - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<SyncerTableColumnTable <SyncerTableColumnTable
title={i18next.t("syncer:Table columns")} title={i18next.t("syncer:Table columns")}
table={this.state.syncer.tableColumns} table={this.state.syncer.tableColumns}
onUpdateTable={(value) => { this.updateSyncerField('tableColumns', value)}} onUpdateTable={(value) => {this.updateSyncerField("tableColumns", value);}}
/> />
</Col> </Col>
</Row> </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("syncer:Affiliation table"), i18next.t("syncer:Affiliation table - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Affiliation table"), i18next.t("syncer:Affiliation table - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.affiliationTable} onChange={e => { <Input value={this.state.syncer.affiliationTable} onChange={e => {
this.updateSyncerField('affiliationTable', e.target.value); this.updateSyncerField("affiliationTable", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Avatar base URL"), i18next.t("syncer:Avatar base URL - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Avatar base URL"), i18next.t("syncer:Avatar base URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.syncer.avatarBaseUrl} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.syncer.avatarBaseUrl} onChange={e => {
this.updateSyncerField('avatarBaseUrl', e.target.value); this.updateSyncerField("avatarBaseUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Sync interval"), i18next.t("syncer:Sync interval - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Sync interval"), i18next.t("syncer:Sync interval - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.syncer.syncInterval} onChange={value => { <InputNumber value={this.state.syncer.syncInterval} onChange={value => {
this.updateSyncerField('syncInterval', value); this.updateSyncerField("syncInterval", value);
}} /> }} />
</Col> </Col>
</Row> </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("syncer:Error text"), i18next.t("syncer:Error text - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Error text"), i18next.t("syncer:Error text - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<div style={{width: "100%", height: "300px"}} > <div style={{width: "100%", height: "300px"}} >
<CodeMirror <CodeMirror
value={this.state.syncer.errorText} value={this.state.syncer.errorText}
options={{mode: 'javascript', theme: "material-darker"}} options={{mode: "javascript", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => { onBeforeChange={(editor, data, value) => {
this.updateSyncerField("errorText", value); this.updateSyncerField("errorText", value);
}} }}
@@ -280,18 +280,18 @@ class SyncerEditPage extends React.Component {
</div> </div>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.syncer.isEnabled} onChange={checked => { <Switch checked={this.state.syncer.isEnabled} onChange={checked => {
this.updateSyncerField('isEnabled', checked); this.updateSyncerField("isEnabled", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitSyncerEdit(willExist) { submitSyncerEdit(willExist) {
@@ -299,19 +299,19 @@ class SyncerEditPage extends React.Component {
SyncerBackend.updateSyncer(this.state.syncer.owner, this.state.syncerName, syncer) SyncerBackend.updateSyncer(this.state.syncer.owner, this.state.syncerName, syncer)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
syncerName: this.state.syncer.name, syncerName: this.state.syncer.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/syncers`); this.props.history.push("/syncers");
} else { } else {
this.props.history.push(`/syncers/${this.state.syncer.name}`); this.props.history.push(`/syncers/${this.state.syncer.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateSyncerField('name', this.state.syncerName); this.updateSyncerField("name", this.state.syncerName);
} }
}) })
.catch(error => { .catch(error => {
@@ -322,7 +322,7 @@ class SyncerEditPage extends React.Component {
deleteSyncer() { deleteSyncer() {
SyncerBackend.deleteSyncer(this.state.syncer) SyncerBackend.deleteSyncer(this.state.syncer)
.then(() => { .then(() => {
this.props.history.push(`/syncers`); this.props.history.push("/syncers");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Syncer failed to delete: ${error}`); Setting.showMessage("error", `Syncer failed to delete: ${error}`);
@@ -335,10 +335,10 @@ class SyncerEditPage extends React.Component {
{ {
this.state.syncer !== null ? this.renderSyncer() : null this.state.syncer !== null ? this.renderSyncer() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitSyncerEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitSyncerEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as SyncerBackend from "./backend/SyncerBackend"; import * as SyncerBackend from "./backend/SyncerBackend";
@@ -42,15 +42,15 @@ class SyncerListPage extends BaseListPage {
avatarBaseUrl: "", avatarBaseUrl: "",
syncInterval: 10, syncInterval: 10,
isEnabled: false, isEnabled: false,
} };
} }
addSyncer() { addSyncer() {
const newSyncer = this.newSyncer(); const newSyncer = this.newSyncer();
SyncerBackend.addSyncer(newSyncer) SyncerBackend.addSyncer(newSyncer)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/syncers/${newSyncer.name}`, mode: "add"}); this.props.history.push({pathname: `/syncers/${newSyncer.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Syncer failed to add: ${error}`); Setting.showMessage("error", `Syncer failed to add: ${error}`);
@@ -60,12 +60,12 @@ class SyncerListPage extends BaseListPage {
deleteSyncer(i) { deleteSyncer(i) {
SyncerBackend.deleteSyncer(this.state.data[i]) SyncerBackend.deleteSyncer(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Syncer deleted successfully`); Setting.showMessage("success", "Syncer deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Syncer failed to delete: ${error}`); Setting.showMessage("error", `Syncer failed to delete: ${error}`);
@@ -76,9 +76,9 @@ class SyncerListPage extends BaseListPage {
this.setState({loading: true}); this.setState({loading: true});
SyncerBackend.runSyncer("admin", this.state.data[i].name) SyncerBackend.runSyncer("admin", this.state.data[i].name)
.then((res) => { .then((res) => {
this.setState({loading: false}); this.setState({loading: false});
Setting.showMessage("success", `Syncer sync users successfully`); Setting.showMessage("success", "Syncer sync users successfully");
} }
) )
.catch(error => { .catch(error => {
this.setState({loading: false}); this.setState({loading: false});
@@ -90,40 +90,40 @@ class SyncerListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/syncers/${text}`}> <Link to={`/syncers/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -131,108 +131,108 @@ class SyncerListPage extends BaseListPage {
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("provider:Type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
width: '100px', width: "100px",
sorter: true, sorter: true,
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'Database', value: 'Database'}, {text: "Database", value: "Database"},
{text: 'LDAP', value: 'LDAP'}, {text: "LDAP", value: "LDAP"},
], ],
}, },
{ {
title: i18next.t("provider:Host"), title: i18next.t("provider:Host"),
dataIndex: 'host', dataIndex: "host",
key: 'host', key: "host",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('host'), ...this.getColumnSearchProps("host"),
}, },
{ {
title: i18next.t("provider:Port"), title: i18next.t("provider:Port"),
dataIndex: 'port', dataIndex: "port",
key: 'port', key: "port",
width: '100px', width: "100px",
sorter: true, sorter: true,
...this.getColumnSearchProps('port'), ...this.getColumnSearchProps("port"),
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
dataIndex: 'user', dataIndex: "user",
key: 'user', key: "user",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('user'), ...this.getColumnSearchProps("user"),
}, },
{ {
title: i18next.t("general:Password"), title: i18next.t("general:Password"),
dataIndex: 'password', dataIndex: "password",
key: 'password', key: "password",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('password'), ...this.getColumnSearchProps("password"),
}, },
{ {
title: i18next.t("syncer:Database type"), title: i18next.t("syncer:Database type"),
dataIndex: 'databaseType', dataIndex: "databaseType",
key: 'databaseType', key: "databaseType",
width: '120px', width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType), sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
}, },
{ {
title: i18next.t("syncer:Database"), title: i18next.t("syncer:Database"),
dataIndex: 'database', dataIndex: "database",
key: 'database', key: "database",
width: '120px', width: "120px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("syncer:Table"), title: i18next.t("syncer:Table"),
dataIndex: 'table', dataIndex: "table",
key: 'table', key: "table",
width: '120px', width: "120px",
sorter: true, sorter: true,
}, },
{ {
title: i18next.t("syncer:Sync interval"), title: i18next.t("syncer:Sync interval"),
dataIndex: 'syncInterval', dataIndex: "syncInterval",
key: 'syncInterval', key: "syncInterval",
width: '130px', width: "130px",
sorter: true, sorter: true,
...this.getColumnSearchProps('syncInterval'), ...this.getColumnSearchProps("syncInterval"),
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
dataIndex: 'isEnabled', dataIndex: "isEnabled",
key: 'isEnabled', key: "isEnabled",
width: '120px', width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '240px', width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete syncer: ${record.name} ?`} title={`Sure to delete syncer: ${record.name} ?`}
onConfirm={() => this.deleteSyncer(index)} onConfirm={() => this.deleteSyncer(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -246,15 +246,15 @@ class SyncerListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={syncers} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={syncers} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Syncers")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Syncers")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addSyncer.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addSyncer.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -267,7 +267,7 @@ class SyncerListPage extends BaseListPage {
field = "type"; field = "type";
value = params.type; value = params.type;
} }
this.setState({ loading: true }); this.setState({loading: true});
SyncerBackend.getSyncers("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) SyncerBackend.getSyncers("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,12 +13,12 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from 'antd'; import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
const { Option } = Select; const {Option} = Select;
class SyncerTableColumnTable extends React.Component { class SyncerTableColumnTable extends React.Component {
constructor(props) { constructor(props) {
@@ -65,64 +65,64 @@ class SyncerTableColumnTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("syncer:Column name"), title: i18next.t("syncer:Column name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'name', e.target.value); this.updateField(table, index, "name", e.target.value);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("syncer:Column type"), title: i18next.t("syncer:Column type"),
dataIndex: 'type', dataIndex: "type",
key: 'type', key: "type",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {this.updateField(table, index, 'type', value);})}> <Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {this.updateField(table, index, "type", value);})}>
{ {
['string', 'integer', 'boolean'] ["string", "integer", "boolean"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("syncer:Casdoor column"), title: i18next.t("syncer:Casdoor column"),
dataIndex: 'casdoorName', dataIndex: "casdoorName",
key: 'casdoorName', key: "casdoorName",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {this.updateField(table, index, 'casdoorName', value);})}> <Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {this.updateField(table, index, "casdoorName", value);})}>
{ {
['Name', 'CreatedTime', 'UpdatedTime', 'Id', 'Type', 'Password', 'PasswordSalt', 'DisplayName', 'FirstName', 'LastName', 'Avatar', 'PermanentAvatar', ["Name", "CreatedTime", "UpdatedTime", "Id", "Type", "Password", "PasswordSalt", "DisplayName", "FirstName", "LastName", "Avatar", "PermanentAvatar",
'Email', 'EmailVerified', 'Phone', 'Location', 'Address', 'Affiliation', 'Title', 'IdCardType', 'IdCard', 'Homepage', 'Bio', 'Tag', 'Region', "Email", "EmailVerified", "Phone", "Location", "Address", "Affiliation", "Title", "IdCardType", "IdCard", "Homepage", "Bio", "Tag", "Region",
'Language', 'Gender', 'Birthday', 'Education', 'Score', 'Ranking', 'IsDefaultAvatar', 'IsOnline', 'IsAdmin', 'IsGlobalAdmin', 'IsForbidden', 'IsDeleted', 'CreatedIp'] "Language", "Gender", "Birthday", "Education", "Score", "Ranking", "IsDefaultAvatar", "IsOnline", "IsAdmin", "IsGlobalAdmin", "IsForbidden", "IsDeleted", "CreatedIp"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
) );
} }
}, },
{ {
title: i18next.t("syncer:Is hashed"), title: i18next.t("syncer:Is hashed"),
dataIndex: 'isHashed', dataIndex: "isHashed",
key: 'isHashed', key: "isHashed",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch checked={text} onChange={checked => { <Switch checked={text} onChange={checked => {
this.updateField(table, index, 'isHashed', checked); this.updateField(table, index, "isHashed", checked);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -143,12 +143,12 @@ class SyncerTableColumnTable extends React.Component {
return ( return (
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table rowKey="index" 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;
<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>
)} )}
/> />
); );
} }
@@ -156,7 +156,7 @@ class SyncerTableColumnTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -164,7 +164,7 @@ class SyncerTableColumnTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@@ -18,7 +18,7 @@ export function sendTestEmail(provider, email) {
testEmailProvider(provider, email) testEmailProvider(provider, email)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully send email`); Setting.showMessage("success", "Successfully send email");
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
@@ -32,7 +32,7 @@ export function connectSmtpServer(provider) {
testEmailProvider(provider) testEmailProvider(provider)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully connecting smtp server`); Setting.showMessage("success", "Successfully connecting smtp server");
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
@@ -49,11 +49,11 @@ function testEmailProvider(provider, email = "") {
sender: provider.displayName, sender: provider.displayName,
receivers: email === "" ? ["TestSmtpServer"] : [email], receivers: email === "" ? ["TestSmtpServer"] : [email],
provider: provider.name, provider: provider.name,
} };
return fetch(`${Setting.ServerUrl}/api/send-email`, { return fetch(`${Setting.ServerUrl}/api/send-email`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(emailForm) body: JSON.stringify(emailForm)
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row} from 'antd'; import {Button, Card, Col, Input, Row} from "antd";
import * as TokenBackend from "./backend/TokenBackend"; import * as TokenBackend from "./backend/TokenBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -65,102 +65,102 @@ class TokenEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("token:New Token") : i18next.t("token:Edit Token")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("token:New Token") : i18next.t("token:Edit Token")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitTokenEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitTokenEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.name} onChange={e => { <Input value={this.state.token.name} onChange={e => {
this.updateTokenField('name', e.target.value); this.updateTokenField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("general:Application")}: {i18next.t("general:Application")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.application} onChange={e => { <Input value={this.state.token.application} onChange={e => {
this.updateTokenField('application', e.target.value); this.updateTokenField("application", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("general:Organization")}: {i18next.t("general:Organization")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.organization} onChange={e => { <Input value={this.state.token.organization} onChange={e => {
this.updateTokenField('organization', e.target.value); this.updateTokenField("organization", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("general:User")}: {i18next.t("general:User")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.user} onChange={e => { <Input value={this.state.token.user} onChange={e => {
this.updateTokenField('user', e.target.value); this.updateTokenField("user", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("token:Authorization code")}: {i18next.t("token:Authorization code")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.code} onChange={e => { <Input value={this.state.token.code} onChange={e => {
this.updateTokenField('code', e.target.value); this.updateTokenField("code", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("token:Access token")}: {i18next.t("token:Access token")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.accessToken} onChange={e => { <Input value={this.state.token.accessToken} onChange={e => {
this.updateTokenField('accessToken', e.target.value); this.updateTokenField("accessToken", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("token:Expires in")}: {i18next.t("token:Expires in")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.expiresIn} onChange={e => { <Input value={this.state.token.expiresIn} onChange={e => {
this.updateTokenField('expiresIn', parseInt(e.target.value)); this.updateTokenField("expiresIn", parseInt(e.target.value));
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("token:Scope")}: {i18next.t("token:Scope")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.scope} onChange={e => { <Input value={this.state.token.scope} onChange={e => {
this.updateTokenField('scope', e.target.value); this.updateTokenField("scope", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("token:Token type")}: {i18next.t("token:Token type")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.token.tokenType} onChange={e => { <Input value={this.state.token.tokenType} onChange={e => {
this.updateTokenField('tokenType', e.target.value); this.updateTokenField("tokenType", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitTokenEdit(willExist) { submitTokenEdit(willExist) {
@@ -168,19 +168,19 @@ class TokenEditPage extends React.Component {
TokenBackend.updateToken(this.state.token.owner, this.state.tokenName, token) TokenBackend.updateToken(this.state.token.owner, this.state.tokenName, token)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
tokenName: this.state.token.name, tokenName: this.state.token.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/tokens`); this.props.history.push("/tokens");
} else { } else {
this.props.history.push(`/tokens/${this.state.token.name}`); this.props.history.push(`/tokens/${this.state.token.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateTokenField('name', this.state.tokenName); this.updateTokenField("name", this.state.tokenName);
} }
}) })
.catch(error => { .catch(error => {
@@ -191,7 +191,7 @@ class TokenEditPage extends React.Component {
deleteToken() { deleteToken() {
TokenBackend.deleteToken(this.state.token) TokenBackend.deleteToken(this.state.token)
.then(() => { .then(() => {
this.props.history.push(`/tokens`); this.props.history.push("/tokens");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Token failed to delete: ${error}`); Setting.showMessage("error", `Token failed to delete: ${error}`);
@@ -201,15 +201,15 @@ class TokenEditPage extends React.Component {
render() { render() {
return ( return (
<div> <div>
{ {
this.state.token !== null ? this.renderToken() : null this.state.token !== null ? this.renderToken() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitTokenEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitTokenEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div> </div>
</div>
); );
} }
} }

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 {Button, Popconfirm, Table} from 'antd'; import {Button, Popconfirm, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as TokenBackend from "./backend/TokenBackend"; import * as TokenBackend from "./backend/TokenBackend";
@@ -35,15 +35,15 @@ class TokenListPage extends BaseListPage {
expiresIn: 7200, expiresIn: 7200,
scope: "read", scope: "read",
tokenType: "Bearer", tokenType: "Bearer",
} };
} }
addToken() { addToken() {
const newToken = this.newToken(); const newToken = this.newToken();
TokenBackend.addToken(newToken) TokenBackend.addToken(newToken)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/tokens/${newToken.name}`, mode: "add"}); this.props.history.push({pathname: `/tokens/${newToken.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Token failed to add: ${error}`); Setting.showMessage("error", `Token failed to add: ${error}`);
@@ -53,12 +53,12 @@ class TokenListPage extends BaseListPage {
deleteToken(i) { deleteToken(i) {
TokenBackend.deleteToken(this.state.data[i]) TokenBackend.deleteToken(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Token deleted successfully`); Setting.showMessage("success", "Token deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Token failed to delete: ${error}`); Setting.showMessage("error", `Token failed to delete: ${error}`);
@@ -69,25 +69,25 @@ class TokenListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: (Setting.isMobile()) ? "100px" : "300px", width: (Setting.isMobile()) ? "100px" : "300px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/tokens/${text}`}> <Link to={`/tokens/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -95,87 +95,87 @@ class TokenListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Application"), title: i18next.t("general:Application"),
dataIndex: 'application', dataIndex: "application",
key: 'application', key: "application",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('application'), ...this.getColumnSearchProps("application"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/applications/${text}`}> <Link to={`/applications/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
dataIndex: 'user', dataIndex: "user",
key: 'user', key: "user",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('user'), ...this.getColumnSearchProps("user"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/users/${record.organization}/${text}`}> <Link to={`/users/${record.organization}/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("token:Authorization code"), title: i18next.t("token:Authorization code"),
dataIndex: 'code', dataIndex: "code",
key: 'code', key: "code",
// width: '150px', // width: '150px',
sorter: true, sorter: true,
...this.getColumnSearchProps('code'), ...this.getColumnSearchProps("code"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getClickable(text); return Setting.getClickable(text);
} }
}, },
{ {
title: i18next.t("token:Access token"), title: i18next.t("token:Access token"),
dataIndex: 'accessToken', dataIndex: "accessToken",
key: 'accessToken', key: "accessToken",
// width: '150px', // width: '150px',
sorter: true, sorter: true,
ellipsis: true, ellipsis: true,
...this.getColumnSearchProps('accessToken'), ...this.getColumnSearchProps("accessToken"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getClickable(text); return Setting.getClickable(text);
} }
}, },
{ {
title: i18next.t("token:Expires in"), title: i18next.t("token:Expires in"),
dataIndex: 'expiresIn', dataIndex: "expiresIn",
key: 'expiresIn', key: "expiresIn",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('expiresIn'), ...this.getColumnSearchProps("expiresIn"),
}, },
{ {
title: i18next.t("token:Scope"), title: i18next.t("token:Scope"),
dataIndex: 'scope', dataIndex: "scope",
key: 'scope', key: "scope",
width: '110px', width: "110px",
sorter: true, sorter: true,
...this.getColumnSearchProps('scope'), ...this.getColumnSearchProps("scope"),
}, },
// { // {
// title: i18next.t("token:Token type"), // title: i18next.t("token:Token type"),
@@ -186,22 +186,22 @@ class TokenListPage extends BaseListPage {
// }, // },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/tokens/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/tokens/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete token: ${record.name} ?`} title={`Sure to delete token: ${record.name} ?`}
onConfirm={() => this.deleteToken(index)} onConfirm={() => this.deleteToken(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -215,15 +215,15 @@ class TokenListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={tokens} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={tokens} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Tokens")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Tokens")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addToken.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addToken.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -232,7 +232,7 @@ class TokenListPage extends BaseListPage {
fetch = (params = {}) => { fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText; let field = params.searchedColumn, value = params.searchText;
let sortField = params.sortField, sortOrder = params.sortOrder; let sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({ loading: true }); this.setState({loading: true});
TokenBackend.getTokens("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) TokenBackend.getTokens("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, LinkOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from 'antd'; import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -63,20 +63,20 @@ class UrlTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("application:Redirect URL"), title: i18next.t("application:Redirect URL"),
dataIndex: 'id', dataIndex: "id",
key: 'id', key: "id",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input prefix={<LinkOutlined/>} value={text} onChange={e => { <Input prefix={<LinkOutlined />} value={text} onChange={e => {
this.updateField(table, index, e.target.value); this.updateField(table, index, e.target.value);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -97,12 +97,12 @@ class UrlTable extends React.Component {
return ( return (
<Table rowKey="index" columns={columns} dataSource={table.map((row, i) => ({id: row, index: i}))} size="middle" bordered pagination={false} <Table rowKey="index" columns={columns} dataSource={table.map((row, i) => ({id: row, index: i}))} size="middle" bordered pagination={false}
title={() => ( title={() => (
<div> <div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp; {this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<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>
)} )}
/> />
); );
} }
@@ -110,7 +110,7 @@ class UrlTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -118,7 +118,7 @@ class UrlTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@@ -13,8 +13,9 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from 'antd'; import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import * as UserWebauthnBackend from "./backend/UserWebauthnBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
@@ -27,13 +28,14 @@ import AffiliationSelect from "./common/AffiliationSelect";
import OAuthWidget from "./common/OAuthWidget"; import OAuthWidget from "./common/OAuthWidget";
import SamlWidget from "./common/SamlWidget"; import SamlWidget from "./common/SamlWidget";
import SelectRegionBox from "./SelectRegionBox"; import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import {Controlled as CodeMirror} from 'react-codemirror2'; import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
require('codemirror/theme/material-darker.css'); require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
const { Option } = Select; const {Option} = Select;
class UserEditPage extends React.Component { class UserEditPage extends React.Component {
constructor(props) { constructor(props) {
@@ -165,156 +167,156 @@ class UserEditPage extends React.Component {
if (accountItem.name === "Organization") { if (accountItem.name === "Organization") {
return ( return (
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.owner} onChange={(value => {this.updateUserField('owner', value);})}> <Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.owner} onChange={(value => {this.updateUserField("owner", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "ID") { } else if (accountItem.name === "ID") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} : {Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.id} disabled={disabled} /> <Input value={this.state.user.id} disabled={disabled} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Name") { } else if (accountItem.name === "Name") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.name} disabled={disabled} onChange={e => { <Input value={this.state.user.name} disabled={disabled} onChange={e => {
this.updateUserField('name', e.target.value); this.updateUserField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Display name") { } else if (accountItem.name === "Display name") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.displayName} onChange={e => { <Input value={this.state.user.displayName} onChange={e => {
this.updateUserField('displayName', e.target.value); this.updateUserField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Avatar") { } else if (accountItem.name === "Avatar") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} : {Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:URL")}: {i18next.t("general:URL")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.user.avatar} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.user.avatar} onChange={e => {
this.updateUserField('avatar', e.target.value); this.updateUserField("avatar", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<a target="_blank" rel="noreferrer" href={this.state.user.avatar}> <a target="_blank" rel="noreferrer" href={this.state.user.avatar}>
<img src={this.state.user.avatar} alt={this.state.user.avatar} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.user.avatar} alt={this.state.user.avatar} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}}> <Row style={{marginTop: "20px"}}>
<CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} account={this.props.account} /> <CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} account={this.props.account} />
</Row> </Row>
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "User type") { } else if (accountItem.name === "User type") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} : {Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.user.type} onChange={(value => {this.updateUserField('type', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.user.type} onChange={(value => {this.updateUserField("type", value);})}>
{ {
['normal-user'] ["normal-user"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Password") { } else if (accountItem.name === "Password") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} : {Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<PasswordModal user={this.state.user} account={this.props.account} disabled={disabled} /> <PasswordModal user={this.state.user} account={this.props.account} disabled={disabled} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Email") { } else if (accountItem.name === "Email") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} : {Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col> </Col>
<Col style={{paddingRight: '20px'}} span={11} > <Col style={{paddingRight: "20px"}} span={11} >
<Input value={this.state.user.email} <Input value={this.state.user.email}
disabled={disabled} disabled={disabled}
onChange={e => { onChange={e => {
this.updateUserField('email', e.target.value); this.updateUserField("email", e.target.value);
}} /> }} />
</Col> </Col>
<Col span={11} > <Col span={11} >
{ this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Phone") { } else if (accountItem.name === "Phone") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} : {Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col> </Col>
<Col style={{paddingRight: '20px'}} span={11} > <Col style={{paddingRight: "20px"}} span={11} >
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} <Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
disabled={disabled} disabled={disabled}
onChange={e => { onChange={e => {
this.updateUserField('phone', e.target.value); this.updateUserField("phone", e.target.value);
}}/> }} />
</Col> </Col>
<Col span={11} > <Col span={11} >
{ this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Country/Region") { } else if (accountItem.name === "Country/Region") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} : {Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -323,113 +325,139 @@ class UserEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Location") { } else if (accountItem.name === "Location") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} : {Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.location} onChange={e => { <Input value={this.state.user.location} onChange={e => {
this.updateUserField('location', e.target.value); this.updateUserField("location", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Affiliation") { } else if (accountItem.name === "Affiliation") {
return ( return (
(this.state.application === null || this.state.user === null) ? null : ( (this.state.application === null || this.state.user === null) ? null : (
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} /> <AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => {return this.updateUserField(key, value);}} />
) )
) );
} else if (accountItem.name === "Title") { } else if (accountItem.name === "Title") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} : {Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.title} onChange={e => { <Input value={this.state.user.title} onChange={e => {
this.updateUserField('title', e.target.value); this.updateUserField("title", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Homepage") { } else if (accountItem.name === "Homepage") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} : {Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.homepage} onChange={e => { <Input value={this.state.user.homepage} onChange={e => {
this.updateUserField('homepage', e.target.value); this.updateUserField("homepage", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Bio") { } else if (accountItem.name === "Bio") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} : {Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.user.bio} onChange={e => { <Input value={this.state.user.bio} onChange={e => {
this.updateUserField('bio', e.target.value); this.updateUserField("bio", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Tag") { } else if (accountItem.name === "Tag") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} : {Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
{ {
this.state.application?.organizationObj.tags?.length > 0 ? ( this.state.application?.organizationObj.tags?.length > 0 ? (
<Select virtual={false} style={{width: '100%'}} value={this.state.user.tag} onChange={(value => {this.updateUserField('tag', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.user.tag} onChange={(value => {this.updateUserField("tag", value);})}>
{ {
this.state.application.organizationObj.tags?.map((tag, index) => { this.state.application.organizationObj.tags?.map((tag, index) => {
const tokens = tag.split("|"); const tokens = tag.split("|");
const value = tokens[0]; const value = tokens[0];
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1]; const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
return <Option key={index} value={value}>{displayValue}</Option> return <Option key={index} value={value}>{displayValue}</Option>;
}) })
} }
</Select> </Select>
) : ( ) : (
<Input value={this.state.user.tag} onChange={e => { <Input value={this.state.user.tag} onChange={e => {
this.updateUserField('tag', e.target.value); this.updateUserField("tag", e.target.value);
}} /> }} />
) )
} }
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Signup application") { } else if (accountItem.name === "Signup application") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} : {Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} disabled={disabled} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField('signupApplication', value);})}> <Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.signupApplication} onChange={(value => {this.updateUserField("signupApplication", value);})}>
{ {
this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>) this.state.applications.map((application, index) => <Option key={index} value={application.name}>{application.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Roles") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Roles"), i18next.t("general:Roles - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.roles.map(role => role.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "Permissions") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Permissions"), i18next.t("general:Permissions - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.permissions.map(permission => permission.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "3rd-party logins") { } else if (accountItem.name === "3rd-party logins") {
return ( return (
!this.isSelfOrAdmin() ? null : ( !this.isSelfOrAdmin() ? null : (
<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("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} : {Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
@@ -438,9 +466,9 @@ class UserEditPage extends React.Component {
(this.state.application === null || this.state.user === null) ? null : ( (this.state.application === null || this.state.user === null) ? null : (
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) => this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
(providerItem.provider.category === "OAuth") ? ( (providerItem.provider.category === "OAuth") ? (
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} /> <OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
) : ( ) : (
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => { return this.unlinked()}} /> <SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
) )
) )
) )
@@ -449,73 +477,84 @@ class UserEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
) )
) );
} else if (accountItem.name === "Properties") { } else if (accountItem.name === "Properties") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("user:Properties")}: {i18next.t("user:Properties")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<CodeMirror <CodeMirror
value={JSON.stringify(this.state.user.properties, null, 4)} value={JSON.stringify(this.state.user.properties, null, 4)}
options={{mode: 'javascript', theme: "material-darker"}} options={{mode: "javascript", theme: "material-darker"}}
/> />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Is admin") { } else if (accountItem.name === "Is admin") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isAdmin} onChange={checked => { <Switch checked={this.state.user.isAdmin} onChange={checked => {
this.updateUserField('isAdmin', checked); this.updateUserField("isAdmin", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Is global admin") { } else if (accountItem.name === "Is global admin") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => { <Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
this.updateUserField('isGlobalAdmin', checked); this.updateUserField("isGlobalAdmin", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Is forbidden") { } else if (accountItem.name === "Is forbidden") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isForbidden} onChange={checked => { <Switch checked={this.state.user.isForbidden} onChange={checked => {
this.updateUserField('isForbidden', checked); this.updateUserField("isForbidden", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if (accountItem.name === "Is deleted") { } else if (accountItem.name === "Is deleted") {
return ( return (
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isDeleted} onChange={checked => { <Switch checked={this.state.user.isDeleted} onChange={checked => {
this.updateUserField('isDeleted', checked); this.updateUserField("isDeleted", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
) );
} else if(accountItem.name === "WebAuthn credentials") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:WebAuthn credentials"), i18next.t("user:WebAuthn credentials"))} :
</Col>
<Col span={22} >
<WebAuthnCredentialTable table={this.state.user.webauthnCredentials} updateTable={(table) => {this.updateUserField("webauthnCredentials", table);}} refresh={this.getUser.bind(this)} />
</Col>
</Row>
);
} }
} }
@@ -525,10 +564,10 @@ class UserEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("user:New User") : i18next.t("user:Edit User")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
{ {
this.state.application?.organizationObj.accountItems?.map(accountItem => { this.state.application?.organizationObj.accountItems?.map(accountItem => {
return ( return (
@@ -537,11 +576,11 @@ class UserEditPage extends React.Component {
this.renderAccountItem(accountItem) this.renderAccountItem(accountItem)
} }
</React.Fragment> </React.Fragment>
) );
}) })
} }
</Card> </Card>
) );
} }
submitUserEdit(willExist) { submitUserEdit(willExist) {
@@ -549,7 +588,7 @@ class UserEditPage extends React.Component {
UserBackend.updateUser(this.state.organizationName, this.state.userName, user) UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
organizationName: this.state.user.owner, organizationName: this.state.user.owner,
userName: this.state.user.name, userName: this.state.user.name,
@@ -557,15 +596,15 @@ class UserEditPage extends React.Component {
if (this.props.history !== undefined) { if (this.props.history !== undefined) {
if (willExist) { if (willExist) {
this.props.history.push(`/users`); this.props.history.push("/users");
} else { } else {
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`); this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
} }
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateUserField('owner', this.state.organizationName); this.updateUserField("owner", this.state.organizationName);
this.updateUserField('name', this.state.userName); this.updateUserField("name", this.state.userName);
} }
}) })
.catch(error => { .catch(error => {
@@ -576,7 +615,7 @@ class UserEditPage extends React.Component {
deleteUser() { deleteUser() {
UserBackend.deleteUser(this.state.user) UserBackend.deleteUser(this.state.user)
.then(() => { .then(() => {
this.props.history.push(`/users`); this.props.history.push("/users");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`); Setting.showMessage("error", `User failed to delete: ${error}`);
@@ -586,26 +625,26 @@ class UserEditPage extends React.Component {
render() { render() {
return ( return (
<div> <div>
{ {
this.state.loading ? <Spin size="large" /> : ( this.state.loading ? <Spin size="large" /> : (
this.state.user !== null ? this.renderUser() : this.state.user !== null ? this.renderUser() :
<Result <Result
status="404" status="404"
title="404 NOT FOUND" title="404 NOT FOUND"
subTitle={i18next.t("general:Sorry, the user you visited does not exist or you are not authorized to access this user.")} subTitle={i18next.t("general:Sorry, the user you visited does not exist or you are not authorized to access this user.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/> />
) )
} }
{ {
this.state.user === null ? null : this.state.user === null ? null :
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} }
</div> </div>
); );
} }
} }

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 {Button, Popconfirm, Switch, Table, Upload} from 'antd'; import {Button, Popconfirm, Switch, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -34,8 +34,8 @@ class UserListPage extends BaseListPage {
pageSize: 10, pageSize: 10,
}, },
loading: false, loading: false,
searchText: '', searchText: "",
searchedColumn: '', searchedColumn: "",
}; };
} }
@@ -62,15 +62,15 @@ class UserListPage extends BaseListPage {
isDeleted: false, isDeleted: false,
properties: {}, properties: {},
signupApplication: "app-built-in", signupApplication: "app-built-in",
} };
} }
addUser() { addUser() {
const newUser = this.newUser(); const newUser = this.newUser();
UserBackend.addUser(newUser) UserBackend.addUser(newUser)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"}); this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `User failed to add: ${error}`); Setting.showMessage("error", `User failed to add: ${error}`);
@@ -80,12 +80,12 @@ class UserListPage extends BaseListPage {
deleteUser(i) { deleteUser(i) {
UserBackend.deleteUser(this.state.data[i]) UserBackend.deleteUser(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `User deleted successfully`); Setting.showMessage("success", "User deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`); Setting.showMessage("error", `User failed to delete: ${error}`);
@@ -93,26 +93,26 @@ class UserListPage extends BaseListPage {
} }
uploadFile(info) { uploadFile(info) {
const { status, response: res } = info.file; const {status, response: res} = info.file;
if (status === 'done') { if (status === "done") {
if (res.status === 'ok') { if (res.status === "ok") {
Setting.showMessage("success", `Users uploaded successfully, refreshing the page`); Setting.showMessage("success", "Users uploaded successfully, refreshing the page");
const { pagination } = this.state; const {pagination} = this.state;
this.fetch({ pagination }); this.fetch({pagination});
} else { } else {
Setting.showMessage("error", `Users failed to upload: ${res.msg}`); Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
} }
} else if (status === 'error') { } else if (status === "error") {
Setting.showMessage("error", `File failed to upload`); Setting.showMessage("error", "File failed to upload");
} }
} }
renderUpload() { renderUpload() {
const props = { const props = {
name: 'file', name: "file",
accept: '.xlsx', accept: ".xlsx",
method: 'post', method: "post",
action: `${Setting.ServerUrl}/api/upload-users`, action: `${Setting.ServerUrl}/api/upload-users`,
withCredentials: true, withCredentials: true,
onChange: (info) => { onChange: (info) => {
@@ -126,7 +126,7 @@ class UserListPage extends BaseListPage {
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")} <UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
</Button> </Button>
</Upload> </Upload>
) );
} }
renderTable(users) { renderTable(users) {
@@ -134,63 +134,63 @@ class UserListPage extends BaseListPage {
var countries = require("i18n-iso-countries"); var countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json")); countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json"));
for (var index in users) { for (var index in users) {
users[index].region = countries.getName(users[index].region, i18next.language, {select: "official"}) users[index].region = countries.getName(users[index].region, i18next.language, {select: "official"});
} }
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'owner', dataIndex: "owner",
key: 'owner', key: "owner",
width: (Setting.isMobile()) ? "100px" : "120px", width: (Setting.isMobile()) ? "100px" : "120px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('owner'), ...this.getColumnSearchProps("owner"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Application"), title: i18next.t("general:Application"),
dataIndex: 'signupApplication', dataIndex: "signupApplication",
key: 'signupApplication', key: "signupApplication",
width: (Setting.isMobile()) ? "100px" : "120px", width: (Setting.isMobile()) ? "100px" : "120px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('signupApplication'), ...this.getColumnSearchProps("signupApplication"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/applications/${text}`}> <Link to={`/applications/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: (Setting.isMobile()) ? "80px" : "110px", width: (Setting.isMobile()) ? "80px" : "110px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/users/${record.owner}/${text}`}> <Link to={`/users/${record.owner}/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -198,47 +198,47 @@ class UserListPage extends BaseListPage {
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('displayName'), ...this.getColumnSearchProps("displayName"),
}, },
{ {
title: i18next.t("general:Avatar"), title: i18next.t("general:Avatar"),
dataIndex: 'avatar', dataIndex: "avatar",
key: 'avatar', key: "avatar",
width: '80px', width: "80px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={50} /> <img src={text} alt={text} width={50} />
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Email"), title: i18next.t("general:Email"),
dataIndex: 'email', dataIndex: "email",
key: 'email', key: "email",
width: '160px', width: "160px",
sorter: true, sorter: true,
...this.getColumnSearchProps('email'), ...this.getColumnSearchProps("email"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a href={`mailto:${text}`}> <a href={`mailto:${text}`}>
{text} {text}
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("general:Phone"), title: i18next.t("general:Phone"),
dataIndex: 'phone', dataIndex: "phone",
key: 'phone', key: "phone",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('phone'), ...this.getColumnSearchProps("phone"),
}, },
// { // {
// title: 'Phone', // title: 'Phone',
@@ -249,94 +249,94 @@ class UserListPage extends BaseListPage {
// }, // },
{ {
title: i18next.t("user:Affiliation"), title: i18next.t("user:Affiliation"),
dataIndex: 'affiliation', dataIndex: "affiliation",
key: 'affiliation', key: "affiliation",
width: '140px', width: "140px",
sorter: true, sorter: true,
...this.getColumnSearchProps('affiliation'), ...this.getColumnSearchProps("affiliation"),
}, },
{ {
title: i18next.t("user:Country/Region"), title: i18next.t("user:Country/Region"),
dataIndex: 'region', dataIndex: "region",
key: 'region', key: "region",
width: '140px', width: "140px",
sorter: true, sorter: true,
...this.getColumnSearchProps('region'), ...this.getColumnSearchProps("region"),
}, },
{ {
title: i18next.t("user:Tag"), title: i18next.t("user:Tag"),
dataIndex: 'tag', dataIndex: "tag",
key: 'tag', key: "tag",
width: '110px', width: "110px",
sorter: true, sorter: true,
...this.getColumnSearchProps('tag'), ...this.getColumnSearchProps("tag"),
}, },
{ {
title: i18next.t("user:Is admin"), title: i18next.t("user:Is admin"),
dataIndex: 'isAdmin', dataIndex: "isAdmin",
key: 'isAdmin', key: "isAdmin",
width: '110px', width: "110px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("user:Is global admin"), title: i18next.t("user:Is global admin"),
dataIndex: 'isGlobalAdmin', dataIndex: "isGlobalAdmin",
key: 'isGlobalAdmin', key: "isGlobalAdmin",
width: '140px', width: "140px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("user:Is forbidden"), title: i18next.t("user:Is forbidden"),
dataIndex: 'isForbidden', dataIndex: "isForbidden",
key: 'isForbidden', key: "isForbidden",
width: '110px', width: "110px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("user:Is deleted"), title: i18next.t("user:Is deleted"),
dataIndex: 'isDeleted', dataIndex: "isDeleted",
key: 'isDeleted', key: "isDeleted",
width: '110px', width: "110px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '190px', width: "190px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete user: ${record.name} ?`} title={`Sure to delete user: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)} onConfirm={() => this.deleteUser(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -350,18 +350,18 @@ class UserListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button> <Button style={{marginRight: "5px"}} type="primary" size="small" onClick={this.addUser.bind(this)}>{i18next.t("general:Add")}</Button>
{ {
this.renderUpload() this.renderUpload()
} }
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -370,7 +370,7 @@ class UserListPage extends BaseListPage {
fetch = (params = {}) => { fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText; let field = params.searchedColumn, value = params.searchText;
let sortField = params.sortField, sortOrder = params.sortOrder; let sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({ loading: true }); this.setState({loading: true});
if (this.state.organizationName === undefined) { if (this.state.organizationName === undefined) {
UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {

View File

@@ -0,0 +1,76 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Table} from "antd";
import i18next from "i18next";
import * as UserWebauthnBackend from "./backend/UserWebauthnBackend";
import * as Setting from "./Setting";
class WebAuthnCredentialTable extends React.Component {
render() {
const columns = [
{
title: i18next.t("user:WebAuthn credentials"),
dataIndex: "ID",
key: "ID",
},
{
title: i18next.t("general:Action"),
key: "action",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);
}
}
];
return (
<Table scroll={{x: "max-content"}} rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
title={() => (
<div>
{i18next.t("user:WebAuthn credentials")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
{i18next.t("general:Add")}
</Button>
</div>
)}
/>
);
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.props.updateTable(table);
}
registerWebAuthn() {
UserWebauthnBackend.registerWebauthnCredential().then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully added webauthn credentials");
} else {
Setting.showMessage("error", res.msg);
}
this.props.refresh();
}).catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
});
}
}
export default WebAuthnCredentialTable;

View File

@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import * as WebhookBackend from "./backend/WebhookBackend"; import * as WebhookBackend from "./backend/WebhookBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -21,12 +21,12 @@ import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import WebhookHeaderTable from "./WebhookHeaderTable"; import WebhookHeaderTable from "./WebhookHeaderTable";
import {Controlled as CodeMirror} from 'react-codemirror2'; import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
require('codemirror/theme/material-darker.css'); require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
const { Option } = Select; const {Option} = Select;
const previewTemplate = { const previewTemplate = {
"id": 9078, "id": 9078,
@@ -152,144 +152,144 @@ class WebhookEditPage extends React.Component {
<div> <div>
{this.state.mode === "add" ? i18next.t("webhook:New Webhook") : i18next.t("webhook:Edit Webhook")}&nbsp;&nbsp;&nbsp;&nbsp; {this.state.mode === "add" ? i18next.t("webhook:New Webhook") : i18next.t("webhook:Edit Webhook")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitWebhookEdit(false)}>{i18next.t("general:Save")}</Button> <Button onClick={() => this.submitWebhookEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner"> } style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.webhook.organization} onChange={(value => {this.updateWebhookField('organization', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.webhook.organization} onChange={(value => {this.updateWebhookField("organization", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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:Name"), i18next.t("general:Name - Tooltip"))} : {Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.webhook.name} onChange={e => { <Input value={this.state.webhook.name} onChange={e => {
this.updateWebhookField('name', e.target.value); this.updateWebhookField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("webhook:URL"), i18next.t("webhook:URL - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:URL"), i18next.t("webhook:URL - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.webhook.url} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.webhook.url} onChange={e => {
this.updateWebhookField('url', e.target.value); this.updateWebhookField("url", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </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("webhook:Method"), i18next.t("webhook:Method - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:Method"), i18next.t("webhook:Method - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.webhook.method} onChange={(value => {this.updateWebhookField('method', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.webhook.method} onChange={(value => {this.updateWebhookField("method", value);})}>
{ {
[ [
{id: 'POST', name: 'POST'}, {id: "POST", name: "POST"},
{id: 'GET', name: 'GET'}, {id: "GET", name: "GET"},
{id: 'PUT', name: 'PUT'}, {id: "PUT", name: "PUT"},
{id: 'DELETE', name: 'DELETE'}, {id: "DELETE", name: "DELETE"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>) ].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.webhook.contentType} onChange={(value => {this.updateWebhookField('contentType', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.webhook.contentType} onChange={(value => {this.updateWebhookField("contentType", value);})}>
{ {
[ [
{id: 'application/json', name: 'application/json'}, {id: "application/json", name: "application/json"},
{id: 'application/x-www-form-urlencoded', name: 'application/x-www-form-urlencoded'}, {id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
].map((contentType, index) => <Option key={index} value={contentType.id}>{contentType.name}</Option>) ].map((contentType, index) => <Option key={index} value={contentType.id}>{contentType.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </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("webhook:Headers"), i18next.t("webhook:Headers - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:Headers"), i18next.t("webhook:Headers - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<WebhookHeaderTable <WebhookHeaderTable
title={i18next.t("webhook:Headers")} title={i18next.t("webhook:Headers")}
table={this.state.webhook.headers} table={this.state.webhook.headers}
onUpdateTable={(value) => { this.updateWebhookField('headers', value)}} onUpdateTable={(value) => {this.updateWebhookField("headers", value);}}
/> />
</Col> </Col>
</Row> </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("webhook:Events"), i18next.t("webhook:Events - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:Events"), i18next.t("webhook:Events - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} <Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.webhook.events} value={this.state.webhook.events}
onChange={value => { onChange={value => {
this.updateWebhookField('events', value); this.updateWebhookField("events", value);
}} > }} >
{ {
( (
["signup", "login", "logout", "update-user"].map((option, index) => { ["signup", "login", "logout", "update-user"].map((option, index) => {
return ( return (
<Option key={option} value={option}>{option}</Option> <Option key={option} value={option}>{option}</Option>
) );
}) })
) )
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} : {Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.webhook.isUserExtended} onChange={checked => { <Switch checked={this.state.webhook.isUserExtended} onChange={checked => {
this.updateWebhookField('isUserExtended', checked); this.updateWebhookField("isUserExtended", checked);
}} /> }} />
</Col> </Col>
</Row> </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:Preview"), i18next.t("general:Preview - Tooltip"))} : {Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<div style={{width: "900px", height: "300px"}} > <div style={{width: "900px", height: "300px"}} >
<CodeMirror <CodeMirror
value={previewText} value={previewText}
options={{mode: 'javascript', theme: "material-darker"}} options={{mode: "javascript", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {}} onBeforeChange={(editor, data, value) => {}}
/> />
</div> </div>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} : {Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col> </Col>
<Col span={1} > <Col span={1} >
<Switch checked={this.state.webhook.isEnabled} onChange={checked => { <Switch checked={this.state.webhook.isEnabled} onChange={checked => {
this.updateWebhookField('isEnabled', checked); this.updateWebhookField("isEnabled", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitWebhookEdit(willExist) { submitWebhookEdit(willExist) {
@@ -297,19 +297,19 @@ class WebhookEditPage extends React.Component {
WebhookBackend.updateWebhook(this.state.webhook.owner, this.state.webhookName, webhook) WebhookBackend.updateWebhook(this.state.webhook.owner, this.state.webhookName, webhook)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
webhookName: this.state.webhook.name, webhookName: this.state.webhook.name,
}); });
if (willExist) { if (willExist) {
this.props.history.push(`/webhooks`); this.props.history.push("/webhooks");
} else { } else {
this.props.history.push(`/webhooks/${this.state.webhook.name}`); this.props.history.push(`/webhooks/${this.state.webhook.name}`);
} }
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.updateWebhookField('name', this.state.webhookName); this.updateWebhookField("name", this.state.webhookName);
} }
}) })
.catch(error => { .catch(error => {
@@ -320,7 +320,7 @@ class WebhookEditPage extends React.Component {
deleteWebhook() { deleteWebhook() {
WebhookBackend.deleteWebhook(this.state.webhook) WebhookBackend.deleteWebhook(this.state.webhook)
.then(() => { .then(() => {
this.props.history.push(`/webhooks`); this.props.history.push("/webhooks");
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `Webhook failed to delete: ${error}`); Setting.showMessage("error", `Webhook failed to delete: ${error}`);
@@ -333,10 +333,10 @@ class WebhookEditPage extends React.Component {
{ {
this.state.webhook !== null ? this.renderWebhook() : null this.state.webhook !== null ? this.renderWebhook() : null
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}> <div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitWebhookEdit(false)}>{i18next.t("general:Save")}</Button> <Button size="large" onClick={() => this.submitWebhookEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button> <Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null} {this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null}
</div> </div>
</div> </div>
); );

View File

@@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from 'antd'; import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@@ -63,33 +63,33 @@ class WebhookHeaderTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("webhook:Name"), title: i18next.t("webhook:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '250px', width: "250px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'name', e.target.value); this.updateField(table, index, "name", e.target.value);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("webhook:Value"), title: i18next.t("webhook:Value"),
dataIndex: 'value', dataIndex: "value",
key: 'value', key: "value",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'value', e.target.value); this.updateField(table, index, "value", e.target.value);
}} /> }} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@@ -110,12 +110,12 @@ class WebhookHeaderTable extends React.Component {
return ( return (
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table rowKey="index" 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;
<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>
)} )}
/> />
); );
} }
@@ -123,7 +123,7 @@ class WebhookHeaderTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@@ -131,7 +131,7 @@ class WebhookHeaderTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

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 {Button, Popconfirm, Switch, Table} from 'antd'; import {Button, Popconfirm, Switch, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as WebhookBackend from "./backend/WebhookBackend"; import * as WebhookBackend from "./backend/WebhookBackend";
@@ -35,15 +35,15 @@ class WebhookListPage extends BaseListPage {
headers: [], headers: [],
events: ["signup", "login", "logout", "update-user"], events: ["signup", "login", "logout", "update-user"],
isEnabled: true, isEnabled: true,
} };
} }
addWebhook() { addWebhook() {
const newWebhook = this.newWebhook(); const newWebhook = this.newWebhook();
WebhookBackend.addWebhook(newWebhook) WebhookBackend.addWebhook(newWebhook)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/webhooks/${newWebhook.name}`, mode: "add"}); this.props.history.push({pathname: `/webhooks/${newWebhook.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Webhook failed to add: ${error}`); Setting.showMessage("error", `Webhook failed to add: ${error}`);
@@ -53,12 +53,12 @@ class WebhookListPage extends BaseListPage {
deleteWebhook(i) { deleteWebhook(i) {
WebhookBackend.deleteWebhook(this.state.data[i]) WebhookBackend.deleteWebhook(this.state.data[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Webhook deleted successfully`); Setting.showMessage("success", "Webhook deleted successfully");
this.setState({ this.setState({
data: Setting.deleteRow(this.state.data, i), data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1}, pagination: {total: this.state.pagination.total - 1},
}); });
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Webhook failed to delete: ${error}`); Setting.showMessage("error", `Webhook failed to delete: ${error}`);
@@ -69,40 +69,40 @@ class WebhookListPage extends BaseListPage {
const columns = [ const columns = [
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: 'organization', dataIndex: "organization",
key: 'organization', key: "organization",
width: '110px', width: "110px",
sorter: true, sorter: true,
...this.getColumnSearchProps('organization'), ...this.getColumnSearchProps("organization"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/organizations/${text}`}> <Link to={`/organizations/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '150px', width: "150px",
fixed: 'left', fixed: "left",
sorter: true, sorter: true,
...this.getColumnSearchProps('name'), ...this.getColumnSearchProps("name"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/webhooks/${text}`}> <Link to={`/webhooks/${text}`}>
{text} {text}
</Link> </Link>
) );
} }
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '180px', width: "180px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
@@ -110,11 +110,11 @@ class WebhookListPage extends BaseListPage {
}, },
{ {
title: i18next.t("webhook:URL"), title: i18next.t("webhook:URL"),
dataIndex: 'url', dataIndex: "url",
key: 'url', key: "url",
width: '300px', width: "300px",
sorter: true, sorter: true,
...this.getColumnSearchProps('url'), ...this.getColumnSearchProps("url"),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
@@ -122,82 +122,82 @@ class WebhookListPage extends BaseListPage {
Setting.getShortText(text) Setting.getShortText(text)
} }
</a> </a>
) );
} }
}, },
{ {
title: i18next.t("webhook:Method"), title: i18next.t("webhook:Method"),
dataIndex: 'method', dataIndex: "method",
key: 'method', key: "method",
width: '120px', width: "120px",
sorter: true, sorter: true,
...this.getColumnSearchProps('method'), ...this.getColumnSearchProps("method"),
}, },
{ {
title: i18next.t("webhook:Content type"), title: i18next.t("webhook:Content type"),
dataIndex: 'contentType', dataIndex: "contentType",
key: 'contentType', key: "contentType",
width: '200px', width: "200px",
sorter: true, sorter: true,
filterMultiple: false, filterMultiple: false,
filters: [ filters: [
{text: 'application/json', value: 'application/json'}, {text: "application/json", value: "application/json"},
{text: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded'}, {text: "application/x-www-form-urlencoded", value: "application/x-www-form-urlencoded"},
] ]
}, },
{ {
title: i18next.t("webhook:Events"), title: i18next.t("webhook:Events"),
dataIndex: 'events', dataIndex: "events",
key: 'events', key: "events",
// width: '100px', // width: '100px',
sorter: true, sorter: true,
...this.getColumnSearchProps('events'), ...this.getColumnSearchProps("events"),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} }
}, },
{ {
title: i18next.t("webhook:Is user extended"), title: i18next.t("webhook:Is user extended"),
dataIndex: 'isUserExtended', dataIndex: "isUserExtended",
key: 'isUserExtended', key: "isUserExtended",
width: '160px', width: "160px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Is enabled"), title: i18next.t("general:Is enabled"),
dataIndex: 'isEnabled', dataIndex: "isEnabled",
key: 'isEnabled', key: "isEnabled",
width: '120px', width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} /> <Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
) );
} }
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: '', dataIndex: "",
key: 'op', key: "op",
width: '170px', width: "170px",
fixed: (Setting.isMobile()) ? "false" : "right", fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete webhook: ${record.name} ?`} title={`Sure to delete webhook: ${record.name} ?`}
onConfirm={() => this.deleteWebhook(index)} onConfirm={() => this.deleteWebhook(index)}
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} }
}, },
]; ];
@@ -211,15 +211,15 @@ class WebhookListPage extends BaseListPage {
return ( return (
<div> <div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={webhooks} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={webhooks} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Webhooks")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Webhooks")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addWebhook.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addWebhook.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={this.state.loading} loading={this.state.loading}
onChange={this.handleTableChange} onChange={this.handleTableChange}
/> />
</div> </div>
); );
@@ -232,7 +232,7 @@ class WebhookListPage extends BaseListPage {
field = "contentType"; field = "contentType";
value = params.contentType; value = params.contentType;
} }
this.setState({ loading: true }); this.setState({loading: true});
WebhookBackend.getWebhooks("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) WebhookBackend.getWebhooks("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {

View File

@@ -19,7 +19,7 @@ class AccountPage extends React.Component {
render() { render() {
return ( return (
<UserEditPage organizationName={this.props.account.owner} userName={this.props.account.name} account={this.props.account} location={this.props.location} /> <UserEditPage organizationName={this.props.account.owner} userName={this.props.account.name} account={this.props.account} location={this.props.location} />
) );
} }
} }

View File

@@ -15,16 +15,16 @@
import {createButton} from "react-social-login-buttons"; import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting"; import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) { function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/adfs.svg`} alt="Sign in with ADFS" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/adfs.svg`} alt="Sign in with ADFS" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with ADFS", text: "Sign in with ADFS",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"}, style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"}, activeStyle: {background: "#ededee"},
}; };
const AdfsLoginButton = createButton(config); const AdfsLoginButton = createButton(config);

Some files were not shown because too many files have changed in this diff Show More