Compare commits

..

20 Commits

Author SHA1 Message Date
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
de49a45e19 Add escapePath for getUploadFileUrl(). 2022-07-12 23:24:24 +08:00
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
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
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
475b6da35a Rename session storage item to signinUrl. 2022-07-10 11:50:48 +08:00
b9404f14dc feat: fix bug of using email provider from wrong application (#869) 2022-07-10 00:40:52 +08:00
0baae87390 feat: fix oauth unknown authority in docker (#871) 2022-07-09 17:36:56 +08:00
06759041a8 Fix socks5Proxy config typo. 2022-07-08 23:24:54 +08:00
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
61773d3173 fix: support user-defined clientId&Secret (#862) 2022-07-06 19:27:59 +08:00
ec29621547 feat: init from configuration file (#858)
* feat: init from configuration file

* Update init_data.json.template

* Update init_data.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-05 00:36:22 +08:00
b8e324cadf fix: azurad provider (#855) 2022-07-04 16:40:23 +08:00
f37fd6ba87 Fix empty arg bug in getPermanentAvatarUrl(). 2022-07-03 19:31:12 +08:00
b4bf734fe8 fix: fix cors filter (#847)
* fix: fix cors filter

* Update cors_filter.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-02 13:45:18 +08:00
f0431701c9 fix: fix OAuth error response (#835)
* fix: fix OAuth error response

* fix: provide more detailed error messages for TokenError
2022-07-01 14:53:34 +08:00
aa5078de15 fix: crowdin kept deleting translations (#843)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-01 10:51:40 +08:00
9a324b2cca fix: Update Crowdin link (#841) 2022-06-30 22:05:20 +08:00
163 changed files with 18486 additions and 17020 deletions

View File

@ -14,13 +14,16 @@ RUN ./build.sh
FROM alpine:latest AS STANDARD
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/swagger ./swagger
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=FRONT /web/build ./web/build
VOLUME /app/files /app/logs
ENTRYPOINT ["/app/server"]
ENTRYPOINT ["/server"]
FROM debian:latest AS db
@ -34,9 +37,10 @@ RUN apt update \
FROM db AS ALLINONE
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/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh

View File

@ -98,7 +98,7 @@ For casdoor, if you have any questions, you can give Issues, or you can also dir
### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.

View File

@ -105,6 +105,7 @@ p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /api/webauthn, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@ -1,7 +1,6 @@
appname = casdoor
httpport = 8000
runmode = dev
SessionOn = true
copyrequestbody = true
driverName = mysql
dataSourceName = root:123456@tcp(localhost:3306)/
@ -12,7 +11,7 @@ redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
sock5Proxy = "127.0.0.1:10808"
socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true

View File

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

View File

@ -50,6 +50,17 @@ func tokenToResponse(token *object.Token) *Response {
// HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
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 {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)

View File

@ -165,6 +165,8 @@ func (c *ApiController) GetOAuthCode() {
// @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
@ -200,6 +202,7 @@ func (c *ApiController) GetOAuthToken() {
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
@ -213,6 +216,8 @@ func (c *ApiController) GetOAuthToken() {
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string false "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type")
@ -235,6 +240,7 @@ func (c *ApiController) RefreshToken() {
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
@ -270,6 +276,8 @@ func (c *ApiController) TokenLogout() {
// @Param token formData string true "access_token's value or refresh_token's value"
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
// @Success 200 {object} object.IntrospectionResponse The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/introspect [post]
func (c *ApiController) IntrospectToken() {
tokenValue := c.Input().Get("token")
@ -279,12 +287,21 @@ func (c *ApiController) IntrospectToken() {
clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" {
c.ResponseError("empty clientId or clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_REQUEST,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
return
}
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_CLIENT,
}
c.SetTokenErrorHttpStatus()
return
}
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)

View File

@ -51,6 +51,23 @@ func (c *ApiController) ResponseError(error string, data ...interface{}) {
c.ServeJSON()
}
// SetTokenErrorHttpStatus ...
func (c *ApiController) SetTokenErrorHttpStatus() {
_, ok := c.Data["json"].(*object.TokenError)
if ok {
if c.Data["json"].(*object.TokenError).Error == object.INVALID_CLIENT {
c.Ctx.Output.SetStatus(401)
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
} else {
c.Ctx.Output.SetStatus(400)
}
}
_, ok = c.Data["json"].(*object.TokenWrapper)
if ok {
c.Ctx.Output.SetStatus(200)
}
}
// RequireSignedIn ...
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()

View File

@ -42,14 +42,14 @@ func (c *ApiController) getCurrentUser() *object.User {
func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
orgId := c.Ctx.Request.Form.Get("organizationId")
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser")
applicationId := c.Ctx.Request.Form.Get("applicationId")
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.")
return
}
@ -74,8 +74,8 @@ func (c *ApiController) SendVerificationCode() {
}
user := c.getCurrentUser()
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
application := object.GetApplication(applicationId)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("Please login first")
@ -85,7 +85,7 @@ func (c *ApiController) SendVerificationCode() {
sendResp := errors.New("Invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
_, name := util.GetOwnerAndNameFromId(orgId)
name := application.Organization
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
}
switch destType {
@ -108,13 +108,12 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError("Invalid phone number")
return
}
org := object.GetOrganization(orgId)
if org == nil {
c.ResponseError("Missing parameter.")
if organization == nil {
c.ResponseError("The organization doesn't exist.")
return
}
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest)
provider := application.GetSmsProvider()
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
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ;fi
service mariadb start
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
exec /app/server --createDatabase=true
exec /server --createDatabase=true

3
go.mod
View File

@ -11,9 +11,10 @@ require (
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.2.0
github.com/casdoor/goth v1.69.0-FIX1
github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0
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-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72

18
go.sum
View File

@ -100,8 +100,8 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
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/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=
@ -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/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
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/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
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/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
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/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=
@ -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/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/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/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
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/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/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.3.0/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/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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
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.27/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-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-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-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

160
init_data.json.template Normal file
View File

@ -0,0 +1,160 @@
{
"organizations": [
{
"owner": "",
"name": "",
"displayName": "",
"websiteUrl": "",
"favicon": "",
"passwordType": "",
"phonePrefix": "",
"defaultAvatar": "",
"tags": [""]
}
],
"applications": [
{
"owner": "",
"name": "",
"displayName": "",
"logo": "",
"homepageUrl": "",
"organization": "",
"cert": "",
"enablePassword": true,
"enableSignUp": true,
"clientId": "",
"clientSecret": "",
"providers": [
{
"name": "",
"canSignUp": true,
"canSignIn": true,
"canUnlink": false,
"prompted": false,
"alertType": "None"
}
],
"signupItems": [
{
"name": "ID",
"visible": false,
"required": true,
"prompted": false,
"rule": "Random"
},
{
"name": "Username",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Display name",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Confirm password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Email",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Phone",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Agreement",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
}
],
"redirectUris": [""],
"expireInHours": 168
}
],
"users": [
{
"owner": "",
"name": "",
"type": "normal-user",
"password": "",
"displayName": "",
"avatar": "",
"email": "",
"phone": "",
"address": [],
"affiliation": "",
"tag": "",
"score": 2000,
"ranking": 1,
"isAdmin": true,
"isGlobalAdmin": true,
"isForbidden": false,
"isDeleted": false,
"signupApplication": "",
"createdIp": ""
}
],
"providers": [
{
"owner": "",
"name": "",
"displayName": "",
"category": "",
"type": ""
}
],
"certs": [
{
"owner": "",
"name": "",
"displayName": "",
"scope": "JWT",
"type": "x509",
"cryptoAlgorithm": "RS256",
"bitSize": 4096,
"expireInYears": 20,
"publicKey": "",
"privateKey": ""
}
],
"ldaps": [
{
"id": "",
"owner": "",
"serverName": "",
"host": "",
"port": 389,
"admin": "",
"passwd": "",
"baseDn": "",
"autoSync": 0,
"lastSync": ""
}
]
}

View File

@ -36,6 +36,7 @@ func main() {
object.InitAdapter(*createDatabase)
object.InitDb()
object.InitFromFile()
object.InitDefaultStorageProvider()
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
@ -55,6 +56,7 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionOn = true
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if conf.GetConfigString("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

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

View File

@ -22,7 +22,7 @@ import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
//_ "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
"xorm.io/core"
"xorm.io/xorm"
@ -36,11 +36,12 @@ func InitConfig() {
panic(err)
}
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true)
}
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase {
adapter.CreateDatabase()
@ -202,6 +203,11 @@ func (a *Adapter) createTable() {
if err != nil {
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 {

View File

@ -47,6 +47,7 @@ type Application struct {
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableWebAuthn bool `json:"enableWebAuthn"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
@ -280,8 +281,12 @@ func UpdateApplication(id string, application *Application) bool {
}
func AddApplication(application *Application) bool {
application.ClientId = util.GenerateClientId()
application.ClientSecret = util.GenerateClientSecret()
if application.ClientId == "" {
application.ClientId = util.GenerateClientId()
}
if application.ClientSecret == "" {
application.ClientSecret = util.GenerateClientSecret()
}
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}

View File

@ -51,6 +51,10 @@ func downloadFile(url string) (*bytes.Buffer, error) {
}
func getPermanentAvatarUrl(organization string, username string, url string) string {
if url == "" {
return ""
}
if defaultStorageProvider == nil {
return ""
}

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")
}
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
import (
"encoding/gob"
"io/ioutil"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
)
func InitDb() {
@ -29,6 +31,8 @@ func InitDb() {
initBuiltInCert()
initBuiltInLdap()
}
initWebAuthn()
}
func initBuiltInOrganization() bool {
@ -72,6 +76,7 @@ func initBuiltInOrganization() bool {
{Name: "Is global admin", 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: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
},
}
AddOrganization(organization)
@ -221,3 +226,7 @@ func initBuiltInProvider() {
}
AddProvider(provider)
}
func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}

146
object/init_data.go Normal file
View File

@ -0,0 +1,146 @@
// 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 object
import "github.com/casdoor/casdoor/util"
type InitData struct {
Organizations []*Organization `json:"organizations"`
Applications []*Application `json:"applications"`
Users []*User `json:"users"`
Certs []*Cert `json:"certs"`
Providers []*Provider `json:"providers"`
Ldaps []*Ldap `json:"ldaps"`
}
func InitFromFile() {
initData := readInitDataFromFile("./init_data.json")
if initData != nil {
for _, organization := range initData.Organizations {
initDefinedOrganization(organization)
}
for _, provider := range initData.Providers {
initDefinedProvider(provider)
}
for _, user := range initData.Users {
initDefinedUser(user)
}
for _, application := range initData.Applications {
initDefinedApplication(application)
}
for _, cert := range initData.Certs {
initDefinedCert(cert)
}
for _, ldap := range initData.Ldaps {
initDefinedLdap(ldap)
}
}
}
func readInitDataFromFile(filePath string) *InitData {
if !util.FileExist(filePath) {
return nil
}
s := util.ReadStringFromPath(filePath)
data := &InitData{}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
return data
}
func initDefinedOrganization(organization *Organization) {
existed := getOrganization(organization.Owner, organization.Name)
if existed != nil {
return
}
organization.CreatedTime = util.GetCurrentTime()
organization.AccountItems = []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
}
AddOrganization(organization)
}
func initDefinedApplication(application *Application) {
existed := getApplication(application.Owner, application.Name)
if existed != nil {
return
}
application.CreatedTime = util.GetCurrentTime()
AddApplication(application)
}
func initDefinedUser(user *User) {
existed := getUser(user.Owner, user.Name)
if existed != nil {
return
}
user.CreatedTime = util.GetCurrentTime()
user.Id = util.GenerateId()
user.Properties = make(map[string]string)
AddUser(user)
}
func initDefinedCert(cert *Cert) {
existed := getCert(cert.Owner, cert.Name)
if existed != nil {
return
}
cert.CreatedTime = util.GetCurrentTime()
AddCert(cert)
}
func initDefinedLdap(ldap *Ldap) {
existed := GetLdap(ldap.Id)
if existed != nil {
return
}
AddLdap(ldap)
}
func initDefinedProvider(provider *Provider) {
existed := GetProvider(provider.GetId())
if existed != nil {
return
}
AddProvider(provider)
}

View File

@ -16,7 +16,12 @@ package object
import (
"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"
"xorm.io/core"
)
@ -39,6 +44,16 @@ type Permission struct {
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 {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Permission{})
@ -95,7 +110,8 @@ func GetPermission(id string) *Permission {
func UpdatePermission(id string, permission *Permission) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getPermission(owner, name) == nil {
oldPermission := getPermission(owner, name)
if oldPermission == nil {
return false
}
@ -104,6 +120,11 @@ func UpdatePermission(id string, permission *Permission) bool {
panic(err)
}
if affected != 0 {
removePolicies(oldPermission)
addPolicies(permission)
}
return affected != 0
}
@ -113,6 +134,10 @@ func AddPermission(permission *Permission) bool {
panic(err)
}
if affected != 0 {
addPolicies(permission)
}
return affected != 0
}
@ -122,9 +147,85 @@ func DeletePermission(permission *Permission) bool {
panic(err)
}
if affected != 0 {
removePolicies(permission)
}
return affected != 0
}
func (permission *Permission) GetId() string {
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 (
"bytes"
"fmt"
"net/url"
"strings"
"github.com/casdoor/casdoor/conf"
@ -42,8 +43,19 @@ func getProviderEndpoint(provider *Provider) string {
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) {
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
escapedPath := escapePath(fullFilePath)
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
host := ""
if provider.Type != "Local File System" {

View File

@ -17,7 +17,6 @@ package object
import (
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"strings"
"time"
@ -28,7 +27,14 @@ import (
)
const (
hourSeconds = 3600
hourSeconds = 3600
INVALID_REQUEST = "invalid_request"
INVALID_CLIENT = "invalid_client"
INVALID_GRANT = "invalid_grant"
UNAUTHORIZED_CLIENT = "unauthorized_client"
UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"
INVALID_SCOPE = "invalid_scope"
ENDPOINT_ERROR = "endpoint_error"
)
type Code struct {
@ -63,7 +69,11 @@ type TokenWrapper struct {
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
Error string `json:"error,omitempty"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
type IntrospectionResponse struct {
@ -311,59 +321,42 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
var errString string
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) interface{} {
application := GetApplicationByClientId(clientId)
if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_id is invalid",
}
}
//Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: UNSUPPORTED_GRANT_TYPE,
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
}
}
var token *Token
var err error
var tokenError *TokenError
switch grantType {
case "authorization_code": // Authorization Code Grant
token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
token, tokenError = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
case "password": // Resource Owner Password Credentials Grant
token, err = GetPasswordToken(application, username, password, scope, host)
token, tokenError = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
token, tokenError = GetClientCredentialsToken(application, clientSecret, scope, host)
}
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
token, tokenError = GetWechatMiniProgramToken(application, code, host, username, avatar)
}
if err != nil {
errString = err.Error()
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
}
if tokenError != nil {
return tokenError
}
token.CodeIsUsed = true
@ -380,81 +373,59 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
return tokenWrapper
}
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
var errString string
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) interface{} {
// check parameters
if grantType != "refresh_token" {
errString = "error: grant_type should be \"refresh_token\""
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: UNSUPPORTED_GRANT_TYPE,
ErrorDescription: "grant_type should be refresh_token",
}
}
application := GetApplicationByClientId(clientId)
if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_id is invalid",
}
}
if clientSecret != "" && application.ClientSecret != clientSecret {
errString = "error: invalid client_secret"
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
}
// check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token)
if err != nil || !existed {
errString = "error: invalid refresh_token"
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "refresh token is invalid, expired or revoked",
}
}
cert := getCertByApplication(application)
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
errString := fmt.Sprintf("error: %s", err.Error())
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_GRANT,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}
}
// generate a new token
user := getUser(application.Organization, token.User)
if user.IsForbidden {
errString = "error: the user is forbidden to sign in, please contact the administrator"
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
return &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
panic(err)
return &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
newToken := &Token{
@ -508,63 +479,99 @@ func IsGrantTypeValid(method string, grantTypes []string) bool {
}
// Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) {
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError) {
if code == "" {
return nil, errors.New("error: authorization code should not be empty")
return nil, &TokenError{
Error: INVALID_REQUEST,
ErrorDescription: "authorization code should not be empty",
}
}
token := getTokenByCode(code)
if token == nil {
return nil, errors.New("error: invalid authorization code")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code is invalid",
}
}
if token.CodeIsUsed {
// anti replay attacks
return nil, errors.New("error: authorization code has been used")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code has been used",
}
}
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, errors.New("error: incorrect code_verifier")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "verifier is invalid",
}
}
if application.ClientSecret != clientSecret {
// when using PKCE, the Client Secret can be empty,
// but if it is provided, it must be accurate.
if token.CodeChallenge == "" {
return nil, errors.New("error: invalid client_secret")
return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
} else {
if clientSecret != "" {
return nil, errors.New("error: invalid client_secret")
return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
}
}
}
if application.Name != token.Application {
return nil, errors.New("error: the token is for wrong application (client_id)")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the token is for wrong application (client_id)",
}
}
if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes
return nil, errors.New("error: authorization code has expired")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code has expired",
}
}
return token, nil
}
// Resource Owner Password Credentials flow
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) {
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError) {
user := getUser(application.Organization, username)
if user == nil {
return nil, errors.New("error: the user does not exist")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the user does not exist",
}
}
msg := CheckPassword(user, password)
if msg != "" {
return nil, errors.New("error: invalid username or password")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "invalid username or password",
}
}
if user.IsForbidden {
return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, err
return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
token := &Token{
Owner: application.Owner,
@ -586,9 +593,12 @@ func GetPasswordToken(application *Application, username string, password string
}
// Client Credentials flow
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) {
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError) {
if application.ClientSecret != clientSecret {
return nil, errors.New("error: invalid client_secret")
return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
}
nullUser := &User{
Owner: application.Owner,
@ -597,7 +607,10 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
}
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil {
return nil, err
return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
token := &Token{
Owner: application.Owner,
@ -643,25 +656,37 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
}
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, *TokenError) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, errors.New("error: the application does not support wechat mini program")
return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "the application does not support wechat mini program",
}
}
provider := GetProvider(util.GetId(mpProvider.Name))
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, err
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
}
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, errors.New("err: WeChat's openid and unionid are empty")
return nil, &TokenError{
Error: INVALID_REQUEST,
ErrorDescription: "the wechat mini program session is invalid",
}
}
user := getUserByWechatId(openId, unionId)
if user == nil {
if !application.EnableSignUp {
return nil, errors.New("err: the application does not allow to sign up new account")
return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the application does not allow to sign up new account",
}
}
//Add new user
var name string
@ -691,7 +716,10 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, err
return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
token := &Token{

View File

@ -20,6 +20,7 @@ import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
"xorm.io/core"
)
@ -99,6 +100,8 @@ type User struct {
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
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 {
columns = []string{"owner", "display_name", "avatar",
"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 {
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 {
sock5Proxy := conf.GetConfigString("sock5Proxy")
if sock5Proxy == "" {
socks5Proxy := conf.GetConfigString("socks5Proxy")
if socks5Proxy == "" {
return &http.Client{}
}
if !isAddressOpen(sock5Proxy) {
if !isAddressOpen(socks5Proxy) {
return &http.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 {
panic(err)
}

View File

@ -109,6 +109,10 @@ func getUrlPath(urlPath string) string {
return "/api/login/oauth"
}
if strings.HasPrefix(urlPath, "/api/webauthn") {
return "/api/webauthn"
}
return urlPath
}

View File

@ -31,7 +31,9 @@ const (
func CorsFilter(ctx *context.Context) {
origin := ctx.Input.Header(headerOrigin)
if origin != "" && origin != conf.GetConfigString("origin") {
originConf := conf.GetConfigString("origin")
if origin != "" && originConf != "" && origin != originConf {
if object.IsAllowOrigin(origin) {
ctx.Output.Header(headerAllowOrigin, origin)
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")

View File

@ -191,4 +191,9 @@ func initAPI() {
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin")
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish")
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "Get:WebAuthnSigninBegin")
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
}

View File

@ -2194,6 +2194,18 @@
"schema": {
"$ref": "#/definitions/object.TokenWrapper"
}
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
}
}
}
@ -2285,6 +2297,18 @@
"schema": {
"$ref": "#/definitions/object.IntrospectionResponse"
}
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
}
}
}
@ -2377,6 +2401,18 @@
"schema": {
"$ref": "#/definitions/object.TokenWrapper"
}
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
}
}
}
@ -3063,11 +3099,11 @@
}
},
"definitions": {
"2200.0xc0003c4b70.false": {
"2127.0xc000398090.false": {
"title": "false",
"type": "object"
},
"2235.0xc0003c4ba0.false": {
"2161.0xc0003980c0.false": {
"title": "false",
"type": "object"
},
@ -3082,6 +3118,9 @@
"content": {
"type": "string"
},
"provider": {
"type": "string"
},
"receivers": {
"type": "array",
"items": {
@ -3182,10 +3221,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2200.0xc0003c4b70.false"
"$ref": "#/definitions/2127.0xc000398090.false"
},
"data2": {
"$ref": "#/definitions/2235.0xc0003c4ba0.false"
"$ref": "#/definitions/2161.0xc0003980c0.false"
},
"msg": {
"type": "string"
@ -4209,6 +4248,18 @@
}
}
},
"object.TokenError": {
"title": "TokenError",
"type": "object",
"properties": {
"error": {
"type": "string"
},
"error_description": {
"type": "string"
}
}
},
"object.TokenWrapper": {
"title": "TokenWrapper",
"type": "object",
@ -4216,9 +4267,6 @@
"access_token": {
"type": "string"
},
"error": {
"type": "string"
},
"expires_in": {
"type": "integer",
"format": "int64"

View File

@ -1435,6 +1435,14 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.TokenWrapper'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/login/oauth/code:
post:
tags:
@ -1497,6 +1505,14 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.IntrospectionResponse'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/login/oauth/logout:
get:
tags:
@ -1559,6 +1575,14 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.TokenWrapper'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/logout:
post:
tags:
@ -2005,10 +2029,10 @@ paths:
- Verification API
operationId: ApiController.VerifyCaptcha
definitions:
2200.0xc0003c4b70.false:
2127.0xc000398090.false:
title: "false"
type: object
2235.0xc0003c4ba0.false:
2161.0xc0003980c0.false:
title: "false"
type: object
Response:
@ -2020,6 +2044,8 @@ definitions:
properties:
content:
type: string
provider:
type: string
receivers:
type: array
items:
@ -2087,9 +2113,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2200.0xc0003c4b70.false'
$ref: '#/definitions/2127.0xc000398090.false'
data2:
$ref: '#/definitions/2235.0xc0003c4ba0.false'
$ref: '#/definitions/2161.0xc0003980c0.false'
msg:
type: string
name:
@ -2776,14 +2802,20 @@ definitions:
type: string
user:
type: string
object.TokenError:
title: TokenError
type: object
properties:
error:
type: string
error_description:
type: string
object.TokenWrapper:
title: TokenWrapper
type: object
properties:
access_token:
type: string
error:
type: string
expires_in:
type: integer
format: int64

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 = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/swagger': {
target: 'http://localhost:8000',
"/swagger": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/files': {
target: 'http://localhost:8000',
"/files": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/.well-known/openid-configuration': {
target: 'http://localhost:8000',
"/.well-known/openid-configuration": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/cas/serviceValidate': {
target: 'http://localhost:8000',
"/cas/serviceValidate": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/cas/proxyValidate': {
target: 'http://localhost:8000',
"/cas/proxyValidate": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/cas/proxy': {
target: 'http://localhost:8000',
"/cas/proxy": {
target: "http://localhost:8000",
changeOrigin: true,
},
'/cas/validate': {
target: 'http://localhost:8000',
"/cas/validate": {
target: "http://localhost:8000",
changeOrigin: true,
}
},
@ -43,7 +43,7 @@ module.exports = {
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: {'@primary-color': 'rgb(45,120,213)'},
modifyVars: {"@primary-color": "rgb(45,120,213)"},
javascriptEnabled: true,
},
},

View File

@ -59,6 +59,8 @@
]
},
"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.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
import {Button, Col, Row, Select, Switch, Table, Tooltip} from 'antd';
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
const { Option } = Select;
const {Option} = Select;
class AccountTable extends React.Component {
constructor(props) {
@ -65,8 +65,8 @@ class AccountTable extends React.Component {
const columns = [
{
title: i18next.t("provider:Name"),
dataIndex: 'name',
key: 'name',
dataIndex: "name",
key: "name",
render: (text, record, index) => {
const items = [
{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 forbidden", displayName: i18next.t("user:Is forbidden")},
{name: "Is deleted", displayName: i18next.t("user:Is deleted")},
{name: "WebAuthn credentials", displayName: i18next.t("user:WebAuthn credentials")},
];
const getItemDisplayName = (text) => {
@ -103,63 +104,63 @@ class AccountTable extends React.Component {
};
return (
<Select virtual={false} style={{width: '100%'}}
value={getItemDisplayName(text)}
onChange={value => {
this.updateField(table, index, 'name', value);
}} >
<Select virtual={false} style={{width: "100%"}}
value={getItemDisplayName(text)}
onChange={value => {
this.updateField(table, index, "name", value);
}} >
{
Setting.getDeduplicatedArray(items, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.displayName}</Option>)
}
</Select>
)
);
}
},
{
title: i18next.t("provider:visible"),
dataIndex: 'visible',
key: 'visible',
width: '120px',
dataIndex: "visible",
key: "visible",
width: "120px",
render: (text, record, index) => {
return (
<Switch checked={text} onChange={checked => {
this.updateField(table, index, 'visible', checked);
this.updateField(table, index, "visible", checked);
}} />
)
);
}
},
{
title: i18next.t("organization:viewRule"),
dataIndex: 'viewRule',
key: 'viewRule',
width: '155px',
dataIndex: "viewRule",
key: "viewRule",
width: "155px",
render: (text, record, index) => {
if (!record.visible) {
return null;
}
let options = [
{id: 'Public', name: 'Public'},
{id: 'Self', name: 'Self'},
{id: 'Admin', name: 'Admin'},
{id: "Public", name: "Public"},
{id: "Self", name: "Self"},
{id: "Admin", name: "Admin"},
];
return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
this.updateField(table, index, 'viewRule', value);
<Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
this.updateField(table, index, "viewRule", value);
})}>
{
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
)
);
}
},
{
title: i18next.t("organization:modifyRule"),
dataIndex: 'modifyRule',
key: 'modifyRule',
width: '155px',
dataIndex: "modifyRule",
key: "modifyRule",
width: "155px",
render: (text, record, index) => {
if (!record.visible) {
return null;
@ -168,32 +169,32 @@ class AccountTable extends React.Component {
let options;
if (record.viewRule === "Admin") {
options = [
{id: 'Admin', name: 'Admin'},
{id: 'Immutable', name: 'Immutable'},
{id: "Admin", name: "Admin"},
{id: "Immutable", name: "Immutable"},
];
} else {
options = [
{id: 'Self', name: 'Self'},
{id: 'Admin', name: 'Admin'},
{id: 'Immutable', name: 'Immutable'},
{id: "Self", name: "Self"},
{id: "Admin", name: "Admin"},
{id: "Immutable", name: "Immutable"},
];
}
return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {
this.updateField(table, index, 'modifyRule', value);
<Select virtual={false} style={{width: "100%"}} value={text} onChange={(value => {
this.updateField(table, index, "modifyRule", value);
})}>
{
options.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
)
);
}
},
{
title: i18next.t("general:Action"),
key: 'action',
width: '100px',
key: "action",
width: "100px",
render: (text, record, index) => {
return (
<div>
@ -213,13 +214,13 @@ class AccountTable extends React.Component {
];
return (
<Table scroll={{x: 'max-content'}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
<Table scroll={{x: "max-content"}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>
);
}
@ -227,7 +228,7 @@ class AccountTable extends React.Component {
render() {
return (
<div>
<Row style={{marginTop: '20px'}} >
<Row style={{marginTop: "20px"}} >
<Col span={24}>
{
this.renderTable(this.props.table)
@ -235,7 +236,7 @@ class AccountTable extends React.Component {
</Col>
</Row>
</div>
)
);
}
}

View File

@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React, {Component} from 'react';
import './App.less';
import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons';
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from 'antd';
import {Link, Redirect, Route, Switch, withRouter} from 'react-router-dom'
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
import UserListPage from "./UserListPage";
@ -63,16 +63,16 @@ import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage";
import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from './SelectLanguageBox';
import i18next from 'i18next';
import SelectLanguageBox from "./SelectLanguageBox";
import i18next from "i18next";
import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from './auth/SamlCallback';
import SamlCallback from "./auth/SamlCallback";
import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage";
const { Header, Footer } = Layout;
const {Header, Footer} = Layout;
class App extends Component {
constructor(props) {
@ -110,46 +110,46 @@ class App extends Component {
this.setState({
uri: uri,
});
if (uri === '/') {
this.setState({ selectedMenuKey: '/' });
} else if (uri.includes('/organizations')) {
this.setState({ selectedMenuKey: '/organizations' });
} else if (uri.includes('/users')) {
this.setState({ selectedMenuKey: '/users' });
} else if (uri.includes('/roles')) {
this.setState({ selectedMenuKey: '/roles' });
} else if (uri.includes('/permissions')) {
this.setState({ selectedMenuKey: '/permissions' });
} else if (uri.includes('/models')) {
this.setState({ selectedMenuKey: '/models' });
} else if (uri.includes('/providers')) {
this.setState({ selectedMenuKey: '/providers' });
} else if (uri.includes('/applications')) {
this.setState({ selectedMenuKey: '/applications' });
} else if (uri.includes('/resources')) {
this.setState({ selectedMenuKey: '/resources' });
} else if (uri.includes('/tokens')) {
this.setState({ selectedMenuKey: '/tokens' });
} else if (uri.includes('/records')) {
this.setState({ selectedMenuKey: '/records' });
} else if (uri.includes('/webhooks')) {
this.setState({ selectedMenuKey: '/webhooks' });
} else if (uri.includes('/syncers')) {
this.setState({ selectedMenuKey: '/syncers' });
} else if (uri.includes('/certs')) {
this.setState({ selectedMenuKey: '/certs' });
} else if (uri.includes('/products')) {
this.setState({ selectedMenuKey: '/products' });
} else if (uri.includes('/payments')) {
this.setState({ selectedMenuKey: '/payments' });
} else if (uri.includes('/signup')) {
this.setState({ selectedMenuKey: '/signup' });
} else if (uri.includes('/login')) {
this.setState({ selectedMenuKey: '/login' });
} else if (uri.includes('/result')) {
this.setState({ selectedMenuKey: '/result' });
if (uri === "/") {
this.setState({selectedMenuKey: "/"});
} else if (uri.includes("/organizations")) {
this.setState({selectedMenuKey: "/organizations"});
} else if (uri.includes("/users")) {
this.setState({selectedMenuKey: "/users"});
} else if (uri.includes("/roles")) {
this.setState({selectedMenuKey: "/roles"});
} else if (uri.includes("/permissions")) {
this.setState({selectedMenuKey: "/permissions"});
} else if (uri.includes("/models")) {
this.setState({selectedMenuKey: "/models"});
} else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/applications")) {
this.setState({selectedMenuKey: "/applications"});
} else if (uri.includes("/resources")) {
this.setState({selectedMenuKey: "/resources"});
} else if (uri.includes("/tokens")) {
this.setState({selectedMenuKey: "/tokens"});
} else if (uri.includes("/records")) {
this.setState({selectedMenuKey: "/records"});
} else if (uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/webhooks"});
} else if (uri.includes("/syncers")) {
this.setState({selectedMenuKey: "/syncers"});
} else if (uri.includes("/certs")) {
this.setState({selectedMenuKey: "/certs"});
} else if (uri.includes("/products")) {
this.setState({selectedMenuKey: "/products"});
} else if (uri.includes("/payments")) {
this.setState({selectedMenuKey: "/payments"});
} else if (uri.includes("/signup")) {
this.setState({selectedMenuKey: "/signup"});
} else if (uri.includes("/login")) {
this.setState({selectedMenuKey: "/login"});
} else if (uri.includes("/result")) {
this.setState({selectedMenuKey: "/result"});
} else {
this.setState({ selectedMenuKey: -1 });
this.setState({selectedMenuKey: -1});
}
}
@ -234,12 +234,12 @@ class App extends Component {
AuthBackend.logout()
.then((res) => {
if (res.status === 'ok') {
if (res.status === "ok") {
this.setState({
account: null
});
Setting.showMessage("success", `Logged out successfully`);
Setting.showMessage("success", "Logged out successfully");
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
@ -259,9 +259,9 @@ class App extends Component {
}
handleRightDropdownClick(e) {
if (e.key === '/account') {
this.props.history.push(`/account`);
} else if (e.key === '/logout') {
if (e.key === "/account") {
this.props.history.push("/account");
} else if (e.key === "/logout") {
this.logout();
}
}
@ -269,16 +269,16 @@ class App extends Component {
renderAvatar() {
if (this.state.account.avatar === "") {
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)}
</Avatar>
)
);
} else {
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)}
</Avatar>
)
);
}
}
@ -287,10 +287,12 @@ class App extends Component {
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account">
<SettingOutlined />
&nbsp;
{i18next.t("account:My Account")}
</Menu.Item>
<Menu.Item key="/logout">
<LogoutOutlined />
&nbsp;
{i18next.t("account:Logout")}
</Menu.Item>
</Menu>
@ -298,7 +300,7 @@ class App extends Component {
return (
<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;
{
@ -312,7 +314,7 @@ class App extends Component {
&nbsp;
</div>
</Dropdown>
)
);
}
renderAccount() {
@ -484,7 +486,7 @@ class App extends Component {
renderHomeIfLoggedIn(component) {
if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to='/' />
return <Redirect to="/" />;
} else {
return component;
}
@ -493,77 +495,114 @@ class App extends Component {
renderLoginIfNotLoggedIn(component) {
if (this.state.account === null) {
sessionStorage.setItem("from", window.location.pathname);
return <Redirect to='/login' />
return <Redirect to="/login" />;
} else if (this.state.account === undefined) {
return null;
}
else {
} else {
return component;
}
}
isStartPages() {
return window.location.pathname.startsWith('/login') ||
window.location.pathname.startsWith('/signup') ||
window.location.pathname === '/';
return window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/signup") ||
window.location.pathname === "/";
}
renderRouter(){
renderRouter() {
return(
<div>
<Switch>
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/>
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)}/>
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)}/>
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)}/>
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)}/>
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)}/>
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />}/>
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)}/>
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)}/>
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)}/>
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/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="/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="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)}/>
{/*<Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)}/>
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)}/>
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)}/>
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)}/>
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)}/>
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
<Route exact path="/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="/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="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</div>
)
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</div>
);
}
renderContent() {
if (!Setting.isMobile()) {
return (
<div style={{display: 'flex', flex: 'auto',width:"100%",flexDirection: 'column'}}>
<Layout style={{display: 'flex', alignItems: 'stretch'}}>
<Header style={{ padding: '0', marginBottom: '3px'}}>
<div style={{display: "flex", flex: "auto", width:"100%", flexDirection: "column"}}>
<Layout style={{display: "flex", alignItems: "stretch"}}>
<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 : (
<Link to={"/"}>
@ -571,66 +610,28 @@ class App extends Component {
</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 : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<Menu
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px' }}
>
{
this.renderMenu()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header>
{
this.renderRouter()
}
</div>
)
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
>
{
this.renderMenu()
}
<div style = {{float: "right"}}>
{
this.renderAccount()
}
<SelectLanguageBox />
</div>
</Menu>
</Header>
{
this.renderRouter()
}
</div>
);
}
}
@ -641,14 +642,14 @@ class App extends Component {
return (
<Footer id="footer" style={
{
borderTop: '1px solid #e8e8e8',
backgroundColor: 'white',
textAlign: 'center',
borderTop: "1px solid #e8e8e8",
backgroundColor: "white",
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>
)
);
}
isDoorPages() {
@ -663,25 +664,30 @@ class App extends Component {
renderPage() {
if (this.isDoorPages()) {
return (
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/>
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/>
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
<Route exact path="/callback" component={AuthCallback}/>
<Route exact path="/callback/saml" component={SamlCallback}/>
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)}/>
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)}/>
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} {...props} />)}/>
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}/>} />
</Switch>
)
<div>
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} />
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
{
this.renderFooter()
}
</div>
);
}
return (
@ -711,7 +717,7 @@ class App extends Component {
this.renderPage()
}
</React.Fragment>
)
);
}
const organization = this.state.account.organization;
@ -725,7 +731,7 @@ class App extends Component {
this.renderPage()
}
</React.Fragment>
)
);
}
}

View File

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

View File

@ -13,7 +13,7 @@
// limitations under the License.
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 * as ApplicationBackend from "./backend/ApplicationBackend";
import * as CertBackend from "./backend/CertBackend";
@ -30,13 +30,13 @@ import SignupTable from "./SignupTable";
import PromptPage from "./auth/PromptPage";
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";
require('codemirror/theme/material-darker.css');
require("codemirror/theme/material-darker.css");
require("codemirror/mode/htmlmixed/htmlmixed");
require("codemirror/mode/xml/xml");
const { Option } = Select;
const {Option} = Select;
class ApplicationEditPage extends React.Component {
constructor(props) {
@ -106,7 +106,7 @@ class ApplicationEditPage extends React.Component {
.then((res) => {
this.setState({
samlMetadata: res,
})
});
});
}
@ -144,7 +144,7 @@ class ApplicationEditPage extends React.Component {
}
}).finally(() => {
this.setState({uploading: false});
})
});
}
renderApplication() {
@ -153,262 +153,272 @@ class ApplicationEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<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>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.application.displayName} onChange={e => {
this.updateApplicationField('displayName', e.target.value);
this.updateApplicationField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.application.logo} onChange={e => {
this.updateApplicationField('logo', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.logo} onChange={e => {
this.updateApplicationField("logo", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<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>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Home"), i18next.t("general:Home - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.homepageUrl} onChange={e => {
this.updateApplicationField('homepageUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.homepageUrl} onChange={e => {
this.updateApplicationField("homepageUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Description"), i18next.t("general:Description - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.application.description} onChange={e => {
this.updateApplicationField('description', e.target.value);
this.updateApplicationField("description", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.application.clientId} onChange={e => {
this.updateApplicationField('clientId', e.target.value);
this.updateApplicationField("clientId", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.application.clientSecret} onChange={e => {
this.updateApplicationField('clientSecret', e.target.value);
this.updateApplicationField("clientSecret", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Redirect URLs"), i18next.t("application:Redirect URLs - Tooltip"))} :
</Col>
<Col span={22} >
<UrlTable
title={i18next.t("application:Redirect URLs")}
table={this.state.application.redirectUris}
onUpdateTable={(value) => { this.updateApplicationField('redirectUris', value)}}
onUpdateTable={(value) => {this.updateApplicationField("redirectUris", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
</Col>
<Col span={22} >
<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>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Refresh token expire"), i18next.t("application:Refresh token expire - Tooltip"))} :
</Col>
<Col span={22} >
<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>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Password ON"), i18next.t("application:Password ON - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enablePassword} onChange={checked => {
this.updateApplicationField('enablePassword', checked);
this.updateApplicationField("enablePassword", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable signup"), i18next.t("application:Enable signup - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSignUp} onChange={checked => {
this.updateApplicationField('enableSignUp', checked);
this.updateApplicationField("enableSignUp", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Signin session"), i18next.t("application:Enable signin session - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSigninSession} onChange={checked => {
this.updateApplicationField('enableSigninSession', checked);
this.updateApplicationField("enableSigninSession", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableCodeSignin} onChange={checked => {
this.updateApplicationField('enableCodeSignin', checked);
this.updateApplicationField("enableCodeSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<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"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.signupUrl} onChange={e => {
this.updateApplicationField('signupUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.signupUrl} onChange={e => {
this.updateApplicationField("signupUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Signin URL"), i18next.t("general:Signin URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.signinUrl} onChange={e => {
this.updateApplicationField('signinUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.signinUrl} onChange={e => {
this.updateApplicationField("signinUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Forget URL"), i18next.t("general:Forget URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.forgetUrl} onChange={e => {
this.updateApplicationField('forgetUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.forgetUrl} onChange={e => {
this.updateApplicationField("forgetUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Affiliation URL"), i18next.t("general:Affiliation URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.application.affiliationUrl} onChange={e => {
this.updateApplicationField('affiliationUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.application.affiliationUrl} onChange={e => {
this.updateApplicationField("affiliationUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Terms of Use"), i18next.t("provider:Terms of Use - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.application.termsOfUse} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("termsOfUse", e.target.value);
}}/>
}} />
<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>
</Upload>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Signup HTML"), i18next.t("provider:Signup HTML - Tooltip"))} :
</Col>
<Col span={22} >
@ -416,7 +426,7 @@ class ApplicationEditPage extends React.Component {
<div style={{width: "900px", height: "300px"}} >
<CodeMirror
value={this.state.application.signupHtml}
options={{mode: 'htmlmixed', theme: "material-darker"}}
options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateApplicationField("signupHtml", value);
}}
@ -424,13 +434,13 @@ class ApplicationEditPage extends React.Component {
</div>
} title={i18next.t("provider:Signup HTML - Edit")} trigger="click">
<Input value={this.state.application.signupHtml} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("signupHtml", e.target.value)
}}/>
this.updateApplicationField("signupHtml", e.target.value);
}} />
</Popover>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Signin HTML"), i18next.t("provider:Signin HTML - Tooltip"))} :
</Col>
<Col span={22} >
@ -438,7 +448,7 @@ class ApplicationEditPage extends React.Component {
<div style={{width: "900px", height: "300px"}} >
<CodeMirror
value={this.state.application.signinHtml}
options={{mode: 'htmlmixed', theme: "material-darker"}}
options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateApplicationField("signinHtml", value);
}}
@ -446,55 +456,55 @@ class ApplicationEditPage extends React.Component {
</div>
} title={i18next.t("provider:Signin HTML - Edit")} trigger="click">
<Input value={this.state.application.signinHtml} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("signinHtml", e.target.value)
}}/>
this.updateApplicationField("signinHtml", e.target.value);
}} />
</Popover>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Grant types"), i18next.t("application:Grant types - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}}
value={this.state.application.grantTypes}
onChange={(value => {
this.updateApplicationField('grantTypes', value);
})} >
{
[
{id: "authorization_code", name: "Authorization Code"},
{id: "password", name: "Password"},
{id: "client_credentials", name: "Client Credentials"},
{id: "token", name: "Token"},
{id: "id_token", name: "ID Token"},
{id: "refresh_token", name: "Refresh Token"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
<Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.application.grantTypes}
onChange={(value => {
this.updateApplicationField("grantTypes", value);
})} >
{
[
{id: "authorization_code", name: "Authorization Code"},
{id: "password", name: "Password"},
{id: "client_credentials", name: "Client Credentials"},
{id: "token", name: "Token"},
{id: "id_token", name: "ID Token"},
{id: "refresh_token", name: "Refresh Token"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable SAML compress"), i18next.t("application:Enable SAML compress - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableSamlCompress} onChange={checked => {
this.updateApplicationField('enableSamlCompress', checked);
this.updateApplicationField("enableSamlCompress", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
</Col>
<Col span={22}>
<CodeMirror
value={this.state.samlMetadata}
options={{mode: 'xml', theme: 'default'}}
options={{mode: "xml", theme: "default"}}
onBeforeChange={(editor, data, value) => {}}
/>
<br/>
<br />
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
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"));
@ -504,8 +514,8 @@ class ApplicationEditPage extends React.Component {
</Button>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Providers"), i18next.t("general:Providers - Tooltip"))} :
</Col>
<Col span={22} >
@ -514,12 +524,12 @@ class ApplicationEditPage extends React.Component {
table={this.state.application.providers}
providers={this.state.providers}
application={this.state.application}
onUpdateTable={(value) => { this.updateApplicationField('providers', value)}}
onUpdateTable={(value) => {this.updateApplicationField("providers", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
{
@ -528,22 +538,22 @@ class ApplicationEditPage extends React.Component {
</Row>
{
!this.state.application.enableSignUp ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Signup items"), i18next.t("application:Signup items - Tooltip"))} :
</Col>
<Col span={22} >
<SignupTable
title={i18next.t("application:Signup items")}
table={this.state.application.signupItems}
onUpdateTable={(value) => { this.updateApplicationField('signupItems', value)}}
onUpdateTable={(value) => {this.updateApplicationField("signupItems", value);}}
/>
</Col>
</Row>
)
}
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
{
@ -551,13 +561,13 @@ class ApplicationEditPage extends React.Component {
}
</Row>
</Card>
)
);
}
renderSignupSigninPreview() {
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 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) {
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")}
</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"}}>
{
this.state.application.enablePassword ? (
@ -592,19 +602,19 @@ class ApplicationEditPage extends React.Component {
>
{i18next.t("application:Copy signin page URL")}
</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"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
<div style={maskStyle}></div>
</div>
</Col>
</React.Fragment>
)
);
}
renderPromptPreview() {
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 (
<Col span={11}>
<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")}
</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"}}>
<PromptPage application={this.state.application} account={this.props.account} />
<div style={maskStyle}></div>
</div>
</Col>
)
);
}
submitApplicationEdit(willExist) {
@ -628,19 +638,19 @@ class ApplicationEditPage extends React.Component {
ApplicationBackend.updateApplication(this.state.application.owner, this.state.applicationName, application)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
applicationName: this.state.application.name,
});
if (willExist) {
this.props.history.push(`/applications`);
this.props.history.push("/applications");
} else {
this.props.history.push(`/applications/${this.state.application.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateApplicationField('name', this.state.applicationName);
this.updateApplicationField("name", this.state.applicationName);
}
})
.catch(error => {
@ -651,7 +661,7 @@ class ApplicationEditPage extends React.Component {
deleteApplication() {
ApplicationBackend.deleteApplication(this.state.application)
.then(() => {
this.props.history.push(`/applications`);
this.props.history.push("/applications");
})
.catch(error => {
Setting.showMessage("error", `Application failed to delete: ${error}`);
@ -661,15 +671,15 @@ class ApplicationEditPage extends React.Component {
render() {
return (
<div>
{
this.state.application !== null ? this.renderApplication() : null
}
<div style={{marginTop: '20px', marginLeft: '40px'}}>
<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>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
{
this.state.application !== null ? this.renderApplication() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<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>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>
</div>
);
}
}

View File

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

View File

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

View File

@ -13,15 +13,15 @@
// limitations under the License.
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 Setting from "./Setting";
import i18next from "i18next";
import copy from "copy-to-clipboard";
import FileSaver from "file-saver";
const { Option } = Select;
const { TextArea } = Input;
const {Option} = Select;
const {TextArea} = Input;
class CertEditPage extends React.Component {
constructor(props) {
@ -70,104 +70,104 @@ class CertEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.cert.name} onChange={e => {
this.updateCertField('name', e.target.value);
this.updateCertField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.cert.displayName} onChange={e => {
this.updateCertField('displayName', e.target.value);
this.updateCertField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Scope"), i18next.t("cert:Scope - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.scope} onChange={(value => {
this.updateCertField('scope', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.scope} onChange={(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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Type"), i18next.t("cert:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.type} onChange={(value => {
this.updateCertField('type', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.type} onChange={(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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Crypto algorithm"), i18next.t("cert:Crypto algorithm - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField('cryptoAlgorithm', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.cert.bitSize} onChange={value => {
this.updateCertField('bitSize', value);
this.updateCertField("bitSize", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Expire in years"), i18next.t("cert:Expire in years - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.cert.expireInYears} onChange={value => {
this.updateCertField('expireInYears', value);
this.updateCertField("expireInYears", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
</Col>
<Col span={9} >
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.publicKey);
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")}
</Button>
<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 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"))} :
</Col>
<Col span={9} >
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.privateKey);
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")}
</Button>
<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>
</Row>
</Card>
)
);
}
submitCertEdit(willExist) {
@ -218,19 +218,19 @@ class CertEditPage extends React.Component {
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
certName: this.state.cert.name,
});
if (willExist) {
this.props.history.push(`/certs`);
this.props.history.push("/certs");
} else {
this.props.history.push(`/certs/${this.state.cert.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateCertField('name', this.state.certName);
this.updateCertField("name", this.state.certName);
}
})
.catch(error => {
@ -241,7 +241,7 @@ class CertEditPage extends React.Component {
deleteCert() {
CertBackend.deleteCert(this.state.cert)
.then(() => {
this.props.history.push(`/certs`);
this.props.history.push("/certs");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

@ -16,122 +16,122 @@ import React, {useState} from "react";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting";
import {Button, Row, Col, Modal} from 'antd';
import {Button, Row, Col, Modal} from "antd";
import i18next from "i18next";
import * as ResourceBackend from "./backend/ResourceBackend";
export const CropperDiv = (props) => {
const [image, setImage] = useState("");
const [cropper, setCropper] = useState();
const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const {title} = props;
const {user} = props;
const {buttonText} = props;
let uploadButton;
const [image, setImage] = useState("");
const [cropper, setCropper] = useState();
const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const {title} = props;
const {user} = props;
const {buttonText} = props;
let uploadButton;
const onChange = (e) => {
e.preventDefault();
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
const reader = new FileReader();
reader.onload = () => {
setImage(reader.result);
};
if (!(files[0] instanceof Blob)) {
return;
}
reader.readAsDataURL(files[0]);
const onChange = (e) => {
e.preventDefault();
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
const reader = new FileReader();
reader.onload = () => {
setImage(reader.result);
};
if (!(files[0] instanceof Blob)) {
return;
}
reader.readAsDataURL(files[0]);
};
const uploadAvatar = () => {
cropper.getCroppedCanvas().toBlob(blob => {
if (blob === null) {
Setting.showMessage("error", "You must select a picture first!");
return false;
}
// Setting.showMessage("success", "uploading...");
const extension = image.substring(image.indexOf('/') + 1, image.indexOf(';base64'));
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
.then((res) => {
if (res.status === "ok") {
window.location.href = "/account";
} else {
Setting.showMessage("error", res.msg);
}
});
return true;
const uploadAvatar = () => {
cropper.getCroppedCanvas().toBlob(blob => {
if (blob === null) {
Setting.showMessage("error", "You must select a picture first!");
return false;
}
// Setting.showMessage("success", "uploading...");
const extension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64"));
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
.then((res) => {
if (res.status === "ok") {
window.location.href = "/account";
} else {
Setting.showMessage("error", res.msg);
}
});
return true;
});
};
const showModal = () => {
setVisible(true);
};
const handleOk = () => {
setConfirmLoading(true);
if (!uploadAvatar()) {
setConfirmLoading(false);
}
};
const showModal = () => {
setVisible(true);
};
const handleCancel = () => {
console.log("Clicked cancel button");
setVisible(false);
};
const handleOk = () => {
setConfirmLoading(true);
if (!uploadAvatar()) {
setConfirmLoading(false);
const selectFile = () => {
uploadButton.click();
};
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>]
}
};
const handleCancel = () => {
console.log('Clicked cancel button');
setVisible(false);
};
const selectFile = () => {
uploadButton.click();
}
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}}>
<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>
)
}
>
<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;

View File

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

View File

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

View File

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

View File

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

View File

@ -13,14 +13,14 @@
// limitations under the License.
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 OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import TextArea from "antd/es/input/TextArea";
const { Option } = Select;
const {Option} = Select;
class ModelEditPage extends React.Component {
constructor(props) {
@ -94,64 +94,64 @@ class ModelEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.model.name} onChange={e => {
this.updateModelField('name', e.target.value);
this.updateModelField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.model.displayName} onChange={e => {
this.updateModelField('displayName', e.target.value);
this.updateModelField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={10} value={this.state.model.modelText} onChange={e => {
this.updateModelField('modelText', e.target.value);
this.updateModelField("modelText", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.model.isEnabled} onChange={checked => {
this.updateModelField('isEnabled', checked);
this.updateModelField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
)
);
}
submitModelEdit(willExist) {
@ -159,19 +159,19 @@ class ModelEditPage extends React.Component {
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
modelName: this.state.model.name,
});
if (willExist) {
this.props.history.push(`/models`);
this.props.history.push("/models");
} else {
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateModelField('name', this.state.modelName);
this.updateModelField("name", this.state.modelName);
}
})
.catch(error => {
@ -182,7 +182,7 @@ class ModelEditPage extends React.Component {
deleteModel() {
ModelBackend.deleteModel(this.state.model)
.then(() => {
this.props.history.push(`/models`);
this.props.history.push("/models");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as LdapBackend from "./backend/LdapBackend";
import * as Setting from "./Setting";
@ -22,7 +22,7 @@ import {LinkOutlined} from "@ant-design/icons";
import LdapTable from "./LdapTable";
import AccountTable from "./AccountTable";
const { Option } = Select;
const {Option} = Select;
class OrganizationEditPage extends React.Component {
constructor(props) {
@ -53,7 +53,7 @@ class OrganizationEditPage extends React.Component {
getLdaps() {
LdapBackend.getLdaps(this.state.organizationName)
.then(res => {
let resdata = []
let resdata = [];
if (res.status === "ok") {
if (res.data !== null) {
resdata = res.data;
@ -61,8 +61,8 @@ class OrganizationEditPage extends React.Component {
}
this.setState({
ldaps: resdata
})
})
});
});
}
parseOrganizationField(key, value) {
@ -88,183 +88,183 @@ class OrganizationEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<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>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.displayName} onChange={e => {
this.updateOrganizationField('displayName', e.target.value);
this.updateOrganizationField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.favicon} onChange={e => {
this.updateOrganizationField('favicon', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.organization.favicon} onChange={e => {
this.updateOrganizationField("favicon", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<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>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Website URL"), i18next.t("organization:Website URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.websiteUrl} onChange={e => {
this.updateOrganizationField('websiteUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.organization.websiteUrl} onChange={e => {
this.updateOrganizationField("websiteUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password type"), i18next.t("general:Password type - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password salt"), i18next.t("general:Password salt - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.passwordSalt} onChange={e => {
this.updateOrganizationField('passwordSalt', e.target.value);
this.updateOrganizationField("passwordSalt", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} :
</Col>
<Col span={22} >
<Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => {
this.updateOrganizationField('phonePrefix', e.target.value);
this.updateOrganizationField("phonePrefix", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.organization.defaultAvatar} onChange={e => {
this.updateOrganizationField('defaultAvatar', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.organization.defaultAvatar} onChange={e => {
this.updateOrganizationField("defaultAvatar", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<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>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.masterPassword} onChange={e => {
this.updateOrganizationField('masterPassword', e.target.value);
this.updateOrganizationField("masterPassword", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.organization.enableSoftDeletion} onChange={checked => {
this.updateOrganizationField('enableSoftDeletion', checked);
this.updateOrganizationField("enableSoftDeletion", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Is profile public"), i18next.t("organization:Is profile public - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.organization.isProfilePublic} onChange={checked => {
this.updateOrganizationField('isProfilePublic', checked);
this.updateOrganizationField("isProfilePublic", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
</Col>
<Col span={22} >
<AccountTable
title={i18next.t("organization:Account items")}
table={this.state.organization.accountItems}
onUpdateTable={(value) => { this.updateOrganizationField('accountItems', value)}}
onUpdateTable={(value) => {this.updateOrganizationField("accountItems", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
</Col>
<Col span={22}>
@ -273,12 +273,13 @@ class OrganizationEditPage extends React.Component {
table={this.state.ldaps}
organizationName={this.state.organizationName}
onUpdateTable={(value) => {
this.setState({ldaps: value}) }}
this.setState({ldaps: value});
}}
/>
</Col>
</Row>
</Card>
)
);
}
submitOrganizationEdit(willExist) {
@ -286,19 +287,19 @@ class OrganizationEditPage extends React.Component {
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
organizationName: this.state.organization.name,
});
if (willExist) {
this.props.history.push(`/organizations`);
this.props.history.push("/organizations");
} else {
this.props.history.push(`/organizations/${this.state.organization.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateOrganizationField('name', this.state.organizationName);
this.updateOrganizationField("name", this.state.organizationName);
}
})
.catch(error => {
@ -309,7 +310,7 @@ class OrganizationEditPage extends React.Component {
deleteOrganization() {
OrganizationBackend.deleteOrganization(this.state.organization)
.then(() => {
this.props.history.push(`/organizations`);
this.props.history.push("/organizations");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

@ -14,7 +14,7 @@
import React from "react";
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 * as Setting from "./Setting";
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 deleted", visible: true, viewRule: "Admin", modifyRule: "Admin"},
],
}
};
}
addOrganization() {
const newOrganization = this.newOrganization();
OrganizationBackend.addOrganization(newOrganization)
.then((res) => {
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
}
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
}
)
.catch(error => {
Setting.showMessage("error", `Organization failed to add: ${error}`);
@ -82,12 +82,12 @@ class OrganizationListPage extends BaseListPage {
deleteOrganization(i) {
OrganizationBackend.deleteOrganization(this.state.data[i])
.then((res) => {
Setting.showMessage("success", `Organization deleted successfully`);
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
Setting.showMessage("success", "Organization deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
.catch(error => {
Setting.showMessage("error", `Organization failed to delete: ${error}`);
@ -98,25 +98,25 @@ class OrganizationListPage extends BaseListPage {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
width: '120px',
fixed: 'left',
dataIndex: "name",
key: "name",
width: "120px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps('name'),
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
)
);
}
},
{
title: i18next.t("general:Created time"),
dataIndex: 'createdTime',
key: 'createdTime',
width: '160px',
dataIndex: "createdTime",
key: "createdTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
@ -124,106 +124,106 @@ class OrganizationListPage extends BaseListPage {
},
{
title: i18next.t("general:Display name"),
dataIndex: 'displayName',
key: 'displayName',
dataIndex: "displayName",
key: "displayName",
// width: '100px',
sorter: true,
...this.getColumnSearchProps('displayName'),
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("organization:Favicon"),
dataIndex: 'favicon',
key: 'favicon',
width: '50px',
dataIndex: "favicon",
key: "favicon",
width: "50px",
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={40} />
</a>
)
);
}
},
{
title: i18next.t("organization:Website URL"),
dataIndex: 'websiteUrl',
key: 'websiteUrl',
width: '300px',
dataIndex: "websiteUrl",
key: "websiteUrl",
width: "300px",
sorter: true,
...this.getColumnSearchProps('websiteUrl'),
...this.getColumnSearchProps("websiteUrl"),
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
{text}
</a>
)
);
}
},
{
title: i18next.t("general:Password type"),
dataIndex: 'passwordType',
key: 'passwordType',
width: '150px',
dataIndex: "passwordType",
key: "passwordType",
width: "150px",
sorter: true,
filterMultiple: false,
filters: [
{text: 'plain', value: 'plain'},
{text: 'salt', value: 'salt'},
{text: 'md5-salt', value: 'md5-salt'},
{text: "plain", value: "plain"},
{text: "salt", value: "salt"},
{text: "md5-salt", value: "md5-salt"},
],
},
{
title: i18next.t("general:Password salt"),
dataIndex: 'passwordSalt',
key: 'passwordSalt',
width: '150px',
dataIndex: "passwordSalt",
key: "passwordSalt",
width: "150px",
sorter: true,
...this.getColumnSearchProps('passwordSalt'),
...this.getColumnSearchProps("passwordSalt"),
},
{
title: i18next.t("organization:Default avatar"),
dataIndex: 'defaultAvatar',
key: 'defaultAvatar',
width: '120px',
dataIndex: "defaultAvatar",
key: "defaultAvatar",
width: "120px",
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={40} />
</a>
)
<a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={40} />
</a>
);
}
},
{
title: i18next.t("organization:Soft deletion"),
dataIndex: 'enableSoftDeletion',
key: 'enableSoftDeletion',
width: '140px',
dataIndex: "enableSoftDeletion",
key: "enableSoftDeletion",
width: "140px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
)
);
}
},
{
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '240px',
dataIndex: "",
key: "op",
width: "240px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<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'}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</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>
<Popconfirm
title={`Sure to delete organization: ${record.name} ?`}
onConfirm={() => this.deleteOrganization(index)}
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>
</div>
)
);
}
},
];
@ -237,15 +237,15 @@ class OrganizationListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Organizations")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
<Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Organizations")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
@ -258,7 +258,7 @@ class OrganizationListPage extends BaseListPage {
field = "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)
.then((res) => {
if (res.status === "ok") {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// 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 React from "react";
import * as UserBackend from "./backend/UserBackend";
@ -50,17 +50,16 @@ export const PasswordModal = (props) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("user:Password Set"));
setVisible(false);
}
else Setting.showMessage("error", i18next.t(`user:${res.msg}`));
})
}
} else {Setting.showMessage("error", i18next.t(`user:${res.msg}`));}
});
};
let hasOldPassword = user.password !== "";
return (
<Row>
<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>
<Modal
maskClosable={false}
@ -74,21 +73,21 @@ export const PasswordModal = (props) => {
width={600}
>
<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"}}>
<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>
) : null}
<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 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>
</Col>
</Modal>
</Row>
)
}
);
};
export default PasswordModal;
export default PasswordModal;

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from 'antd';
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as PermissionBackend from "./backend/PermissionBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend";
@ -21,8 +21,9 @@ import * as Setting from "./Setting";
import i18next from "i18next";
import * as RoleBackend from "./backend/RoleBackend";
import * as ModelBackend from "./backend/ModelBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend";
const { Option } = Select;
const {Option} = Select;
class PermissionEditPage extends React.Component {
constructor(props) {
@ -36,6 +37,7 @@ class PermissionEditPage extends React.Component {
users: [],
roles: [],
models: [],
resources: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
@ -55,6 +57,7 @@ class PermissionEditPage extends React.Component {
this.getUsers(permission.owner);
this.getRoles(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) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
@ -117,17 +129,17 @@ class PermissionEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.owner} onChange={(owner => {
this.updatePermissionField('owner', owner);
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.owner} onChange={(owner => {
this.updatePermissionField("owner", owner);
this.getUsers(owner);
this.getRoles(owner);
@ -138,33 +150,33 @@ class PermissionEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.permission.name} onChange={e => {
this.updatePermissionField('name', e.target.value);
this.updatePermissionField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.permission.displayName} onChange={e => {
this.updatePermissionField('displayName', e.target.value);
this.updatePermissionField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Model"), i18next.t("general:Model - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.model} onChange={(model => {
this.updatePermissionField('model', model);
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.model} onChange={(model => {
this.updatePermissionField("model", model);
})}>
{
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>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Resource type"), i18next.t("permission:Resource type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.resourceType} onChange={(value => {
this.updatePermissionField('resourceType', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.resourceType} onChange={(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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<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"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.permission.actions} onChange={(value => {
this.updatePermissionField('actions', value);
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.actions} onChange={(value => {
this.updatePermissionField("actions", value);
})}>
{
[
{id: 'Read', name: 'Read'},
{id: 'Write', name: 'Write'},
{id: 'Admin', name: 'Admin'},
{id: "Read", name: "Read"},
{id: "Write", name: "Write"},
{id: "Admin", name: "Admin"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("permission:Effect"), i18next.t("permission:Effect - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.permission.effect} onChange={(value => {
this.updatePermissionField('effect', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.effect} onChange={(value => {
this.updatePermissionField("effect", value);
})}>
{
[
{id: 'Allow', name: 'Allow'},
{id: 'Deny', name: 'Deny'},
{id: "Allow", name: "Allow"},
{id: "Deny", name: "Deny"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.permission.isEnabled} onChange={checked => {
this.updatePermissionField('isEnabled', checked);
this.updatePermissionField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
)
);
}
submitPermissionEdit(willExist) {
@ -266,19 +290,19 @@ class PermissionEditPage extends React.Component {
PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
permissionName: this.state.permission.name,
});
if (willExist) {
this.props.history.push(`/permissions`);
this.props.history.push("/permissions");
} else {
this.props.history.push(`/permissions/${this.state.permission.owner}/${this.state.permission.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updatePermissionField('name', this.state.permissionName);
this.updatePermissionField("name", this.state.permissionName);
}
})
.catch(error => {
@ -289,7 +313,7 @@ class PermissionEditPage extends React.Component {
deletePermission() {
PermissionBackend.deletePermission(this.state.permission)
.then(() => {
this.props.history.push(`/permissions`);
this.props.history.push("/permissions");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

@ -94,7 +94,7 @@ class ProductBuyPage extends React.Component {
let providerMap = {};
this.state.providers.forEach(provider => {
providerMap[provider.name] = provider;
})
});
return product.providers.map(providerName => providerMap[providerName]);
}
@ -153,7 +153,7 @@ class ProductBuyPage extends React.Component {
text
}
</Button>
)
);
}
renderProviderButton(provider, product) {
@ -165,7 +165,7 @@ class ProductBuyPage extends React.Component {
}
</span>
</span>
)
);
}
renderPay(product) {
@ -183,7 +183,7 @@ class ProductBuyPage extends React.Component {
const providers = this.getProviders(product);
return providers.map(provider => {
return this.renderProviderButton(provider, product);
})
});
}
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%"}} >
<Descriptions title={i18next.t("product:Buy Product")} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 28}}>
{product?.displayName}
</span>
<span style={{fontSize: 28}}>
{product?.displayName}
</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:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
<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 label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{
this.getPrice(product)
}
</span>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{
this.getPrice(product)
}
</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>
@ -225,7 +225,7 @@ class ProductBuyPage extends React.Component {
</Descriptions>
</Spin>
</div>
)
);
}
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
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 Setting from "./Setting";
import i18next from "i18next";
@ -21,7 +21,7 @@ import {LinkOutlined} from "@ant-design/icons";
import * as ProviderBackend from "./backend/ProviderBackend";
import ProductBuyPage from "./ProductBuyPage";
const { Option } = Select;
const {Option} = Select;
class ProductEditPage extends React.Component {
constructor(props) {
@ -82,165 +82,165 @@ class ProductEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.name} onChange={e => {
this.updateProductField('name', e.target.value);
this.updateProductField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.displayName} onChange={e => {
this.updateProductField('displayName', e.target.value);
this.updateProductField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: '100%'} :{}}>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.product.image} onChange={e => {
this.updateProductField('image', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.product.image} onChange={e => {
this.updateProductField("image", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<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>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Tag"), i18next.t("product:Tag - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.tag} onChange={e => {
this.updateProductField('tag', e.target.value);
this.updateProductField("tag", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Detail"), i18next.t("product:Detail - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.product.detail} onChange={e => {
this.updateProductField('detail', e.target.value);
this.updateProductField("detail", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Currency"), i18next.t("product:Currency - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.product.currency} onChange={(value => {
this.updateProductField('currency', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.product.currency} onChange={(value => {
this.updateProductField("currency", value);
})}>
{
[
{id: 'USD', name: 'USD'},
{id: 'CNY', name: 'CNY'},
{id: "USD", name: "USD"},
{id: "CNY", name: "CNY"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Price"), i18next.t("product:Price - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.price} onChange={value => {
this.updateProductField('price', value);
this.updateProductField("price", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Quantity"), i18next.t("product:Quantity - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.quantity} onChange={value => {
this.updateProductField('quantity', value);
this.updateProductField("quantity", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Sold"), i18next.t("product:Sold - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.product.sold} onChange={value => {
this.updateProductField('sold', value);
this.updateProductField("sold", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Payment providers"), i18next.t("product:Payment providers - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.product.returnUrl} onChange={e => {
this.updateProductField('returnUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.product.returnUrl} onChange={e => {
this.updateProductField("returnUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.product.state} onChange={(value => {
this.updateProductField('state', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.product.state} onChange={(value => {
this.updateProductField("state", value);
})}>
{
[
{id: 'Published', name: 'Published'},
{id: 'Draft', name: 'Draft'},
{id: "Published", name: "Published"},
{id: "Draft", name: "Draft"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
{
@ -248,7 +248,7 @@ class ProductEditPage extends React.Component {
}
</Row>
</Card>
)
);
}
renderPreview() {
@ -258,13 +258,13 @@ class ProductEditPage extends React.Component {
<a style={{marginBottom: "10px", display: "flex"}} target="_blank" rel="noreferrer" href={buyUrl}>
<Button type="primary">{i18next.t("product:Test buy page..")}</Button>
</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"}}>
<ProductBuyPage product={this.state.product} />
</div>
</Col>
)
);
}
submitProductEdit(willExist) {
@ -272,19 +272,19 @@ class ProductEditPage extends React.Component {
ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
productName: this.state.product.name,
});
if (willExist) {
this.props.history.push(`/products`);
this.props.history.push("/products");
} else {
this.props.history.push(`/products/${this.state.product.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateProductField('name', this.state.productName);
this.updateProductField("name", this.state.productName);
}
})
.catch(error => {
@ -295,7 +295,7 @@ class ProductEditPage extends React.Component {
deleteProduct() {
ProductBackend.deleteProduct(this.state.product)
.then(() => {
this.props.history.push(`/products`);
this.props.history.push("/products");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

@ -13,18 +13,18 @@
// limitations under the License.
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 * as ProviderBackend from "./backend/ProviderBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import { authConfig } from "./auth/Auth";
import {authConfig} from "./auth/Auth";
import * as ProviderEditTestEmail from "./TestEmailWidget";
import copy from 'copy-to-clipboard';
import { CaptchaPreview } from "./common/CaptchaPreview";
import copy from "copy-to-clipboard";
import {CaptchaPreview} from "./common/CaptchaPreview";
const { Option } = Select;
const { TextArea } = Input;
const {Option} = Select;
const {TextArea} = Input;
class ProviderEditPage extends React.Component {
constructor(props) {
@ -70,47 +70,47 @@ class ProviderEditPage extends React.Component {
getClientIdLabel() {
switch (this.state.provider.category) {
case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} 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:
case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} 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"));
}
}
getClientSecretLabel() {
switch (this.state.provider.category) {
case "Email":
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} 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:
case "Email":
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} 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"));
}
}
@ -122,7 +122,7 @@ class ProviderEditPage extends React.Component {
} else if (this.state.provider.type === "WeCom" && this.state.provider.subType === "Internal") {
text = i18next.t("provider:Agent ID");
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");
tooltip = i18next.t("provider:Agent ID - Tooltip");
} 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 <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
return <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(text, tooltip)} :
</Col>
<Col span={22} >
<Input value={this.state.provider.appId} onChange={e => {
this.updateProviderField('appId', e.target.value);
this.updateProviderField("appId", e.target.value);
}} />
</Col>
</Row>;
@ -164,83 +164,83 @@ class ProviderEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.name} onChange={e => {
this.updateProviderField('name', e.target.value);
this.updateProviderField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.displayName} onChange={e => {
this.updateProviderField('displayName', e.target.value);
this.updateProviderField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.category} onChange={(value => {
this.updateProviderField('category', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.category} onChange={(value => {
this.updateProviderField("category", value);
if (value === "OAuth") {
this.updateProviderField('type', 'GitHub');
this.updateProviderField("type", "GitHub");
} else if (value === "Email") {
this.updateProviderField('type', 'Default');
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("type", "Default");
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.");
} else if (value === "SMS") {
this.updateProviderField('type', 'Aliyun SMS');
this.updateProviderField("type", "Aliyun SMS");
} else if (value === "Storage") {
this.updateProviderField('type', 'Local File System');
this.updateProviderField('domain', Setting.getFullServerUrl());
this.updateProviderField("type", "Local File System");
this.updateProviderField("domain", Setting.getFullServerUrl());
} else if (value === "SAML") {
this.updateProviderField('type', 'Aliyun IDaaS');
this.updateProviderField("type", "Aliyun IDaaS");
} else if (value === "Captcha") {
this.updateProviderField('type', 'Default');
this.updateProviderField("type", "Default");
}
})}>
{
[
{id: 'OAuth', name: 'OAuth'},
{id: 'Email', name: 'Email'},
{id: 'SMS', name: 'SMS'},
{id: 'Storage', name: 'Storage'},
{id: 'SAML', name: 'SAML'},
{id: 'Payment', name: 'Payment'},
{id: 'Captcha', name: 'Captcha'},
{id: "OAuth", name: "OAuth"},
{id: "Email", name: "Email"},
{id: "SMS", name: "SMS"},
{id: "Storage", name: "Storage"},
{id: "SAML", name: "SAML"},
{id: "Payment", name: "Payment"},
{id: "Captcha", name: "Captcha"},
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.type} onChange={(value => {
this.updateProviderField('type', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.type} onChange={(value => {
this.updateProviderField("type", value);
if (value === "Local File System") {
this.updateProviderField('domain', Setting.getFullServerUrl());
this.updateProviderField("domain", Setting.getFullServerUrl());
}
if (value === "Custom") {
this.updateProviderField('customAuthUrl', 'https://door.casdoor.com/login/oauth/authorize');
this.updateProviderField('customScope', 'openid profile email');
this.updateProviderField('customTokenUrl', 'https://door.casdoor.com/api/login/oauth/access_token');
this.updateProviderField('customUserInfoUrl', 'https://door.casdoor.com/api/userinfo');
this.updateProviderField("customAuthUrl", "https://door.casdoor.com/login/oauth/authorize");
this.updateProviderField("customScope", "openid profile email");
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
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 : (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Sub type"), i18next.t("provider:Sub type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.subType} onChange={value => {
this.updateProviderField('subType', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.subType} onChange={value => {
this.updateProviderField("subType", value);
}}>
{
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>
{
this.state.provider.type !== "WeCom" ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.method} onChange={value => {
this.updateProviderField('method', value);
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.method} onChange={value => {
this.updateProviderField("method", value);
}}>
{
[{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 : (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Auth URL"), i18next.t("provider:Auth URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customAuthUrl} onChange={e => {
this.updateProviderField('customAuthUrl', e.target.value);
this.updateProviderField("customAuthUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customScope} onChange={e => {
this.updateProviderField('customScope', e.target.value);
this.updateProviderField("customScope", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customTokenUrl} onChange={e => {
this.updateProviderField('customTokenUrl', e.target.value);
this.updateProviderField("customTokenUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.customUserInfoUrl} onChange={e => {
this.updateProviderField('customUserInfoUrl', e.target.value);
this.updateProviderField("customUserInfoUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
</Col>
<Col span={23} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.customLogo} onChange={e => {
this.updateProviderField('customLogo', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.provider.customLogo} onChange={e => {
this.updateProviderField("customLogo", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}:
</Col>
<Col span={23} >
<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>
</Col>
</Row>
@ -362,23 +362,23 @@ class ProviderEditPage extends React.Component {
{
this.state.provider.category === "Captcha" && this.state.provider.type === "Default" ? null : (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel()}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField('clientId', e.target.value);
this.updateProviderField("clientId", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecretLabel()}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientSecret} onChange={e => {
this.updateProviderField('clientSecret', e.target.value);
this.updateProviderField("clientSecret", e.target.value);
}} />
</Col>
</Row>
@ -388,27 +388,27 @@ class ProviderEditPage extends React.Component {
{
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" ? null : (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.state.provider.type === "Aliyun Captcha"
? 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"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId2} onChange={e => {
this.updateProviderField('clientId2', e.target.value);
this.updateProviderField("clientId2", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.state.provider.type === "Aliyun Captcha"
? 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"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientSecret2} onChange={e => {
this.updateProviderField('clientSecret2', e.target.value);
this.updateProviderField("clientSecret2", e.target.value);
}} />
</Col>
</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 : (
<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>
</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}>
<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);
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>
</Row>
{this.state.provider.type === "AWS S3" || this.state.provider.type === "Tencent Cloud COS" ? (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.regionId} onChange={e => {
this.updateProviderField('regionId', e.target.value);
this.updateProviderField("regionId", e.target.value);
}} />
</Col>
</Row>
@ -488,160 +488,160 @@ class ProviderEditPage extends React.Component {
{
this.state.provider.category === "Email" ? (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.host} onChange={e => {
this.updateProviderField('host', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => {
this.updateProviderField("host", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.provider.port} onChange={value => {
this.updateProviderField('port', value);
this.updateProviderField("port", value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email Title"), i18next.t("provider:Email Title - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.title} onChange={e => {
this.updateProviderField('title', e.target.value);
this.updateProviderField("title", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
</Col>
<Col span={22} >
<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>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
</Col>
<Col span={4} >
<Input value={this.state.testEmail}
placeHolder = {i18next.t("user:Input your email")}
onChange={e => {
this.setState({testEmail: e.target.value})
}} />
placeHolder = {i18next.t("user:Input your email")}
onChange={e => {
this.setState({testEmail: e.target.value});
}} />
</Col>
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary"
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
{i18next.t("provider:Test Connection")}
</Button>
<Button style={{marginLeft: '10px', marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidEmail(this.state.testEmail)}
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
disabled={!Setting.isValidEmail(this.state.testEmail)}
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
{i18next.t("provider:Send Test Email")}
</Button>
</Row>
</React.Fragment>
) : this.state.provider.category === "SMS" ? (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Sign Name"), i18next.t("provider:Sign Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.signName} onChange={e => {
this.updateProviderField('signName', e.target.value);
this.updateProviderField("signName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template Code"), i18next.t("provider:Template Code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField('templateCode', e.target.value);
this.updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
) : this.state.provider.category === "SAML" ? (
<React.Fragment>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Sign request"), i18next.t("provider:Sign request - Tooltip"))} :
</Col>
<Col span={22} >
<Switch checked={this.state.provider.enableSignAuthnRequest} onChange={checked => {
this.updateProviderField('enableSignAuthnRequest', checked);
this.updateProviderField("enableSignAuthnRequest", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={this.state.provider.metadata} onChange={e => {
this.updateProviderField('metadata', e.target.value);
this.updateProviderField("metadata", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}}>
<Col style={{marginTop: '5px'}} span={2}></Col>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={2}></Col>
<Col span={2}>
<Button type="primary" onClick={() => {
try {
this.loadSamlConfiguration();
Setting.showMessage("success", i18next.t("provider:Parse Metadata successfully"));
} catch (err) {
Setting.showMessage("error", i18next.t("provider:Can not parse Metadata"));
}
}}>
try {
this.loadSamlConfiguration();
Setting.showMessage("success", i18next.t("provider:Parse Metadata successfully"));
} catch (err) {
Setting.showMessage("error", i18next.t("provider:Can not parse Metadata"));
}
}}>
{i18next.t("provider:Parse")}
</Button>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:SAML 2.0 Endpoint (HTTP)"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField('endpoint', e.target.value);
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP public key"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.idP} onChange={e => {
this.updateProviderField('idP', e.target.value);
this.updateProviderField("idP", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Issuer URL"), i18next.t("provider:Issuer URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.issuerUrl} onChange={e => {
this.updateProviderField('issuerUrl', e.target.value);
this.updateProviderField("issuerUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SP ACS URL"), i18next.t("provider:SP ACS URL - Tooltip"))} :
</Col>
<Col span={21} >
@ -656,8 +656,8 @@ class ProviderEditPage extends React.Component {
</Button>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SP Entity ID"), i18next.t("provider:SP ACS URL - Tooltip"))} :
</Col>
<Col span={21} >
@ -676,20 +676,20 @@ class ProviderEditPage extends React.Component {
) : null
}
{this.getAppIdRow()}
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Provider URL"), i18next.t("provider:Provider URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.provider.providerUrl} onChange={e => {
this.updateProviderField('providerUrl', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.provider.providerUrl} onChange={e => {
this.updateProviderField("providerUrl", e.target.value);
}} />
</Col>
</Row>
{
this.state.provider.category !== "Captcha" ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
<Col span={22} >
@ -711,7 +711,7 @@ class ProviderEditPage extends React.Component {
)
}
</Card>
)
);
}
submitProviderEdit(willExist) {
@ -719,19 +719,19 @@ class ProviderEditPage extends React.Component {
ProviderBackend.updateProvider(this.state.provider.owner, this.state.providerName, provider)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
providerName: this.state.provider.name,
});
if (willExist) {
this.props.history.push(`/providers`);
this.props.history.push("/providers");
} else {
this.props.history.push(`/providers/${this.state.provider.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateProviderField('name', this.state.providerName);
this.updateProviderField("name", this.state.providerName);
}
})
.catch(error => {
@ -742,7 +742,7 @@ class ProviderEditPage extends React.Component {
deleteProvider() {
ProviderBackend.deleteProvider(this.state.provider)
.then(() => {
this.props.history.push(`/providers`);
this.props.history.push("/providers");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

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

View File

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

View File

@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// 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 React from "react";
import * as Setting from "./Setting"
import * as UserBackend from "./backend/UserBackend"
import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend";
import {CountDownInput} from "./common/CountDownInput";
import {MailOutlined, PhoneOutlined} from "@ant-design/icons";
@ -25,7 +25,7 @@ export const ResetModal = (props) => {
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [dest, setDest] = React.useState("");
const [code, setCode] = React.useState("");
const {buttonText, destType, org} = props;
const {buttonText, destType, application} = props;
const showModal = () => {
setVisible(true);
@ -53,12 +53,11 @@ export const ResetModal = (props) => {
Setting.showMessage("error", i18next.t("user:" + res.msg));
setConfirmLoading(false);
}
})
}
});
};
let placeHolder = "";
if (destType === "email") placeHolder = i18next.t("user:Input your email");
else if (destType === "phone") placeHolder = i18next.t("user:Input your phone number");
if (destType === "email") {placeHolder = i18next.t("user:Input your email");} else if (destType === "phone") {placeHolder = i18next.t("user:Input your phone number");}
return (
<Row>
@ -89,13 +88,13 @@ export const ResetModal = (props) => {
<CountDownInput
textBefore={i18next.t("code:Code You Received")}
onChange={setCode}
onButtonClickArgs={[dest, destType, `${org?.owner}/${org?.name}`]}
onButtonClickArgs={[dest, destType, Setting.getApplicationName(application)]}
/>
</Row>
</Col>
</Modal>
</Row>
)
}
);
};
export default ResetModal;

View File

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

View File

@ -13,14 +13,14 @@
// limitations under the License.
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 OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
const { Option } = Select;
const {Option} = Select;
class RoleEditPage extends React.Component {
constructor(props) {
@ -104,78 +104,78 @@ class RoleEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.role.name} onChange={e => {
this.updateRoleField('name', e.target.value);
this.updateRoleField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.role.displayName} onChange={e => {
this.updateRoleField('displayName', e.target.value);
this.updateRoleField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.role.isEnabled} onChange={checked => {
this.updateRoleField('isEnabled', checked);
this.updateRoleField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
)
);
}
submitRoleEdit(willExist) {
@ -183,19 +183,19 @@ class RoleEditPage extends React.Component {
RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
roleName: this.state.role.name,
});
if (willExist) {
this.props.history.push(`/roles`);
this.props.history.push("/roles");
} else {
this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateRoleField('name', this.state.roleName);
this.updateRoleField("name", this.state.roleName);
}
})
.catch(error => {
@ -206,7 +206,7 @@ class RoleEditPage extends React.Component {
deleteRole() {
RoleBackend.deleteRole(this.state.role)
.then(() => {
this.props.history.push(`/roles`);
this.props.history.push("/roles");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

@ -14,12 +14,12 @@
import React from "react";
import * as Setting from "./Setting";
import { Menu, Dropdown} from "antd";
import { createFromIconfontCN } from '@ant-design/icons';
import './App.less';
import {Menu, Dropdown} from "antd";
import {createFromIconfontCN} from "@ant-design/icons";
import "./App.less";
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,12 +13,12 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons';
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from 'antd';
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
const { Option } = Select;
const {Option} = Select;
class SyncerTableColumnTable extends React.Component {
constructor(props) {
@ -65,64 +65,64 @@ class SyncerTableColumnTable extends React.Component {
const columns = [
{
title: i18next.t("syncer:Column name"),
dataIndex: 'name',
key: 'name',
dataIndex: "name",
key: "name",
render: (text, record, index) => {
return (
<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"),
dataIndex: 'type',
key: 'type',
dataIndex: "type",
key: "type",
render: (text, record, index) => {
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>)
}
</Select>
)
);
}
},
{
title: i18next.t("syncer:Casdoor column"),
dataIndex: 'casdoorName',
key: 'casdoorName',
dataIndex: "casdoorName",
key: "casdoorName",
render: (text, record, index) => {
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',
'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']
["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",
"Language", "Gender", "Birthday", "Education", "Score", "Ranking", "IsDefaultAvatar", "IsOnline", "IsAdmin", "IsGlobalAdmin", "IsForbidden", "IsDeleted", "CreatedIp"]
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
)
);
}
},
{
title: i18next.t("syncer:Is hashed"),
dataIndex: 'isHashed',
key: 'isHashed',
dataIndex: "isHashed",
key: "isHashed",
render: (text, record, index) => {
return (
<Switch checked={text} onChange={checked => {
this.updateField(table, index, 'isHashed', checked);
this.updateField(table, index, "isHashed", checked);
}} />
)
);
}
},
{
title: i18next.t("general:Action"),
key: 'action',
width: '100px',
key: "action",
width: "100px",
render: (text, record, index) => {
return (
<div>
@ -143,12 +143,12 @@ class SyncerTableColumnTable extends React.Component {
return (
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>
);
}
@ -156,7 +156,7 @@ class SyncerTableColumnTable extends React.Component {
render() {
return (
<div>
<Row style={{marginTop: '20px'}} >
<Row style={{marginTop: "20px"}} >
<Col span={24}>
{
this.renderTable(this.props.table)
@ -164,7 +164,7 @@ class SyncerTableColumnTable extends React.Component {
</Col>
</Row>
</div>
)
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -13,8 +13,9 @@
// limitations under the License.
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 UserWebauthnBackend from "./backend/UserWebauthnBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import {LinkOutlined} from "@ant-design/icons";
@ -27,13 +28,14 @@ import AffiliationSelect from "./common/AffiliationSelect";
import OAuthWidget from "./common/OAuthWidget";
import SamlWidget from "./common/SamlWidget";
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";
require('codemirror/theme/material-darker.css');
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const { Option } = Select;
const {Option} = Select;
class UserEditPage extends React.Component {
constructor(props) {
@ -165,156 +167,156 @@ class UserEditPage extends React.Component {
if (accountItem.name === "Organization") {
return (
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
)
);
} else if (accountItem.name === "ID") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("ID", i18next.t("general:ID - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.id} disabled={disabled} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Name") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.name} disabled={disabled} onChange={e => {
this.updateUserField('name', e.target.value);
this.updateUserField("name", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Display name") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.displayName} onChange={e => {
this.updateUserField('displayName', e.target.value);
this.updateUserField("displayName", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Avatar") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:URL")}:
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.user.avatar} onChange={e => {
this.updateUserField('avatar', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.user.avatar} onChange={e => {
this.updateUserField("avatar", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Preview")}:
</Col>
<Col span={22} >
<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>
</Col>
</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} />
</Row>
</Col>
</Row>
)
);
} else if (accountItem.name === "User type") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:User type"), i18next.t("general:User type - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
)
);
} else if (accountItem.name === "Password") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<PasswordModal user={this.state.user} account={this.props.account} disabled={disabled} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Email") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col>
<Col style={{paddingRight: '20px'}} span={11} >
<Col style={{paddingRight: "20px"}} span={11} >
<Input value={this.state.user.email}
disabled={disabled}
onChange={e => {
this.updateUserField('email', e.target.value);
}} />
disabled={disabled}
onChange={e => {
this.updateUserField("email", e.target.value);
}} />
</Col>
<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>
</Row>
)
);
} else if (accountItem.name === "Phone") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</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}`}
disabled={disabled}
onChange={e => {
this.updateUserField('phone', e.target.value);
}}/>
disabled={disabled}
onChange={e => {
this.updateUserField("phone", e.target.value);
}} />
</Col>
<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>
</Row>
)
);
} else if (accountItem.name === "Country/Region") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
</Col>
<Col span={22} >
@ -323,113 +325,113 @@ class UserEditPage extends React.Component {
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Location") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.location} onChange={e => {
this.updateUserField('location', e.target.value);
this.updateUserField("location", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Affiliation") {
return (
(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") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.title} onChange={e => {
this.updateUserField('title', e.target.value);
this.updateUserField("title", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Homepage") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.homepage} onChange={e => {
this.updateUserField('homepage', e.target.value);
this.updateUserField("homepage", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Bio") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.bio} onChange={e => {
this.updateUserField('bio', e.target.value);
this.updateUserField("bio", e.target.value);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Tag") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
</Col>
<Col span={22} >
{
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) => {
const tokens = tag.split("|");
const value = tokens[0];
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>
) : (
<Input value={this.state.user.tag} onChange={e => {
this.updateUserField('tag', e.target.value);
this.updateUserField("tag", e.target.value);
}} />
)
}
</Col>
</Row>
)
);
} else if (accountItem.name === "Signup application") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Signup application"), i18next.t("general:Signup application - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
)
);
} else if (accountItem.name === "3rd-party logins") {
return (
!this.isSelfOrAdmin() ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:3rd-party logins"), i18next.t("user:3rd-party logins - Tooltip"))} :
</Col>
<Col span={22} >
@ -438,9 +440,9 @@ class UserEditPage extends React.Component {
(this.state.application === null || this.state.user === null) ? null : (
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
(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>
</Row>
)
)
);
} else if (accountItem.name === "Properties") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("user:Properties")}:
</Col>
<Col span={22} >
<CodeMirror
value={JSON.stringify(this.state.user.properties, null, 4)}
options={{mode: 'javascript', theme: "material-darker"}}
options={{mode: "javascript", theme: "material-darker"}}
/>
</Col>
</Row>
)
);
} else if (accountItem.name === "Is admin") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
</Col>
<Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isAdmin} onChange={checked => {
this.updateUserField('isAdmin', checked);
this.updateUserField("isAdmin", checked);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Is global admin") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
</Col>
<Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isGlobalAdmin} onChange={checked => {
this.updateUserField('isGlobalAdmin', checked);
this.updateUserField("isGlobalAdmin", checked);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Is forbidden") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is forbidden"), i18next.t("user:Is forbidden - Tooltip"))} :
</Col>
<Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isForbidden} onChange={checked => {
this.updateUserField('isForbidden', checked);
this.updateUserField("isForbidden", checked);
}} />
</Col>
</Row>
)
);
} else if (accountItem.name === "Is deleted") {
return (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} :
</Col>
<Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch checked={this.state.user.isDeleted} onChange={checked => {
this.updateUserField('isDeleted', checked);
this.updateUserField("isDeleted", checked);
}} />
</Col>
</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>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
{
this.state.application?.organizationObj.accountItems?.map(accountItem => {
return (
@ -537,11 +550,11 @@ class UserEditPage extends React.Component {
this.renderAccountItem(accountItem)
}
</React.Fragment>
)
);
})
}
</Card>
)
);
}
submitUserEdit(willExist) {
@ -549,7 +562,7 @@ class UserEditPage extends React.Component {
UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
organizationName: this.state.user.owner,
userName: this.state.user.name,
@ -557,15 +570,15 @@ class UserEditPage extends React.Component {
if (this.props.history !== undefined) {
if (willExist) {
this.props.history.push(`/users`);
this.props.history.push("/users");
} else {
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
}
}
} else {
Setting.showMessage("error", res.msg);
this.updateUserField('owner', this.state.organizationName);
this.updateUserField('name', this.state.userName);
this.updateUserField("owner", this.state.organizationName);
this.updateUserField("name", this.state.userName);
}
})
.catch(error => {
@ -576,7 +589,7 @@ class UserEditPage extends React.Component {
deleteUser() {
UserBackend.deleteUser(this.state.user)
.then(() => {
this.props.history.push(`/users`);
this.props.history.push("/users");
})
.catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`);
@ -586,26 +599,26 @@ class UserEditPage extends React.Component {
render() {
return (
<div>
{
this.state.loading ? <Spin size="large" /> : (
this.state.user !== null ? this.renderUser() :
<Result
status="404"
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.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
)
}
{
this.state.user === null ? null :
<div style={{marginTop: '20px', marginLeft: '40px'}}>
<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>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
}
</div>
{
this.state.loading ? <Spin size="large" /> : (
this.state.user !== null ? this.renderUser() :
<Result
status="404"
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.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
)
}
{
this.state.user === null ? null :
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<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>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
}
</div>
);
}
}

View File

@ -14,7 +14,7 @@
import React from "react";
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 moment from "moment";
import * as Setting from "./Setting";
@ -34,8 +34,8 @@ class UserListPage extends BaseListPage {
pageSize: 10,
},
loading: false,
searchText: '',
searchedColumn: '',
searchText: "",
searchedColumn: "",
};
}
@ -62,15 +62,15 @@ class UserListPage extends BaseListPage {
isDeleted: false,
properties: {},
signupApplication: "app-built-in",
}
};
}
addUser() {
const newUser = this.newUser();
UserBackend.addUser(newUser)
.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 => {
Setting.showMessage("error", `User failed to add: ${error}`);
@ -80,12 +80,12 @@ class UserListPage extends BaseListPage {
deleteUser(i) {
UserBackend.deleteUser(this.state.data[i])
.then((res) => {
Setting.showMessage("success", `User deleted successfully`);
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
Setting.showMessage("success", "User deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
.catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`);
@ -93,26 +93,26 @@ class UserListPage extends BaseListPage {
}
uploadFile(info) {
const { status, response: res } = info.file;
if (status === 'done') {
if (res.status === 'ok') {
Setting.showMessage("success", `Users uploaded successfully, refreshing the page`);
const {status, response: res} = info.file;
if (status === "done") {
if (res.status === "ok") {
Setting.showMessage("success", "Users uploaded successfully, refreshing the page");
const { pagination } = this.state;
this.fetch({ pagination });
const {pagination} = this.state;
this.fetch({pagination});
} else {
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
}
} else if (status === 'error') {
Setting.showMessage("error", `File failed to upload`);
} else if (status === "error") {
Setting.showMessage("error", "File failed to upload");
}
}
renderUpload() {
const props = {
name: 'file',
accept: '.xlsx',
method: 'post',
name: "file",
accept: ".xlsx",
method: "post",
action: `${Setting.ServerUrl}/api/upload-users`,
withCredentials: true,
onChange: (info) => {
@ -126,7 +126,7 @@ class UserListPage extends BaseListPage {
<UploadOutlined /> {i18next.t("user:Upload (.xlsx)")}
</Button>
</Upload>
)
);
}
renderTable(users) {
@ -134,63 +134,63 @@ class UserListPage extends BaseListPage {
var countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json"));
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 = [
{
title: i18next.t("general:Organization"),
dataIndex: 'owner',
key: 'owner',
dataIndex: "owner",
key: "owner",
width: (Setting.isMobile()) ? "100px" : "120px",
fixed: 'left',
fixed: "left",
sorter: true,
...this.getColumnSearchProps('owner'),
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
)
);
}
},
{
title: i18next.t("general:Application"),
dataIndex: 'signupApplication',
key: 'signupApplication',
dataIndex: "signupApplication",
key: "signupApplication",
width: (Setting.isMobile()) ? "100px" : "120px",
fixed: 'left',
fixed: "left",
sorter: true,
...this.getColumnSearchProps('signupApplication'),
...this.getColumnSearchProps("signupApplication"),
render: (text, record, index) => {
return (
<Link to={`/applications/${text}`}>
{text}
</Link>
)
);
}
},
{
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
dataIndex: "name",
key: "name",
width: (Setting.isMobile()) ? "80px" : "110px",
fixed: 'left',
fixed: "left",
sorter: true,
...this.getColumnSearchProps('name'),
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${text}`}>
{text}
</Link>
)
);
}
},
{
title: i18next.t("general:Created time"),
dataIndex: 'createdTime',
key: 'createdTime',
width: '160px',
dataIndex: "createdTime",
key: "createdTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
@ -198,47 +198,47 @@ class UserListPage extends BaseListPage {
},
{
title: i18next.t("general:Display name"),
dataIndex: 'displayName',
key: 'displayName',
dataIndex: "displayName",
key: "displayName",
// width: '100px',
sorter: true,
...this.getColumnSearchProps('displayName'),
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("general:Avatar"),
dataIndex: 'avatar',
key: 'avatar',
width: '80px',
dataIndex: "avatar",
key: "avatar",
width: "80px",
render: (text, record, index) => {
return (
<a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={50} />
</a>
)
);
}
},
{
title: i18next.t("general:Email"),
dataIndex: 'email',
key: 'email',
width: '160px',
dataIndex: "email",
key: "email",
width: "160px",
sorter: true,
...this.getColumnSearchProps('email'),
...this.getColumnSearchProps("email"),
render: (text, record, index) => {
return (
<a href={`mailto:${text}`}>
{text}
</a>
)
);
}
},
{
title: i18next.t("general:Phone"),
dataIndex: 'phone',
key: 'phone',
width: '120px',
dataIndex: "phone",
key: "phone",
width: "120px",
sorter: true,
...this.getColumnSearchProps('phone'),
...this.getColumnSearchProps("phone"),
},
// {
// title: 'Phone',
@ -249,94 +249,94 @@ class UserListPage extends BaseListPage {
// },
{
title: i18next.t("user:Affiliation"),
dataIndex: 'affiliation',
key: 'affiliation',
width: '140px',
dataIndex: "affiliation",
key: "affiliation",
width: "140px",
sorter: true,
...this.getColumnSearchProps('affiliation'),
...this.getColumnSearchProps("affiliation"),
},
{
title: i18next.t("user:Country/Region"),
dataIndex: 'region',
key: 'region',
width: '140px',
dataIndex: "region",
key: "region",
width: "140px",
sorter: true,
...this.getColumnSearchProps('region'),
...this.getColumnSearchProps("region"),
},
{
title: i18next.t("user:Tag"),
dataIndex: 'tag',
key: 'tag',
width: '110px',
dataIndex: "tag",
key: "tag",
width: "110px",
sorter: true,
...this.getColumnSearchProps('tag'),
...this.getColumnSearchProps("tag"),
},
{
title: i18next.t("user:Is admin"),
dataIndex: 'isAdmin',
key: 'isAdmin',
width: '110px',
dataIndex: "isAdmin",
key: "isAdmin",
width: "110px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
)
);
}
},
{
title: i18next.t("user:Is global admin"),
dataIndex: 'isGlobalAdmin',
key: 'isGlobalAdmin',
width: '140px',
dataIndex: "isGlobalAdmin",
key: "isGlobalAdmin",
width: "140px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
)
);
}
},
{
title: i18next.t("user:Is forbidden"),
dataIndex: 'isForbidden',
key: 'isForbidden',
width: '110px',
dataIndex: "isForbidden",
key: "isForbidden",
width: "110px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
)
);
}
},
{
title: i18next.t("user:Is deleted"),
dataIndex: 'isDeleted',
key: 'isDeleted',
width: '110px',
dataIndex: "isDeleted",
key: "isDeleted",
width: "110px",
sorter: true,
render: (text, record, index) => {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
)
);
}
},
{
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '190px',
dataIndex: "",
key: "op",
width: "190px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<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
title={`Sure to delete user: ${record.name} ?`}
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>
</div>
)
);
}
},
];
@ -350,18 +350,18 @@ class UserListPage extends BaseListPage {
return (
<div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{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>
{
this.renderUpload()
}
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{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>
{
this.renderUpload()
}
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
@ -370,7 +370,7 @@ class UserListPage extends BaseListPage {
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
let sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({ loading: true });
this.setState({loading: true});
if (this.state.organizationName === undefined) {
UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.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.
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 * as WebhookBackend from "./backend/WebhookBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
@ -21,12 +21,12 @@ import * as Setting from "./Setting";
import i18next from "i18next";
import WebhookHeaderTable from "./WebhookHeaderTable";
import {Controlled as CodeMirror} from 'react-codemirror2';
import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css";
require('codemirror/theme/material-darker.css');
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const { Option } = Select;
const {Option} = Select;
const previewTemplate = {
"id": 9078,
@ -152,144 +152,144 @@ class WebhookEditPage extends React.Component {
<div>
{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 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}
<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}
</div>
} style={(Setting.isMobile())? {margin: '5px'}:{}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<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>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.webhook.name} onChange={e => {
this.updateWebhookField('name', e.target.value);
this.updateWebhookField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:URL"), i18next.t("webhook:URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.webhook.url} onChange={e => {
this.updateWebhookField('url', e.target.value);
<Input prefix={<LinkOutlined />} value={this.state.webhook.url} onChange={e => {
this.updateWebhookField("url", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Method"), i18next.t("webhook:Method - Tooltip"))} :
</Col>
<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: 'GET', name: 'GET'},
{id: 'PUT', name: 'PUT'},
{id: 'DELETE', name: 'DELETE'},
{id: "POST", name: "POST"},
{id: "GET", name: "GET"},
{id: "PUT", name: "PUT"},
{id: "DELETE", name: "DELETE"},
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Content type"), i18next.t("webhook:Content type - Tooltip"))} :
</Col>
<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/x-www-form-urlencoded', name: 'application/x-www-form-urlencoded'},
{id: "application/json", name: "application/json"},
{id: "application/x-www-form-urlencoded", name: "application/x-www-form-urlencoded"},
].map((contentType, index) => <Option key={index} value={contentType.id}>{contentType.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Headers"), i18next.t("webhook:Headers - Tooltip"))} :
</Col>
<Col span={22} >
<WebhookHeaderTable
title={i18next.t("webhook:Headers")}
table={this.state.webhook.headers}
onUpdateTable={(value) => { this.updateWebhookField('headers', value)}}
onUpdateTable={(value) => {this.updateWebhookField("headers", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("webhook:Events"), i18next.t("webhook:Events - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}}
value={this.state.webhook.events}
onChange={value => {
this.updateWebhookField('events', value);
}} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.webhook.events}
onChange={value => {
this.updateWebhookField("events", value);
}} >
{
(
["signup", "login", "logout", "update-user"].map((option, index) => {
return (
<Option key={option} value={option}>{option}</Option>
)
);
})
)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.webhook.isUserExtended} onChange={checked => {
this.updateWebhookField('isUserExtended', checked);
this.updateWebhookField("isUserExtended", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Preview"), i18next.t("general:Preview - Tooltip"))} :
</Col>
<Col span={22} >
<div style={{width: "900px", height: "300px"}} >
<CodeMirror
value={previewText}
options={{mode: 'javascript', theme: "material-darker"}}
options={{mode: "javascript", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {}}
/>
</div>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.webhook.isEnabled} onChange={checked => {
this.updateWebhookField('isEnabled', checked);
this.updateWebhookField("isEnabled", checked);
}} />
</Col>
</Row>
</Card>
)
);
}
submitWebhookEdit(willExist) {
@ -297,19 +297,19 @@ class WebhookEditPage extends React.Component {
WebhookBackend.updateWebhook(this.state.webhook.owner, this.state.webhookName, webhook)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", `Successfully saved`);
Setting.showMessage("success", "Successfully saved");
this.setState({
webhookName: this.state.webhook.name,
});
if (willExist) {
this.props.history.push(`/webhooks`);
this.props.history.push("/webhooks");
} else {
this.props.history.push(`/webhooks/${this.state.webhook.name}`);
}
} else {
Setting.showMessage("error", res.msg);
this.updateWebhookField('name', this.state.webhookName);
this.updateWebhookField("name", this.state.webhookName);
}
})
.catch(error => {
@ -320,7 +320,7 @@ class WebhookEditPage extends React.Component {
deleteWebhook() {
WebhookBackend.deleteWebhook(this.state.webhook)
.then(() => {
this.props.history.push(`/webhooks`);
this.props.history.push("/webhooks");
})
.catch(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
}
<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 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}
<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}
</div>
</div>
);

View File

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

View File

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

View File

@ -19,7 +19,7 @@ class AccountPage extends React.Component {
render() {
return (
<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 {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/adfs.svg`} alt="Sign in with ADFS" style={{width: 24, height: 24}} />;
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/adfs.svg`} alt="Sign in with ADFS" style={{width: 24, height: 24}} />;
}
const config = {
text: "Sign in with ADFS",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
text: "Sign in with ADFS",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const AdfsLoginButton = createButton(config);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,16 +15,16 @@
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/bilibili.svg`} alt="Sign in with Bilibili"/>;
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/bilibili.svg`} alt="Sign in with Bilibili" />;
}
const config = {
text: "Sign in with Bilibili",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"},
text: "Sign in with Bilibili",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"},
};
const BilibiliLoginButton = createButton(config);

View File

@ -27,8 +27,8 @@ class CasLogout extends React.Component {
msg: null,
};
if (props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner
this.state.applicationName = props.match?.params.casApplicationName
this.state.owner = props.match?.params.owner;
this.state.applicationName = props.match?.params.casApplicationName;
}
}
@ -37,14 +37,14 @@ class CasLogout extends React.Component {
AuthBackend.logout()
.then((res) => {
if (res.status === 'ok') {
Setting.showMessage("success", `Logged out successfully`);
this.props.clearAccount()
if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully");
this.props.clearAccount();
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
} else if (params.has("service")) {
Setting.goToLink(params.get("service"))
Setting.goToLink(params.get("service"));
} else {
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%"}} />
}
</div>
)
);
}
}
export default withRouter(CasLogout);

View File

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

View File

@ -15,16 +15,16 @@
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/dingtalk.svg`} alt="Sign in with DingTalk"/>;
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/dingtalk.svg`} alt="Sign in with DingTalk" />;
}
const config = {
text: "Sign in with DingTalk",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"},
text: "Sign in with DingTalk",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#0191e0"},
activeStyle: {background: "rgb(76,143,208)"},
};
const DingTalkLoginButton = createButton(config);

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