Compare commits

...

49 Commits

Author SHA1 Message Date
4ea482223d feat: add geetest captcha (#953) 2022-08-04 20:55:04 +08:00
d55ae7d1d2 Enable some other DBs 2022-08-04 20:28:09 +08:00
d72e00605f fix: updateProviderField when add provider payment (#952) 2022-08-04 19:39:25 +08:00
be74cb621f feat: Support sub-directory (#943)
By adding PUBLIC_URL to relative `.`

Signed-off-by: zzjin <tczzjin@gmail.com>
2022-08-02 00:21:15 +08:00
13404d6035 feat: fix binding after registration causes the page to crash (#945) 2022-08-01 21:08:10 +08:00
afa9c530ad fix: panic triggered when user is nil (#940) 2022-07-31 23:23:36 +08:00
1600615aca Support sqlite3 DB 2022-07-31 18:11:18 +08:00
2bb8491499 fix: unable to get user if profile is private (#936) 2022-07-31 10:54:41 +08:00
293283ed25 feat: add get user by phone (#934)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

* feat: add get user by phone

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-31 01:02:28 +08:00
9cb519d1e9 fix: Admins should not be allowed to add third-party login for their members (#932)
* feat: admin can unlink the other user

* feat: global admin can unlink other user

* fix
2022-07-30 23:11:02 +08:00
fb9b8f1662 fix: skip the duplicated users when sync users (#928)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-30 22:24:23 +08:00
2fec3f72ae fix: check reset phone & email modify rules (#927)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

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

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name

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

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

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

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

* Update App.js

* Update Setting.js

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

* fix

* fix

* fix

* Update BaseListPage.js

* Update OrganizationListPage.js

* Update OrganizationListPage.js

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

* Update user.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-14 21:46:13 +08:00
10449e89ab Fix owner bug in GetUser(). 2022-07-13 22:56:35 +08:00
6e70f0fc58 Refactor CheckAccessPermission(). 2022-07-13 00:50:32 +08:00
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
186 changed files with 19134 additions and 17073 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

@ -78,6 +78,7 @@ p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, *, /api/login/oauth, *, *
@ -92,6 +93,7 @@ p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/notify-payment, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
@ -105,6 +107,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)

82
captcha/geetest.go Normal file
View File

@ -0,0 +1,82 @@
// 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 captcha
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/casdoor/casdoor/util"
)
const GEETESTCaptchaVerifyUrl = "http://gcaptcha4.geetest.com/validate"
type GEETESTCaptchaProvider struct {
}
func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
captcha := &GEETESTCaptchaProvider{}
return captcha
}
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err
}
signToken := util.GetHmacSha256(clientSecret, pathData["lot_number"][0])
formData := make(url.Values)
formData["lot_number"] = []string{pathData["lot_number"][0]}
formData["captcha_output"] = []string{pathData["captcha_output"][0]}
formData["pass_token"] = []string{pathData["pass_token"][0]}
formData["gen_time"] = []string{pathData["gen_time"][0]}
formData["sign_token"] = []string{signToken}
captchaId := pathData["captcha_id"][0]
cli := http.Client{Timeout: time.Second * 5}
resp, err := cli.PostForm(fmt.Sprintf("%s?captcha_id=%s", GEETESTCaptchaVerifyUrl, captchaId), formData)
if err != nil || resp.StatusCode != 200 {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Result string `json:"result"`
Reason string `json:"reason"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if captchaResp.Result == "success" {
return true, nil
}
return false, errors.New(captchaResp.Reason)
}

View File

@ -27,6 +27,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
return NewHCaptchaProvider()
} else if captchaType == "Aliyun Captcha" {
return NewAliyunCaptchaProvider()
} else if captchaType == "GEETEST" {
return NewGEETESTCaptchaProvider()
}
return nil
}

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

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

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.CheckAccessPermission(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

@ -21,7 +21,8 @@ import (
)
type LinkForm struct {
ProviderType string `json:"providerType"`
ProviderType string `json:"providerType"`
User object.User `json:"user"`
}
// Unlink ...
@ -40,16 +41,55 @@ func (c *ApiController) Unlink() {
}
providerType := form.ProviderType
// the user will be unlinked from the provider
unlinkedUser := form.User
user := object.GetUser(userId)
value := object.GetUserField(user, providerType)
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
c.ResponseError("You are not the global admin, you can't unlink other users")
return
}
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
application := object.GetApplicationByUser(user)
if application == nil {
c.ResponseError("You can't unlink yourself, you are not a member of any application")
return
}
if len(application.Providers) == 0 {
c.ResponseError("This application has no providers")
return
}
provider := application.GetProviderItemByType(providerType)
if provider == nil {
c.ResponseError("This application has no providers of type " + providerType)
return
}
if !provider.CanUnlink {
c.ResponseError("This provider can't be unlinked")
return
}
}
// only two situations can happen here
// 1. the user is the global admin
// 2. the user is unlinking themselves and provider can be unlinked
value := object.GetUserField(&unlinkedUser, providerType)
if value == "" {
c.ResponseError("Please link first", value)
return
}
object.ClearUserOAuthProperties(user, providerType)
object.ClearUserOAuthProperties(&unlinkedUser, providerType)
object.LinkUserAccount(user, providerType, "")
object.LinkUserAccount(&unlinkedUser, providerType, "")
c.ResponseOk()
}

View File

@ -80,19 +80,27 @@ func (c *ApiController) GetUsers() {
// @Title GetUser
// @Tag User API
// @Description get user
// @Param id query string true "The id of the user"
// @Param id query string true "The id of the user"
// @Param owner query string false "The owner of the user"
// @Param email query string false "The email of the user"
// @Param phone query string false "The phone of the user"
// @Success 200 {object} object.User The Response object
// @router /get-user [get]
func (c *ApiController) GetUser() {
id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
phone := c.Input().Get("phone")
userId := c.Input().Get("userId")
owner := c.Input().Get("owner")
if owner == "" {
owner, _ = util.GetOwnerAndNameFromId(id)
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false)
if !hasPermission {
c.ResponseError(err.Error())
return
@ -100,10 +108,22 @@ func (c *ApiController) GetUser() {
}
var user *object.User
if email == "" {
user = object.GetUser(id)
} else {
switch {
case email != "":
user = object.GetUserByEmail(owner, email)
case phone != "":
user = object.GetUserByPhone(owner, phone)
case userId != "":
user = object.GetUserByUserId(owner, userId)
default:
user = object.GetUser(id)
}
if user != nil {
roles := object.GetRolesByUser(user.GetId())
user.Roles = roles
permissions := object.GetPermissionsByUser(user.GetId())
user.Permissions = permissions
}
c.Data["json"] = object.GetMaskedUser(user)
@ -246,7 +266,7 @@ func (c *ApiController) SetPassword() {
requestUserId := c.GetSessionUsername()
userId := fmt.Sprintf("%s/%s", userOwner, userName)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true)
if !hasPermission {
c.ResponseError(err.Error())
return

View File

@ -42,15 +42,31 @@ 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 {
c.ResponseError("Missing parameter.")
if destType == "" {
c.ResponseError("Missing parameter: type.")
return
}
if dest == "" {
c.ResponseError("Missing parameter: dest.")
return
}
if applicationId == "" {
c.ResponseError("Missing parameter: applicationId.")
return
}
if !strings.Contains(applicationId, "/") {
c.ResponseError("Wrong parameter: applicationId.")
return
}
if checkType == "" {
c.ResponseError("Missing parameter: checkType.")
return
}
@ -74,8 +90,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 +101,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 +124,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)
}
@ -153,13 +168,35 @@ func (c *ApiController) ResetEmailOrPhone() {
}
checkDest := dest
org := object.GetOrganizationByUser(user)
if destType == "phone" {
org := object.GetOrganizationByUser(user)
phoneItem := object.GetAccountItemByName("Phone", org)
if phoneItem == nil {
c.ResponseError("Unable to get the phone modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user); !pass {
c.ResponseError(errMsg)
return
}
phonePrefix := "86"
if org != nil && org.PhonePrefix != "" {
phonePrefix = org.PhonePrefix
}
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" {
emailItem := object.GetAccountItemByName("Email", org)
if emailItem == nil {
c.ResponseError("Unable to get the email modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user); !pass {
c.ResponseError(errMsg)
return
}
}
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
c.ResponseError(ret)

138
controllers/webauthn.go Normal file
View File

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

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

@ -14,6 +14,8 @@ require (
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/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
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
@ -22,6 +24,7 @@ require (
github.com/google/uuid v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0
github.com/lib/pq v1.8.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1

16
go.sum
View File

@ -111,6 +111,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/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=
@ -123,7 +125,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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 h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
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 +140,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,8 +171,10 @@ 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 h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -201,6 +210,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 +309,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 +410,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 +428,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=

View File

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

View File

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

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,
"certificate": "",
"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

@ -21,9 +21,10 @@ import (
"github.com/astaxie/beego"
"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/lib/pq" // db = postgres
_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
_ "github.com/lib/pq" // db = postgres
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
"xorm.io/core"
"xorm.io/xorm"
)
@ -36,11 +37,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 +204,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

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

View File

@ -197,14 +197,18 @@ func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
}
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
if userId != "" {
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
userOwner = targetUser.Owner
}
hasPermission := false
@ -219,7 +223,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner {
} else if userOwner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
@ -229,4 +233,30 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
}
}
func CheckAccessPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled || len(permission.Users) == 0 {
continue
}
isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
}
}
if isHit {
enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read")
break
}
}
return allowed, err
}

View File

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

148
object/init_data.go Normal file
View File

@ -0,0 +1,148 @@
// 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: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "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

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

View File

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

View File

@ -16,7 +16,12 @@ package object
import (
"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,95 @@ 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)
}
}
func GetPermissionsByUser(userId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}

View File

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

View File

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

View File

@ -33,6 +33,15 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
return nil
}
func (application *Application) GetProviderItemByType(providerType string) *ProviderItem {
for _, item := range application.Providers {
if item.Provider.Type == providerType {
return item
}
}
return nil
}
func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false

View File

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

View File

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

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" {
@ -60,9 +72,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
}
fileUrl := util.UrlJoin(host, objectKey)
fileUrl := util.UrlJoin(host, escapePath(objectKey))
if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
}
return fileUrl, objectKey

View File

@ -22,7 +22,7 @@ import (
func (syncer *Syncer) syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := syncer.getUserMap()
users, userMap, userNameMap := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap()
if err != nil {
fmt.Printf(err.Error())
@ -44,9 +44,11 @@ func (syncer *Syncer) syncUsers() {
for _, oUser := range oUsers {
id := oUser.Id
if _, ok := userMap[id]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
if _, ok := userNameMap[oUser.Name]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
}
} else {
user := userMap[id]
oHash := syncer.calculateHash(oUser)

View File

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

View File

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

View File

@ -19,12 +19,15 @@ func (syncer *Syncer) getUsers() []*User {
return users
}
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
users := syncer.getUsers()
m := map[string]*User{}
m1 := map[string]*User{}
m2 := map[string]*User{}
for _, user := range users {
m[user.Id] = user
m1[user.Id] = user
m2[user.Name] = user
}
return users, m
return users, m1, m2
}

View File

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

View File

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

View File

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

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"
)
@ -72,7 +73,7 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"`
GitHub string `xorm:"github varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
@ -96,11 +97,16 @@ type User struct {
Steam string `xorm:"steam varchar(100)" json:"steam"`
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
}
type Userinfo struct {
@ -267,6 +273,42 @@ func GetUserByEmail(owner string, email string) *User {
}
}
func GetUserByPhone(owner string, phone string) *User {
if owner == "" || phone == "" {
return nil
}
user := User{Owner: owner, Phone: phone}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUserByUserId(owner string, userId string) *User {
if owner == "" || userId == "" {
return nil
}
user := User{Owner: owner, Id: userId}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
@ -326,9 +368,11 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
}
if len(columns) == 0 {
columns = []string{"owner", "display_name", "avatar",
columns = []string{
"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"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")
@ -395,10 +439,10 @@ func AddUsers(users []*User) bool {
return false
}
//organization := GetOrganizationByUser(users[0])
// organization := GetOrganizationByUser(users[0])
for _, user := range users {
// this function is only used for syncer or batch upload, so no need to encrypt the password
//user.UpdateUserPassword(organization)
// user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash

View File

@ -37,11 +37,11 @@ func TestSyncAvatarsFromGitHub(t *testing.T) {
users := GetGlobalUsers()
for _, user := range users {
if user.Github == "" {
if user.GitHub == "" {
continue
}
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.Github)
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.GitHub)
updateUserColumn("avatar", user)
}
}

View File

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

102
object/user_webauthn.go Normal file
View File

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

View File

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

View File

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

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
}
@ -118,6 +122,10 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx)
if strings.HasPrefix(urlPath, "/api/notify-payment") {
urlPath = "/api/notify-payment"
}
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
result := "deny"

View File

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

View File

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

View File

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

View File

@ -17,7 +17,9 @@ package util
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func GetHmacSha1(keyStr, value string) string {
@ -28,3 +30,10 @@ func GetHmacSha1(keyStr, value string) string {
return res
}
func GetHmacSha256(key string, data string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
return hex.EncodeToString(mac.Sum(nil))
}

View File

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

1
web/.env Normal file
View File

@ -0,0 +1 @@
PUBLIC_URL=.

2
web/.eslintignore Normal file
View File

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

63
web/.eslintrc Normal file
View File

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

View File

@ -1,38 +1,38 @@
const CracoLessPlugin = require('craco-less');
const CracoLessPlugin = require("craco-less");
module.exports = {
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 {DeleteOutlined, DownOutlined, 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")},
@ -86,12 +86,15 @@ class AccountTable extends React.Component {
{name: "Bio", displayName: i18next.t("user:Bio")},
{name: "Tag", displayName: i18next.t("user:Tag")},
{name: "Signup application", displayName: i18next.t("general:Signup application")},
{name: "Roles", displayName: i18next.t("general:Roles")},
{name: "Permissions", displayName: i18next.t("general:Permissions")},
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
{name: "Properties", displayName: i18next.t("user:Properties")},
{name: "Is admin", displayName: i18next.t("user:Is admin")},
{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 +106,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 +171,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 +216,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 +230,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 +238,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, Button, Card, Dropdown, Layout, Menu, Result} 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,16 +234,20 @@ class App extends Component {
AuthBackend.logout()
.then((res) => {
if (res.status === 'ok') {
if (res.status === "ok") {
const owner = this.state.account.owner;
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);
}else{
} else if (owner !== "built-in") {
Setting.goToLink(`${window.location.origin}/login/${owner}`);
} else {
Setting.goToLinkSoft(this, "/");
}
} else {
@ -259,9 +263,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 +273,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 +291,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 +304,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 +318,7 @@ class App extends Component {
&nbsp;
</div>
</Dropdown>
)
);
}
renderAccount() {
@ -484,7 +490,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 +499,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 +614,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 +646,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 +668,32 @@ 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="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/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 +723,7 @@ class App extends Component {
this.renderPage()
}
</React.Fragment>
)
);
}
const organization = this.state.account.organization;
@ -725,7 +737,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,122 @@ 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: "",
isAuthorized: true,
};
}
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,127 +70,127 @@ 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}>
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col>
<Col span={9} >
<Button style={{marginRight: '10px', marginBottom: '10px'}} onClick={() => {
copy(this.state.cert.publicKey);
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}}
>
{i18next.t("cert:Copy public key")}
{i18next.t("cert:Copy certificate")}
</Button>
<Button type="primary" onClick={() => {
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"});
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem");
}}
>
{i18next.t("cert:Download public key")}
{i18next.t("cert:Download certificate")}
</Button>
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => {
this.updateCertField('publicKey', e.target.value);
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.certificate} onChange={e => {
this.updateCertField("certificate", 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";
@ -34,17 +34,17 @@ class CertListPage extends BaseListPage {
cryptoAlgorithm: "RS256",
bitSize: 4096,
expireInYears: 20,
publicKey: "",
certificate: "",
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, Col, Modal, Row} 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

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Col, Row, Table, Popconfirm} from "antd";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next";
@ -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, Result, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend";
@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
{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: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is admin", visible: true, viewRule: "Admin", modifyRule: "Admin"},
@ -64,15 +66,15 @@ class OrganizationListPage extends BaseListPage {
{name: "Is forbidden", visible: true, viewRule: "Admin", modifyRule: "Admin"},
{name: "Is 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 +84,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 +100,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 +126,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>
)
);
}
},
];
@ -235,17 +237,28 @@ class OrganizationListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return (
<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 +271,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") {
@ -272,6 +285,13 @@ class OrganizationListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Modal, Row, Input,} from "antd";
import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next";
import 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,20 +129,22 @@ 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);
this.getModels(owner);
this.getResources(owner);
})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
@ -138,33 +152,33 @@ class PermissionEditPage extends React.Component {
</Select>
</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 +186,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 +292,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 +315,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 +328,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";
@ -33,18 +33,18 @@ class PermissionListPage extends BaseListPage {
roles: [],
resourceType: "Application",
resources: ["app-built-in"],
action: "Read",
actions: ["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,85 @@ 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 === "Payment") {
this.updateProviderField("type", "Alipay");
} 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 +254,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 +270,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 +291,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 +364,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 +390,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 +419,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 +490,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}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP public key"))} :
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
</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 +658,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 +678,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 +713,7 @@ class ProviderEditPage extends React.Component {
)
}
</Card>
)
);
}
submitProviderEdit(willExist) {
@ -719,19 +721,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 +744,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 +757,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 {DeleteOutlined, DownOutlined, 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, Input, Modal, Row} 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 {Dropdown, Menu} 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

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {message, Tag, Tooltip} from "antd";
import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect";
@ -122,6 +122,10 @@ export const OtherProviderInfo = {
"Aliyun Captcha": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://help.aliyun.com/product/28308.html",
},
"GEETEST": {
logo: `${StaticBaseUrl}/img/social_geetest.png`,
url: "https://www.geetest.com",
}
}
};
@ -135,14 +139,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 +173,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 +328,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 +402,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 +419,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 +429,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 +442,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 +466,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 +516,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 +537,89 @@ 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"},
{id: "GEETEST", name: "GEETEST"},
]);
} else {
return [];
@ -629,14 +630,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 +652,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"}} />
);
}
}
@ -666,8 +667,8 @@ export function goToLogin(ths, application) {
return;
}
if (!application.enablePassword && window.location.pathname.includes("/signup/oauth/authorize")) {
const link = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
goToLink(link);
return;
}
@ -689,7 +690,7 @@ export function goToSignup(ths, application) {
}
if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) {
const link = window.location.href.replace("/login/oauth/authorize", "/signup/oauth/authorize");
const link = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
goToLink(link);
return;
}
@ -731,13 +732,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 +762,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 +803,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 +818,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 +849,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 {DeleteOutlined, DownOutlined, 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") {

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