Compare commits

...

13 Commits

Author SHA1 Message Date
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
leoshine
b9404f14dc feat: fix bug of using email provider from wrong application (#869) 2022-07-10 00:40:52 +08:00
Bingchang Chen
0baae87390 feat: fix oauth unknown authority in docker (#871) 2022-07-09 17:36:56 +08:00
Gucheng Wang
06759041a8 Fix socks5Proxy config typo. 2022-07-08 23:24:54 +08:00
Bingchang Chen
cf4e76f9dc feat: add footer to door pages (#868) 2022-07-08 20:36:49 +08:00
Товарищ программист
81f2d01dc1 fix: fix dockerfile (#866) 2022-07-07 16:10:15 +08:00
Bingchang Chen
61773d3173 fix: support user-defined clientId&Secret (#862) 2022-07-06 19:27:59 +08:00
154 changed files with 17864 additions and 16882 deletions

View File

@@ -14,13 +14,16 @@ RUN ./build.sh
FROM alpine:latest AS STANDARD FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
WORKDIR /app RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl
RUN apk add ca-certificates && update-ca-certificates
WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=FRONT /web/build ./web/build COPY --from=FRONT /web/build ./web/build
VOLUME /app/files /app/logs ENTRYPOINT ["/server"]
ENTRYPOINT ["/app/server"]
FROM debian:latest AS db FROM debian:latest AS db
@@ -34,9 +37,10 @@ RUN apt update \
FROM db AS ALLINONE FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
ENV MYSQL_ROOT_PASSWORD=123456 RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates
WORKDIR /app WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh

View File

@@ -105,6 +105,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)/
@@ -12,7 +11,7 @@ redisEndpoint =
defaultStorageProvider = defaultStorageProvider =
isCloudIntranet = false isCloudIntranet = false
authState = "casdoor" authState = "casdoor"
sock5Proxy = "127.0.0.1:10808" socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10 verificationCodeTimeout = 10
initScore = 2000 initScore = 2000
logPostOnly = true logPostOnly = true

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

@@ -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.CheckPermission(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

@@ -42,14 +42,14 @@ func (c *ApiController) getCurrentUser() *object.User {
func (c *ApiController) SendVerificationCode() { func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type") destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest") dest := c.Ctx.Request.Form.Get("dest")
orgId := c.Ctx.Request.Form.Get("organizationId")
checkType := c.Ctx.Request.Form.Get("checkType") checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId") checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey") checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser") checkUser := c.Ctx.Request.Form.Get("checkUser")
applicationId := c.Ctx.Request.Form.Get("applicationId")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 { if destType == "" || dest == "" || applicationId == "" || !strings.Contains(applicationId, "/") || checkType == "" {
c.ResponseError("Missing parameter.") c.ResponseError("Missing parameter.")
return return
} }
@@ -74,8 +74,8 @@ func (c *ApiController) SendVerificationCode() {
} }
user := c.getCurrentUser() user := c.getCurrentUser()
organization := object.GetOrganization(orgId) application := object.GetApplication(applicationId)
application := object.GetApplicationByOrganizationName(organization.Name) organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil { if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("Please login first") c.ResponseError("Please login first")
@@ -85,7 +85,7 @@ func (c *ApiController) SendVerificationCode() {
sendResp := errors.New("Invalid dest type") sendResp := errors.New("Invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" { if user == nil && checkUser != "" && checkUser != "true" {
_, name := util.GetOwnerAndNameFromId(orgId) name := application.Organization
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser)) user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
} }
switch destType { switch destType {
@@ -108,13 +108,12 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError("Invalid phone number") c.ResponseError("Invalid phone number")
return return
} }
org := object.GetOrganization(orgId) if organization == nil {
if org == nil { c.ResponseError("The organization doesn't exist.")
c.ResponseError("Missing parameter.")
return return
} }
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest) dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest)
provider := application.GetSmsProvider() provider := application.GetSmsProvider()
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest) sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
} }

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)
}

View File

@@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ;fi
service mariadb start service mariadb start
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
exec /app/server --createDatabase=true exec /server --createDatabase=true

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

@@ -23,6 +23,8 @@
"cert": "", "cert": "",
"enablePassword": true, "enablePassword": true,
"enableSignUp": true, "enableSignUp": true,
"clientId": "",
"clientSecret": "",
"providers": [ "providers": [
{ {
"name": "", "name": "",

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

@@ -16,7 +16,7 @@ data:
defaultStorageProvider = defaultStorageProvider =
isCloudIntranet = false isCloudIntranet = false
authState = "casdoor" authState = "casdoor"
sock5Proxy = "127.0.0.1:10808" socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10 verificationCodeTimeout = 10
initScore = 2000 initScore = 2000
logPostOnly = true logPostOnly = true

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"`
@@ -280,8 +281,12 @@ func UpdateApplication(id string, application *Application) bool {
} }
func AddApplication(application *Application) bool { func AddApplication(application *Application) bool {
application.ClientId = util.GenerateClientId() if application.ClientId == "" {
application.ClientSecret = util.GenerateClientSecret() application.ClientId = util.GenerateClientId()
}
if application.ClientSecret == "" {
application.ClientSecret = util.GenerateClientSecret()
}
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
providerItem.Provider = nil providerItem.Provider = nil
} }

View File

@@ -229,4 +229,21 @@ 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 CheckPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
allow := true
var err error
for _, permission := range permissions {
if permission.IsEnabled {
for _, resource := range permission.Resources {
if resource == application.Name {
enforcer := getEnforcer(permission)
allow, err = enforcer.Enforce(userId, application.Name, "read")
}
}
}
}
return allow, err
} }

View File

@@ -15,9 +15,11 @@
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() {
@@ -29,6 +31,8 @@ func InitDb() {
initBuiltInCert() initBuiltInCert()
initBuiltInLdap() initBuiltInLdap()
} }
initWebAuthn()
} }
func initBuiltInOrganization() bool { func initBuiltInOrganization() bool {
@@ -72,6 +76,7 @@ func initBuiltInOrganization() bool {
{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)
@@ -221,3 +226,7 @@ func initBuiltInProvider() {
} }
AddProvider(provider) AddProvider(provider)
} }
func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}

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,85 @@ 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)
}
}

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" {

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"
) )
@@ -99,6 +100,8 @@ type User struct {
Douyin string `xorm:"douyin vachar(100)" json:"douyin"` Douyin string `xorm:"douyin vachar(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"`
} }
@@ -328,7 +331,7 @@ 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")

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

@@ -54,17 +54,17 @@ func isAddressOpen(address string) bool {
} }
func getProxyHttpClient() *http.Client { func getProxyHttpClient() *http.Client {
sock5Proxy := conf.GetConfigString("sock5Proxy") socks5Proxy := conf.GetConfigString("socks5Proxy")
if sock5Proxy == "" { if socks5Proxy == "" {
return &http.Client{} return &http.Client{}
} }
if !isAddressOpen(sock5Proxy) { if !isAddressOpen(socks5Proxy) {
return &http.Client{} return &http.Client{}
} }
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client // https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
dialer, err := proxy.SOCKS5("tcp", sock5Proxy, nil, proxy.Direct) dialer, err := proxy.SOCKS5("tcp", socks5Proxy, nil, proxy.Direct)
if err != nil { if err != nil {
panic(err) panic(err)
} }

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
} }

View File

@@ -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")
} }

2
web/.eslintignore Normal file
View File

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

59
web/.eslintrc Normal file
View File

@@ -0,0 +1,59 @@
{
"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",
"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 {DownOutlined, DeleteOutlined, 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")},
@@ -92,6 +92,7 @@ class AccountTable extends React.Component {
{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 +104,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 +169,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 +214,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 +228,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 +236,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, Dropdown, Layout, Menu, Card, Result, Button} 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,12 +234,12 @@ class App extends Component {
AuthBackend.logout() AuthBackend.logout()
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === "ok") {
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);
@@ -259,9 +259,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 +269,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 +287,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 +300,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 +314,7 @@ class App extends Component {
&nbsp; &nbsp;
</div> </div>
</Dropdown> </Dropdown>
) );
} }
renderAccount() { renderAccount() {
@@ -484,7 +486,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 +495,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 +610,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 +642,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() {
@@ -663,25 +664,30 @@ class App extends Component {
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<Switch> <div>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/> <Switch>
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage 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/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/login" 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="/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="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} 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="/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/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} /> <Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/callback" component={AuthCallback}/> <Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
<Route exact path="/callback/saml" component={SamlCallback}/> <Route exact path="/callback" component={AuthCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/> <Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)}/> <Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)}/> <Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...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" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")} <Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}/>} /> <Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
</Switch> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
) </Switch>
{
this.renderFooter()
}
</div>
);
} }
return ( return (
@@ -711,7 +717,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;
@@ -725,7 +731,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,121 @@ 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: "",
}; };
} }
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,104 +70,104 @@ 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:Public key"), i18next.t("cert:Public 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.publicKey); copy(this.state.cert.publicKey);
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
}} }}
@@ -182,15 +182,15 @@ class CertEditPage extends React.Component {
{i18next.t("cert:Download public key")} {i18next.t("cert:Download public key")}
</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.publicKey} onChange={e => {
this.updateCertField('publicKey', e.target.value); this.updateCertField("publicKey", 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";
@@ -36,15 +36,15 @@ class CertListPage extends BaseListPage {
expireInYears: 20, expireInYears: 20,
publicKey: "", publicKey: "",
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, Row, Col, Modal} 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

@@ -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, 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";
@@ -64,15 +64,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 +82,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 +98,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 +124,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>
) );
} }
}, },
]; ];
@@ -237,15 +237,15 @@ class OrganizationListPage extends BaseListPage {
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 +258,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") {

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, Modal, Row, Input} 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,17 +129,17 @@ 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);
@@ -138,33 +150,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 +184,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 +290,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 +313,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 +326,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";
@@ -36,15 +36,15 @@ class PermissionListPage extends BaseListPage {
action: "Read", action: "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 public key"))} :
</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 {DownOutlined, DeleteOutlined, 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, Modal, Row, Input} 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";
@@ -25,7 +25,7 @@ export const ResetModal = (props) => {
const [confirmLoading, setConfirmLoading] = React.useState(false); const [confirmLoading, setConfirmLoading] = React.useState(false);
const [dest, setDest] = React.useState(""); const [dest, setDest] = React.useState("");
const [code, setCode] = React.useState(""); const [code, setCode] = React.useState("");
const {buttonText, destType, org} = props; const {buttonText, destType, application} = props;
const showModal = () => { const showModal = () => {
setVisible(true); setVisible(true);
@@ -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>
@@ -89,13 +88,13 @@ export const ResetModal = (props) => {
<CountDownInput <CountDownInput
textBefore={i18next.t("code:Code You Received")} textBefore={i18next.t("code:Code You Received")}
onChange={setCode} onChange={setCode}
onButtonClickArgs={[dest, destType, `${org?.owner}/${org?.name}`]} onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]}
/> />
</Row> </Row>
</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 {Menu, Dropdown} 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

@@ -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"}} />
); );
} }
} }
@@ -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)}>
@@ -817,6 +813,10 @@ export function getApplicationOrgName(application) {
return `${application?.organizationObj.owner}/${application?.organizationObj.name}`; return `${application?.organizationObj.owner}/${application?.organizationObj.name}`;
} }
export function getApplicationName(application) {
return `${application?.owner}/${application?.name}`;
}
export function getRandomName() { export function getRandomName() {
return Math.random().toString(36).slice(-6); return Math.random().toString(36).slice(-6);
} }
@@ -844,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 {DownOutlined, DeleteOutlined, 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 {DownOutlined, DeleteOutlined, 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 {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} 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 org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} 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 org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null} {this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} 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,113 @@ 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 === "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 +440,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 +451,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 +538,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 +550,11 @@ class UserEditPage extends React.Component {
this.renderAccountItem(accountItem) this.renderAccountItem(accountItem)
} }
</React.Fragment> </React.Fragment>
) );
}) })
} }
</Card> </Card>
) );
} }
submitUserEdit(willExist) { submitUserEdit(willExist) {
@@ -549,7 +562,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 +570,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 +589,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 +599,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 {DownOutlined, DeleteOutlined, 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);

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/alipay.svg`} alt="Sign in with Alipay" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/alipay.svg`} alt="Sign in with Alipay" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Alipay", text: "Sign in with Alipay",
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 AlipayLoginButton = createButton(config); const AlipayLoginButton = createButton(config);

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/apple.svg`} alt="Sign in with Apple" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/apple.svg`} alt="Sign in with Apple" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Apple", text: "Sign in with Apple",
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 AppleLoginButton = createButton(config); const AppleLoginButton = createButton(config);

View File

@@ -16,14 +16,14 @@ import {authConfig} from "./Auth";
export function getAccount(query) { export function getAccount(query) {
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, { return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
method: 'GET', method: "GET",
credentials: 'include' credentials: "include"
}).then(res => res.json()); }).then(res => res.json());
} }
export function signup(values) { export function signup(values) {
return fetch(`${authConfig.serverUrl}/api/signup`, { return fetch(`${authConfig.serverUrl}/api/signup`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),
}).then(res => res.json()); }).then(res => res.json());
@@ -49,14 +49,14 @@ function oAuthParamsToQuery(oAuthParams) {
export function getApplicationLogin(oAuthParams) { export function getApplicationLogin(oAuthParams) {
return fetch(`${authConfig.serverUrl}/api/get-app-login${oAuthParamsToQuery(oAuthParams)}`, { return fetch(`${authConfig.serverUrl}/api/get-app-login${oAuthParamsToQuery(oAuthParams)}`, {
method: 'GET', method: "GET",
credentials: 'include', credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function login(values, oAuthParams) { export function login(values, oAuthParams) {
return fetch(`${authConfig.serverUrl}/api/login${oAuthParamsToQuery(oAuthParams)}`, { return fetch(`${authConfig.serverUrl}/api/login${oAuthParamsToQuery(oAuthParams)}`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),
}).then(res => res.json()); }).then(res => res.json());
@@ -64,7 +64,7 @@ export function login(values, oAuthParams) {
export function loginCas(values, params) { export function loginCas(values, params) {
return fetch(`${authConfig.serverUrl}/api/login?service=${params.service}`, { return fetch(`${authConfig.serverUrl}/api/login?service=${params.service}`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),
}).then(res => res.json()); }).then(res => res.json());
@@ -72,14 +72,14 @@ export function loginCas(values, params) {
export function logout() { export function logout() {
return fetch(`${authConfig.serverUrl}/api/logout`, { return fetch(`${authConfig.serverUrl}/api/logout`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function unlink(values) { export function unlink(values) {
return fetch(`${authConfig.serverUrl}/api/unlink`, { return fetch(`${authConfig.serverUrl}/api/unlink`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),
}).then(res => res.json()); }).then(res => res.json());
@@ -87,15 +87,15 @@ export function unlink(values) {
export function getSamlLogin(providerId, relayState) { export function getSamlLogin(providerId, relayState) {
return fetch(`${authConfig.serverUrl}/api/get-saml-login?id=${providerId}&relayState=${relayState}`, { return fetch(`${authConfig.serverUrl}/api/get-saml-login?id=${providerId}&relayState=${relayState}`, {
method: 'GET', method: "GET",
credentials: 'include', credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function loginWithSaml(values, param) { export function loginWithSaml(values, param) {
return fetch(`${authConfig.serverUrl}/api/login${param}`, { return fetch(`${authConfig.serverUrl}/api/login${param}`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
body: JSON.stringify(values), body: JSON.stringify(values),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@@ -51,7 +51,7 @@ class AuthCallback extends React.Component {
if (realRedirectUri === null) { if (realRedirectUri === null) {
const samlRequest = innerParams.get("SAMLRequest"); const samlRequest = innerParams.get("SAMLRequest");
if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") { if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") {
return "saml" return "saml";
} }
return "login"; return "login";
} }
@@ -64,7 +64,7 @@ class AuthCallback extends React.Component {
} else { } else {
const responseType = innerParams.get("response_type"); const responseType = innerParams.get("response_type");
if (responseType !== null) { if (responseType !== null) {
return responseType return responseType;
} }
return "code"; return "code";
} }
@@ -77,7 +77,7 @@ class AuthCallback extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
const params = new URLSearchParams(this.props.location.search); const params = new URLSearchParams(this.props.location.search);
let isSteam = params.get("openid.mode") let isSteam = params.get("openid.mode");
let code = params.get("code"); let code = params.get("code");
// WeCom returns "auth_code=xxx" instead of "code=xxx" // WeCom returns "auth_code=xxx" instead of "code=xxx"
if (code === null) { if (code === null) {
@@ -85,11 +85,11 @@ class AuthCallback extends React.Component {
} }
// Dingtalk now returns "authCode=xxx" instead of "code=xxx" // Dingtalk now returns "authCode=xxx" instead of "code=xxx"
if (code === null) { if (code === null) {
code = params.get("authCode") code = params.get("authCode");
} }
//Steam don't use code, so we should use all params as code. // Steam don't use code, so we should use all params as code.
if (isSteam !== null && code === null) { if (isSteam !== null && code === null) {
code = this.props.location.search code = this.props.location.search;
} }
const innerParams = this.getInnerParams(); const innerParams = this.getInnerParams();
@@ -112,13 +112,13 @@ class AuthCallback extends React.Component {
method: method, method: method,
}; };
const oAuthParams = Util.getOAuthGetParameters(innerParams); const oAuthParams = Util.getOAuthGetParameters(innerParams);
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?'; const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
AuthBackend.login(body, oAuthParams) AuthBackend.login(body, oAuthParams)
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === "ok") {
const responseType = this.getResponseType(); const responseType = this.getResponseType();
if (responseType === "login") { if (responseType === "login") {
Util.showMessage("success", `Logged in successfully`); Util.showMessage("success", "Logged in successfully");
// Setting.goToLinkSoft(this, "/"); // Setting.goToLinkSoft(this, "/");
const link = Setting.getFromLink(); const link = Setting.getFromLink();
@@ -127,7 +127,7 @@ class AuthCallback extends React.Component {
const code = res.data; const code = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`); Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
// Util.showMessage("success", `Authorization code: ${res.data}`); // Util.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token"){ } else if (responseType === "token" || responseType === "id_token") {
const token = res.data; const token = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`); Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "link") { } else if (responseType === "link") {
@@ -157,7 +157,7 @@ class AuthCallback extends React.Component {
) )
} }
</div> </div>
) );
} }
} }

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/azuread.svg`} alt="Sign in with AzureAD" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/azuread.svg`} alt="Sign in with AzureAD" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with AzureAD", text: "Sign in with AzureAD",
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 AzureADLoginButton = createButton(config); const AzureADLoginButton = createButton(config);

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/baidu.svg`} alt="Sign in with Baidu" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/baidu.svg`} alt="Sign in with Baidu" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Baidu", text: "Sign in with Baidu",
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 BaiduLoginButton = createButton(config); const BaiduLoginButton = createButton(config);

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/bilibili.svg`} alt="Sign in with Bilibili"/>; return <img src={`${StaticBaseUrl}/buttons/bilibili.svg`} alt="Sign in with Bilibili" />;
} }
const config = { const config = {
text: "Sign in with Bilibili", text: "Sign in with Bilibili",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"}, style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"}, activeStyle: {background: "rgb(76,143,208)"},
}; };
const BilibiliLoginButton = createButton(config); const BilibiliLoginButton = createButton(config);

View File

@@ -27,8 +27,8 @@ class CasLogout extends React.Component {
msg: null, msg: null,
}; };
if (props.match?.params.casApplicationName !== undefined) { if (props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner this.state.owner = props.match?.params.owner;
this.state.applicationName = props.match?.params.casApplicationName this.state.applicationName = props.match?.params.casApplicationName;
} }
} }
@@ -37,14 +37,14 @@ class CasLogout extends React.Component {
AuthBackend.logout() AuthBackend.logout()
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === "ok") {
Setting.showMessage("success", `Logged out successfully`); Setting.showMessage("success", "Logged out successfully");
this.props.clearAccount() this.props.clearAccount();
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 if (params.has("service")) { } else if (params.has("service")) {
Setting.goToLink(params.get("service")) Setting.goToLink(params.get("service"));
} else { } else {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`); Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
} }
@@ -62,7 +62,7 @@ class CasLogout extends React.Component {
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
} }
</div> </div>
) );
} }
} }
export default withRouter(CasLogout); export default withRouter(CasLogout);

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/casdoor.svg`} alt="Sign in with Casdoor" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/casdoor.svg`} alt="Sign in with Casdoor" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Casdoor", text: "Sign in with Casdoor",
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 CasdoorLoginButton = createButton(config); const CasdoorLoginButton = createButton(config);

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/dingtalk.svg`} alt="Sign in with DingTalk"/>; return <img src={`${StaticBaseUrl}/buttons/dingtalk.svg`} alt="Sign in with DingTalk" />;
} }
const config = { const config = {
text: "Sign in with DingTalk", text: "Sign in with DingTalk",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"}, style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"}, activeStyle: {background: "rgb(76,143,208)"},
}; };
const DingTalkLoginButton = createButton(config); const DingTalkLoginButton = createButton(config);

View File

@@ -16,15 +16,15 @@ 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/douyin.svg`} alt="Sign in with Douyin" style={{width: 24, height: 24}}/>; return <img src={`${StaticBaseUrl}/buttons/douyin.svg`} alt="Sign in with Douyin" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Douyin", text: "Sign in with Douyin",
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 DouyinLoginButton = createButton(config); const DouyinLoginButton = createButton(config);

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/facebook.svg`} alt="Sign in with Facebook"/>; return <img src={`${StaticBaseUrl}/buttons/facebook.svg`} alt="Sign in with Facebook" />;
} }
const config = { const config = {
text: "Sign in with Facebook", text: "Sign in with Facebook",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "#3b5998"}, style: {background: "#3b5998"},
activeStyle: {background: "#2b3f65"}, activeStyle: {background: "#2b3f65"},
}; };
const FacebookLoginButton = createButton(config); const FacebookLoginButton = createButton(config);

View File

@@ -24,8 +24,8 @@ import * as UserBackend from "../backend/UserBackend";
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons"; import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
const { Step } = Steps; const {Step} = Steps;
const { Option } = Select; const {Option} = Select;
class ForgetPage extends React.Component { class ForgetPage extends React.Component {
constructor(props) { constructor(props) {
@@ -35,8 +35,8 @@ class ForgetPage extends React.Component {
account: props.account, account: props.account,
applicationName: applicationName:
props.applicationName !== undefined props.applicationName !== undefined
? props.applicationName ? props.applicationName
: props.match === undefined : props.match === undefined
? null ? null
: props.match.params.applicationName, : props.match.params.applicationName,
application: null, application: null,
@@ -61,8 +61,8 @@ class ForgetPage extends React.Component {
this.getApplication(); this.getApplication();
} else { } else {
Util.showMessage( Util.showMessage(
"error", "error",
i18next.t(`forget:Unknown forgot type: `) + this.state.type i18next.t("forget:Unknown forgot type: ") + this.state.type
); );
} }
} }
@@ -73,11 +73,11 @@ class ForgetPage extends React.Component {
} }
ApplicationBackend.getApplication("admin", this.state.applicationName).then( ApplicationBackend.getApplication("admin", this.state.applicationName).then(
(application) => { (application) => {
this.setState({ this.setState({
application: application, application: application,
}); });
} }
); );
} }
@@ -90,87 +90,87 @@ class ForgetPage extends React.Component {
} }
onFormFinish(name, info, forms) { onFormFinish(name, info, forms) {
switch (name) { switch (name) {
case "step1": case "step1":
const username = forms.step1.getFieldValue("username") const username = forms.step1.getFieldValue("username");
AuthBackend.getEmailAndPhone({ AuthBackend.getEmailAndPhone({
application: forms.step1.getFieldValue("application"), application: forms.step1.getFieldValue("application"),
organization: forms.step1.getFieldValue("organization"), organization: forms.step1.getFieldValue("organization"),
username: username username: username
}).then((res) => { }).then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
const phone = res.data.phone; const phone = res.data.phone;
const email = res.data.email; const email = res.data.email;
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name}); this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
if (phone !== "" && email === "") { if (phone !== "" && email === "") {
this.setState({ this.setState({
verifyType: "phone", verifyType: "phone",
}); });
} else if (phone === "" && email !== "") { } else if (phone === "" && email !== "") {
this.setState({ this.setState({
verifyType: "email", verifyType: "email",
}); });
} }
switch (res.data2) { switch (res.data2) {
case "email": case "email":
this.setState({isFixed: true, fixedContent: email, verifyType: "email"}); this.setState({isFixed: true, fixedContent: email, verifyType: "email"});
break break;
case "phone": case "phone":
this.setState({isFixed: true, fixedContent: phone, verifyType: "phone"}); this.setState({isFixed: true, fixedContent: phone, verifyType: "phone"});
break break;
default:
break
}
if (this.state.isFixed) {
forms.step2.setFieldsValue({email: this.state.fixedContent})
this.setState({username: this.state.fixedContent})
}
this.setState({current: 1})
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
});
break;
case "step2":
const oAuthParams = Util.getOAuthGetParameters();
if (this.state.verifyType === "email") {
this.setState({username: this.state.email})
} else if (this.state.verifyType === "phone") {
this.setState({username: this.state.phone})
}
AuthBackend.login({
application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"),
username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login"
}, oAuthParams).then(res => {
if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]})
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
})
break
default: default:
break break;
}
if (this.state.isFixed) {
forms.step2.setFieldsValue({email: this.state.fixedContent});
this.setState({username: this.state.fixedContent});
}
this.setState({current: 1});
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
});
break;
case "step2":
const oAuthParams = Util.getOAuthGetParameters();
if (this.state.verifyType === "email") {
this.setState({username: this.state.email});
} else if (this.state.verifyType === "phone") {
this.setState({username: this.state.phone});
} }
AuthBackend.login({
application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"),
username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login"
}, oAuthParams).then(res => {
if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
});
break;
default:
break;
}
} }
onFinish(values) { onFinish(values) {
values.username = this.state.username; values.username = this.state.username;
values.userOwner = this.state.application?.organizationObj.name values.userOwner = this.state.application?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.goToLogin(this, this.state.application); Setting.goToLogin(this, this.state.application);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }
}) });
} }
onFinishFailed(values, errorFields) {} onFinishFailed(values, errorFields) {}
@@ -199,283 +199,283 @@ class ForgetPage extends React.Component {
renderForm(application) { renderForm(application) {
return ( return (
<Form.Provider onFormFinish={(name, {info, forms}) => { <Form.Provider onFormFinish={(name, {info, forms}) => {
this.onFormFinish(name, info, forms); this.onFormFinish(name, info, forms);
}}> }}>
{/* STEP 1: input username -> get email & phone */} {/* STEP 1: input username -> get email & phone */}
<Form <Form
hidden={this.state.current !== 0} hidden={this.state.current !== 0}
ref={this.form} ref={this.form}
name="step1" name="step1"
onFinishFailed={(errorInfo) => console.log(errorInfo)} onFinishFailed={(errorInfo) => console.log(errorInfo)}
initialValues={{ initialValues={{
application: application.name, application: application.name,
organization: application.organization, organization: application.organization,
}} }}
style={{ width: "300px" }} style={{width: "300px"}}
size="large" size="large"
>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="application"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
},
]}
/>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
},
]}
/>
<Form.Item
name="username"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your username!"
),
whitespace: true,
},
]}
> >
<Form.Item <Input
style={{ height: 0, visibility: "hidden" }} onChange={(e) => {
name="application" this.setState({
rules={[ username: e.target.value,
{ });
required: true, }}
message: i18next.t( prefix={<UserOutlined />}
`forget:Please input your application!` placeholder={i18next.t("login:username, Email or phone")}
),
},
]}
/> />
<Form.Item </Form.Item>
style={{ height: 0, visibility: "hidden" }} <br />
name="organization" <Form.Item>
rules={[ <Button block type="primary" htmlType="submit">
{ {i18next.t("forget:Next Step")}
required: true, </Button>
message: i18next.t( </Form.Item>
`forget:Please input your organization!` </Form>
),
}, {/* STEP 2: verify email or phone */}
]} <Form
/> hidden={this.state.current !== 1}
<Form.Item ref={this.form}
name="username" name="step2"
rules={[ onFinishFailed={(errorInfo) =>
{ this.onFinishFailed(
required: true, errorInfo.values,
message: i18next.t( errorInfo.errorFields,
"forget:Please input your username!" errorInfo.outOfDate
), )
whitespace: true, }
}, initialValues={{
]} application: application.name,
> organization: application.organization,
<Input }}
onChange={(e) => { style={{width: "300px"}}
size="large"
>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="application"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
},
]}
/>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
},
]}
/>
<Form.Item
name="email" // use email instead of email/phone to adapt to RequestForm in account.go
validateFirst
hasFeedback
>
{
this.state.isFixed ? <Input disabled /> :
<Select
key={this.state.verifyType}
virtual={false} style={{textAlign: "left"}}
defaultValue={this.state.verifyType}
disabled={this.state.username === ""}
placeholder={i18next.t("forget:Choose email or phone")}
onChange={(value) => {
this.setState({ this.setState({
username: e.target.value, verifyType: value,
}); });
}} }}
prefix={<UserOutlined />} >
placeholder={i18next.t("login:username, Email or phone")} {
/> this.renderOptions()
</Form.Item> }
<br /> </Select>
<Form.Item> }
<Button block type="primary" htmlType="submit"> </Form.Item>
{i18next.t("forget:Next Step")} <Form.Item
</Button> name="emailCode" // use emailCode instead of email/phoneCode to adapt to RequestForm in account.go
</Form.Item> rules={[
</Form> {
required: true,
{/* STEP 2: verify email or phone */} message: i18next.t(
<Form "code:Please input your verification code!"
hidden={this.state.current !== 1} ),
ref={this.form} },
name="step2" ]}
onFinishFailed={(errorInfo) =>
this.onFinishFailed(
errorInfo.values,
errorInfo.errorFields,
errorInfo.outOfDate
)
}
initialValues={{
application: application.name,
organization: application.organization,
}}
style={{ width: "300px" }}
size="large"
> >
<Form.Item {this.state.verifyType === "email" ? (
style={{ height: 0, visibility: "hidden" }} <CountDownInput
name="application" disabled={this.state.username === "" || this.state.verifyType === ""}
rules={[ onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]}
{ />
required: true, ) : (
message: i18next.t( <CountDownInput
`forget:Please input your application!` disabled={this.state.username === "" || this.state.verifyType === ""}
), onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]}
}, />
]} )}
/> </Form.Item>
<Form.Item <br />
style={{ height: 0, visibility: "hidden" }} <Form.Item>
name="organization" <Button
rules={[ block
{ type="primary"
required: true, htmlType="submit"
message: i18next.t(
`forget:Please input your organization!`
),
},
]}
/>
<Form.Item
name="email" //use email instead of email/phone to adapt to RequestForm in account.go
validateFirst
hasFeedback
> >
{ {i18next.t("forget:Next Step")}
this.state.isFixed ? <Input disabled/> : </Button>
<Select </Form.Item>
key={this.state.verifyType} </Form>
virtual={false} style={{textAlign: 'left'}}
defaultValue={this.state.verifyType}
disabled={this.state.username === ""}
placeholder={i18next.t("forget:Choose email or phone")}
onChange={(value) => {
this.setState({
verifyType: value,
});
}}
>
{
this.renderOptions()
}
</Select>
}
</Form.Item>
<Form.Item
name="emailCode" //use emailCode instead of email/phoneCode to adapt to RequestForm in account.go
rules={[
{
required: true,
message: i18next.t(
"code:Please input your verification code!"
),
},
]}
>
{this.state.verifyType === "email" ? (
<CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/>
) : (
<CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/>
)}
</Form.Item>
<br />
<Form.Item>
<Button
block
type="primary"
htmlType="submit"
>
{i18next.t("forget:Next Step")}
</Button>
</Form.Item>
</Form>
{/* STEP 3 */} {/* STEP 3 */}
<Form <Form
hidden={this.state.current !== 2} hidden={this.state.current !== 2}
ref={this.form} ref={this.form}
name="step3" name="step3"
onFinish={(values) => this.onFinish(values)} onFinish={(values) => this.onFinish(values)}
onFinishFailed={(errorInfo) => onFinishFailed={(errorInfo) =>
this.onFinishFailed( this.onFinishFailed(
errorInfo.values, errorInfo.values,
errorInfo.errorFields, errorInfo.errorFields,
errorInfo.outOfDate errorInfo.outOfDate
) )
} }
initialValues={{ initialValues={{
application: application.name, application: application.name,
organization: application.organization, organization: application.organization,
}} }}
style={{ width: "300px" }} style={{width: "300px"}}
size="large" size="large"
>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="application"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your application!"
),
},
]}
/>
<Form.Item
style={{height: 0, visibility: "hidden"}}
name="organization"
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your organization!"
),
},
]}
/>
<Form.Item
name="newPassword"
hidden={this.state.current !== 2}
rules={[
{
required: true,
message: i18next.t(
"forget:Please input your password!"
),
},
]}
hasFeedback
> >
<Form.Item <Input.Password
style={{ height: 0, visibility: "hidden" }} disabled={this.state.userId === ""}
name="application" prefix={<LockOutlined />}
rules={[ placeholder={i18next.t("forget:Password")}
{
required: true,
message: i18next.t(
`forget:Please input your application!`
),
},
]}
/> />
<Form.Item </Form.Item>
style={{ height: 0, visibility: "hidden" }} <Form.Item
name="organization" name="confirm"
rules={[ dependencies={["newPassword"]}
{ hasFeedback
required: true, rules={[
message: i18next.t( {
`forget:Please input your organization!` required: true,
), message: i18next.t(
}, "forget:Please confirm your password!"
]} ),
},
({getFieldValue}) => ({
validator(rule, value) {
if (!value || getFieldValue("newPassword") === value) {
return Promise.resolve();
}
return Promise.reject(
i18next.t(
"forget:Your confirmed password is inconsistent with the password!"
)
);
},
}),
]}
>
<Input.Password
disabled={this.state.userId === ""}
prefix={<CheckCircleOutlined />}
placeholder={i18next.t("forget:Confirm")}
/> />
<Form.Item </Form.Item>
name="newPassword" <br />
hidden={this.state.current !== 2} <Form.Item hidden={this.state.current !== 2}>
rules={[ <Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
{ {i18next.t("forget:Change Password")}
required: true, </Button>
message: i18next.t( </Form.Item>
"forget:Please input your password!" </Form>
), </Form.Provider>
},
]}
hasFeedback
>
<Input.Password
disabled={this.state.userId === ""}
prefix={<LockOutlined />}
placeholder={i18next.t("forget:Password")}
/>
</Form.Item>
<Form.Item
name="confirm"
dependencies={["newPassword"]}
hasFeedback
rules={[
{
required: true,
message: i18next.t(
"forget:Please confirm your password!"
),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("newPassword") === value) {
return Promise.resolve();
}
return Promise.reject(
i18next.t(
"forget:Your confirmed password is inconsistent with the password!"
)
);
},
}),
]}
>
<Input.Password
disabled={this.state.userId === ""}
prefix={<CheckCircleOutlined />}
placeholder={i18next.t("forget:Confirm")}
/>
</Form.Item>
<br />
<Form.Item hidden={this.state.current !== 2}>
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
{i18next.t("forget:Change Password")}
</Button>
</Form.Item>
</Form>
</Form.Provider>
); );
} }
render() { render() {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
@@ -533,8 +533,8 @@ class ForgetPage extends React.Component {
</Col> </Col>
</Row> </Row>
</Col> </Col>
<Col span={24} style={{ display: "flex", justifyContent: "center" }}> <Col span={24} style={{display: "flex", justifyContent: "center"}}>
<div style={{ marginTop: "10px", textAlign: "center" }}> <div style={{marginTop: "10px", textAlign: "center"}}>
{this.renderForm(application)} {this.renderForm(application)}
</div> </div>
</Col> </Col>

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/github.svg`} alt="Sign in with GitHub"/>; return <img src={`${StaticBaseUrl}/buttons/github.svg`} alt="Sign in with GitHub" />;
} }
const config = { const config = {
text: "Sign in with GitHub", text: "Sign in with GitHub",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "#333333"}, style: {background: "#333333"},
activeStyle: {background: "#393934"}, activeStyle: {background: "#393934"},
}; };
const GitHubLoginButton = createButton(config); const GitHubLoginButton = createButton(config);

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/gitlab.svg`} alt="Sign in with GitLab" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/gitlab.svg`} alt="Sign in with GitLab" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with GitLab", text: "Sign in with GitLab",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "rgb(255,255,255)", color: "#000000"}, style: {background: "rgb(255,255,255)", color: "#000000"},
activeStyle: {background: "rgb(100,150,250)"}, activeStyle: {background: "rgb(100,150,250)"},
}; };
const GitLabLoginButton = createButton(config); const GitLabLoginButton = createButton(config);

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/gitee.svg`} alt="Sign in with Gitee"/>; return <img src={`${StaticBaseUrl}/buttons/gitee.svg`} alt="Sign in with Gitee" />;
} }
const config = { const config = {
text: "Sign in with Gitee", text: "Sign in with Gitee",
icon: Icon, icon: Icon,
iconFormat: name => `fa fa-${name}`, iconFormat: name => `fa fa-${name}`,
style: {background: "rgb(199,29,35)"}, style: {background: "rgb(199,29,35)"},
activeStyle: {background: "rgb(147,22,26)"}, activeStyle: {background: "rgb(147,22,26)"},
}; };
const GiteeLoginButton = createButton(config); const GiteeLoginButton = createButton(config);

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/google.svg`} alt="Sign in with Google"/>; return <img src={`${StaticBaseUrl}/buttons/google.svg`} alt="Sign in with Google" />;
} }
const config = { const config = {
text: "Sign in with Google", text: "Sign in with Google",
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: "#eff0ee"}, activeStyle: {background: "#eff0ee"},
}; };
const GoogleLoginButton = createButton(config); const GoogleLoginButton = createButton(config);

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/infoflow.svg`} alt="Sign in with Infoflow" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/infoflow.svg`} alt="Sign in with Infoflow" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Infoflow", text: "Sign in with Infoflow",
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 InfoflowLoginButton = createButton(config); const InfoflowLoginButton = createButton(config);

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/lark.svg`} alt="Sign in with Lark" style={{width: 24, height: 24}} />; return <img src={`${StaticBaseUrl}/buttons/lark.svg`} alt="Sign in with Lark" style={{width: 24, height: 24}} />;
} }
const config = { const config = {
text: "Sign in with Lark", text: "Sign in with Lark",
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 LarkLoginButton = createButton(config); const LarkLoginButton = createButton(config);

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