Compare commits

...

21 Commits

Author SHA1 Message Date
78e45d07cf fix: support RBAC With Domains/Tenants (#1333)
* feat: support RBAC With Domains/Tenants

* fix: add verify for `UpdatePermission`

* Update permission.go

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-05 16:08:17 +08:00
0856977b92 feat: update to antd 5.0 (#1362)
* feat: update to ant5.X

* fix: incompatible styles

* fix: adjust the style
2022-12-04 23:05:30 +08:00
a44a4b0300 feat: fix React incorrect usage to fix issue that verification code must be submitted twice to succeed (#1348)
* fix: synchronized user login fields saving

* fix: synchronized user login fields saving

* recover changes

* fix: save username in step 2

* fix: format
2022-12-04 20:58:07 +08:00
4b29dd8c41 fix: responsive certs page editor (#1360)
* fix: responsive editor width

* fix: format
2022-12-04 16:04:04 +08:00
165e2e33e3 fix: disable formcss in mobile (#1359) 2022-12-04 15:53:46 +08:00
d13a307ad5 Allow org admin to access GetResources() 2022-12-03 01:10:45 +08:00
27bd771fed feat: handle the dataSourceName when DB changes (#1352)
* fix: handle the dataSourceName when DB changes

* reduce duplication of code
2022-12-02 22:20:18 +08:00
9f3ee275a8 feat: reformat frontend alert texts with correct i18n (#1341)
* fix: add i18

* fix: standard prompt message
2022-12-02 00:06:28 +08:00
fcda64ad7d fix: provider sort alphabetical order (#1347) 2022-12-01 22:51:10 +08:00
d815bf92bd fix: handle add message in frontend (#1340) 2022-11-29 20:32:47 +08:00
7867060b71 feat: add quota limitation to organizations, users, providers and applications (#1339) 2022-11-29 11:01:41 +08:00
8890d1d7c7 fix: check credential existence when signing via WebAuthn (#1336)
* fix: check credential existence when signing via WebAuthn

* fix review problem
2022-11-28 21:47:17 +08:00
6e6a0a074a fix: application edit mobile view (#1331)
* fix: application edit mobile view

* fix: decompose elements

* fix: decomposition

* fix: remove space component

* Update ApplicationEditPage.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-28 21:10:49 +08:00
cff3007992 feat: add get-permissions-by-role API (#1335) 2022-11-28 15:30:46 +08:00
fe448cbcf4 feat: check user existence when signing in via verification code (#1334)
* fix:check user existence when logining by verification code

* fix review problems

* Update verification.go

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-28 00:11:33 +08:00
2ab25df950 fix: prompt page translation (#1330)
* fix: prompt page translation

* add multiple translations

* fix: translation consistency

* fix: translation consistency

* fix: add translation

* fix: add translation

* Update data.json

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-27 21:04:45 +08:00
b895926754 feat: use another filename when uploading a duplicated file instead of replacing it (#1329)
* fix: upload a file with the same name, not replace

* Update resource.go

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-27 17:32:15 +08:00
5bb7a4153f feat: add cloudflare turnstile captcha (#1327)
* feat: add cloudflare turnstile captcha

* fix: rename turnstile to cloudflare turnstile
2022-11-26 17:17:49 +08:00
b7cd598ee8 fix: fail to return after flush the page (#1325)
* fix: fail to return after flush the page 

Old methed just get the url path parameter when click the butten. But when the page flushed, the returnUrl will disappear, so can not return to the specified page.

* Update UserEditPage.js

* Update UserEditPage.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-25 23:08:45 +08:00
b10fb97c92 feat: finish policy list management (#1317) 2022-11-25 16:02:20 +08:00
b337b908ea feat: fix the bug that admin cannot upload avatar for other users (#1323) 2022-11-25 09:36:47 +08:00
107 changed files with 1953 additions and 951 deletions

View File

@ -32,7 +32,9 @@ func InitAuthz() {
var err error
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
a, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}

View File

@ -31,6 +31,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
return NewAliyunCaptchaProvider()
} else if captchaType == "GEETEST" {
return NewGEETESTCaptchaProvider()
} else if captchaType == "Cloudflare Turnstile" {
return NewCloudflareTurnstileProvider()
}
return nil
}

66
captcha/turnstile.go Normal file
View File

@ -0,0 +1,66 @@
// 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"
"io"
"net/http"
"net/url"
"strings"
)
const CloudflareTurnstileVerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
type CloudflareTurnstileProvider struct{}
func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider {
captcha := &CloudflareTurnstileProvider{}
return captcha
}
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},
}
resp, err := http.PostForm(CloudflareTurnstileVerifyUrl, reqData)
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Success bool `json:"success"`
ErrorCodes []string `json:"error-codes"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if len(captchaResp.ErrorCodes) > 0 {
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
}
return captchaResp.Success, nil
}

View File

@ -21,3 +21,4 @@ isDemoMode = false
batchSize = 100
ldapServerPort = 389
languages = en,zh,es,fr,de,ja,ko,ru
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}

View File

@ -15,6 +15,7 @@
package conf
import (
"encoding/json"
"fmt"
"os"
"runtime"
@ -24,6 +25,15 @@ import (
"github.com/beego/beego"
)
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() {
// this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"}
@ -35,6 +45,17 @@ func init() {
}
}
}
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
}
func GetConfigString(key string) string {
@ -95,3 +116,17 @@ func GetConfigBatchSize() int {
}
return res
}
func GetConfigQuota() *Quota {
return quota
}
func GetConfigRealDataSourceName(driverName string) string {
var dataSourceName string
if driverName != "mysql" {
dataSourceName = GetConfigDataSourceName()
} else {
dataSourceName = GetConfigDataSourceName() + GetConfigString("dbName")
}
return dataSourceName
}

View File

@ -93,3 +93,19 @@ func TestGetConfBool(t *testing.T) {
})
}
}
func TestGetConfigQuota(t *testing.T) {
scenarios := []struct {
description string
expected *Quota
}{
{"default", &Quota{-1, -1, -1, -1}},
}
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
quota := GetConfigQuota()
assert.Equal(t, scenery.expected, quota)
}
}

View File

@ -167,6 +167,12 @@ func (c *ApiController) AddApplication() {
return
}
count := object.GetApplicationCount("", "", "")
if err := checkQuotaForApplication(count); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
c.ServeJSON()
}

View File

@ -307,7 +307,7 @@ func (c *ApiController) Login() {
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(util.GetId(form.Provider))
provider := object.GetProvider(util.GetId("admin", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
if !providerItem.IsProviderVisible() {
c.ResponseError(fmt.Sprintf(c.T("ProviderErr.ProviderNotEnabled"), provider.Name))

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -89,6 +90,69 @@ func (c *ApiController) SyncPolicies() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
c.Data["json"] = object.SyncPolicies(adapter)
policies, err := object.SyncPolicies(adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = policies
c.ServeJSON()
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
var policies []xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdatePolicy(util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) AddPolicy() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.AddPolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) RemovePolicy() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.RemovePolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

View File

@ -99,6 +99,12 @@ func (c *ApiController) AddOrganization() {
return
}
count := object.GetOrganizationCount("", "", "")
if err := checkQuotaForOrganization(count); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
c.ServeJSON()
}

View File

@ -65,6 +65,20 @@ func (c *ApiController) GetPermissionsBySubmitter() {
return
}
// GetPermissionsByRole
// @Title GetPermissionsByRole
// @Tag Permission API
// @Description get permissions by role
// @Param id query string true "The id of the role"
// @Success 200 {array} object.Permission The Response object
// @router /get-permissions-by-role [get]
func (c *ApiController) GetPermissionsByRole() {
id := c.Input().Get("id")
permissions := object.GetPermissionsByRole(id)
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermission
// @Title GetPermission
// @Tag Permission API

View File

@ -122,6 +122,12 @@ func (c *ApiController) AddProvider() {
return
}
count := object.GetProviderCount("", "", "")
if err := checkQuotaForProvider(count); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
c.ServeJSON()
}

View File

@ -40,6 +40,15 @@ func (c *ApiController) GetResources() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
userObj, ok := c.RequireSignedInUser()
if !ok {
return
}
if userObj.IsAdmin {
user = ""
}
if limit == "" || page == "" {
c.Data["json"] = object.GetResources(owner, user)
c.ServeJSON()
@ -156,7 +165,7 @@ func (c *ApiController) UploadResource() {
return
}
provider, user, ok := c.GetProviderFromContext("Storage")
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
return
}
@ -171,6 +180,20 @@ func (c *ApiController) UploadResource() {
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
}
if tag != "avatar" && tag != "termsOfUse" {
ext := filepath.Ext(filepath.Base(fullFilePath))
index := len(fullFilePath) - len(ext)
for i := 1; ; i++ {
_, objectKey := object.GetUploadFileUrl(provider, fullFilePath, true)
if object.GetResourceCount(owner, username, "name", objectKey) == 0 {
break
}
// duplicated fullFilePath found, change it
fullFilePath = fullFilePath[:index] + fmt.Sprintf("-%d", i) + ext
}
}
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
@ -202,12 +225,10 @@ func (c *ApiController) UploadResource() {
switch tag {
case "avatar":
user := object.GetUserNoCheck(util.GetId(owner, username))
if user == nil {
user = object.GetUserNoCheck(username)
if user == nil {
c.ResponseError(c.T("ResourceErr.UserIsNil"))
return
}
c.ResponseError(c.T("ResourceErr.UserIsNil"))
return
}
user.Avatar = fileUrl

View File

@ -60,7 +60,7 @@ func (c *ApiController) SendEmail() {
var provider *object.Provider
if emailForm.Provider != "" {
// called by frontend's TestEmailWidget, provider name is set by frontend
provider = object.GetProvider(util.GetId(emailForm.Provider))
provider = object.GetProvider(util.GetId("admin", emailForm.Provider))
} else {
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
var ok bool

View File

@ -183,6 +183,12 @@ func (c *ApiController) AddUser() {
return
}
count := object.GetUserCount("", "", "")
if err := checkQuotaForUser(count); err != nil {
c.ResponseError(err.Error())
return
}
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)

View File

@ -126,7 +126,7 @@ func getInitScore() (int, error) {
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
providerName := c.Input().Get("provider")
if providerName != "" {
provider := object.GetProvider(util.GetId(providerName))
provider := object.GetProvider(util.GetId("admin", providerName))
if provider == nil {
c.ResponseError(c.T("ProviderErr.ProviderNotFound"), providerName)
return nil, nil, false
@ -153,3 +153,47 @@ func (c *ApiController) GetProviderFromContext(category string) (*object.Provide
return provider, user, true
}
func checkQuotaForApplication(count int) error {
quota := conf.GetConfigQuota().Application
if quota == -1 {
return nil
}
if count >= quota {
return fmt.Errorf("application quota is exceeded")
}
return nil
}
func checkQuotaForOrganization(count int) error {
quota := conf.GetConfigQuota().Organization
if quota == -1 {
return nil
}
if count >= quota {
return fmt.Errorf("organization quota is exceeded")
}
return nil
}
func checkQuotaForProvider(count int) error {
quota := conf.GetConfigQuota().Provider
if quota == -1 {
return nil
}
if count >= quota {
return fmt.Errorf("provider quota is exceeded")
}
return nil
}
func checkQuotaForUser(count int) error {
quota := conf.GetConfigQuota().User
if quota == -1 {
return nil
}
if count >= quota {
return fmt.Errorf("user quota is exceeded")
}
return nil
}

View File

@ -92,6 +92,10 @@ func (c *ApiController) SendVerificationCode() {
user := c.getCurrentUser()
application := object.GetApplication(applicationId)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
if organization == nil {
c.ResponseError(c.T("OrgErr.DoNotExist"))
return
}
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError(c.T("LoginErr.LoginFirst"))
@ -114,6 +118,12 @@ func (c *ApiController) SendVerificationCode() {
return
}
userByEmail := object.GetUserByEmail(organization.Name, dest)
if userByEmail == nil {
c.ResponseError(c.T("UserErr.DoNotExistSignUp"))
return
}
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
@ -124,8 +134,10 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(c.T("PhoneErr.NumberInvalid"))
return
}
if organization == nil {
c.ResponseError(c.T("OrgErr.DoNotExist"))
userByPhone := object.GetUserByPhone(organization.Name, dest)
if userByPhone == nil {
c.ResponseError(c.T("UserErr.DoNotExistSignUp"))
return
}

View File

@ -104,6 +104,11 @@ func (c *ApiController) WebAuthnSigninBegin() {
c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExistInOrg"), userOwner, userName))
return
}
if len(user.WebauthnCredentials) == 0 {
c.ResponseError(c.T("UserErr.NoWebAuthnCredential"))
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
if err != nil {
c.ResponseError(err.Error())

View File

@ -25,6 +25,6 @@ import (
)
func TestDeployStaticFiles(t *testing.T) {
provider := object.GetProvider(util.GetId("provider_storage_aliyun_oss"))
provider := object.GetProvider(util.GetId("admin", "provider_storage_aliyun_oss"))
deployStaticFiles(provider)
}

View File

@ -83,7 +83,7 @@ func parseToData() *I18nData {
data := I18nData{}
for _, word := range allWords {
tokens := strings.Split(word, ":")
tokens := strings.SplitN(word, ":", 2)
namespace := tokens[0]
key := tokens[1]

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -134,4 +134,5 @@ NameTooLang = Username is too long (maximum is 39 characters).
NameFormatErr = The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.
PasswordLessThanSixCharacters = Password must have at least 6 characters
InvalidInformation = Invalid information
NoWebAuthnCredential = Found no credentials for this user

View File

@ -1,5 +1,5 @@
[ApplicationErr]
AppNotFound = 应用 %%s 未找到
AppNotFound = 应用 %s 未找到
AppNotFoundForUserID = 找不到该用户的应用程序 %s
GrantTypeNotSupport = 此应用中不支持此授权类型
HasNoProviders = 该应用无提供商
@ -25,7 +25,7 @@ EmptyErr = 邮箱不可为空
EmailInvalid = 无效邮箱
EmailCheckResult = Email: %s
EmptyParam = 邮件参数为空: %v
InvalidReceivers = 无效的邮箱接收者: %%s
InvalidReceivers = 无效的邮箱接收者: %s
UnableGetModifyRule = 无法得到Email修改规则
[EnforcerErr]
@ -131,6 +131,7 @@ NameFormatErr = 用户名只能包含字母数字字符、下划线或连字符
PasswordLessThanSixCharacters = 密码至少为6字符
DoNotExistSignUp = 用户不存在,请先注册
InvalidInformation = 无效信息
NoWebAuthnCredential = 该用户没有WebAuthn凭据
[StorageErr]
ObjectKeyNotAllowed = object key :%s 不被允许

View File

@ -60,7 +60,7 @@ func getPermanentAvatarUrl(organization string, username string, url string, upl
}
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
if upload {
DownloadAndUpload(url, fullFilePath)

View File

@ -148,34 +148,7 @@ func (casbinAdapter *CasbinAdapter) getTable() string {
}
}
func safeReturn(policy []string, i int) string {
if len(policy) > i {
return policy[i]
} else {
return ""
}
}
func matrixToCasbinRules(pType string, policies [][]string) []*xormadapter.CasbinRule {
res := []*xormadapter.CasbinRule{}
for _, policy := range policies {
line := xormadapter.CasbinRule{
Ptype: pType,
V0: safeReturn(policy, 0),
V1: safeReturn(policy, 1),
V2: safeReturn(policy, 2),
V3: safeReturn(policy, 3),
V4: safeReturn(policy, 4),
V5: safeReturn(policy, 5),
}
res = append(res, &line)
}
return res
}
func SyncPolicies(casbinAdapter *CasbinAdapter) []*xormadapter.CasbinRule {
func initEnforcer(modelObj *Model, casbinAdapter *CasbinAdapter) (*casbin.Enforcer, error) {
// init Adapter
if casbinAdapter.Adapter == nil {
var dataSourceName string
@ -191,20 +164,60 @@ func SyncPolicies(casbinAdapter *CasbinAdapter) []*xormadapter.CasbinRule {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
}
casbinAdapter.Adapter, _ = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(casbinAdapter.DatabaseType, dataSourceName, casbinAdapter.Database).Engine, casbinAdapter.getTable(), "")
var err error
casbinAdapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(casbinAdapter.DatabaseType, dataSourceName, casbinAdapter.Database).Engine, casbinAdapter.getTable(), "")
if err != nil {
return nil, err
}
}
// init Model
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
m, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
panic(err)
return nil, err
}
// init Enforcer
enforcer, err := casbin.NewEnforcer(m, casbinAdapter.Adapter)
if err != nil {
panic(err)
return nil, err
}
return enforcer, nil
}
func safeReturn(policy []string, i int) string {
if len(policy) > i {
return policy[i]
} else {
return ""
}
}
func matrixToCasbinRules(Ptype string, policies [][]string) []*xormadapter.CasbinRule {
res := []*xormadapter.CasbinRule{}
for _, policy := range policies {
line := xormadapter.CasbinRule{
Ptype: Ptype,
V0: safeReturn(policy, 0),
V1: safeReturn(policy, 1),
V2: safeReturn(policy, 2),
V3: safeReturn(policy, 3),
V4: safeReturn(policy, 4),
V5: safeReturn(policy, 5),
}
res = append(res, &line)
}
return res
}
func SyncPolicies(casbinAdapter *CasbinAdapter) ([]*xormadapter.CasbinRule, error) {
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
enforcer, err := initEnforcer(modelObj, casbinAdapter)
if err != nil {
return nil, err
}
policies := matrixToCasbinRules("p", enforcer.GetPolicy())
@ -212,5 +225,48 @@ func SyncPolicies(casbinAdapter *CasbinAdapter) []*xormadapter.CasbinRule {
policies = append(policies, matrixToCasbinRules("g", enforcer.GetGroupingPolicy())...)
}
return policies
return policies, nil
}
func UpdatePolicy(oldPolicy, newPolicy []string, casbinAdapter *CasbinAdapter) (bool, error) {
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
enforcer, err := initEnforcer(modelObj, casbinAdapter)
if err != nil {
return false, err
}
affected, err := enforcer.UpdatePolicy(oldPolicy, newPolicy)
if err != nil {
return affected, err
}
return affected, nil
}
func AddPolicy(policy []string, casbinAdapter *CasbinAdapter) (bool, error) {
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
enforcer, err := initEnforcer(modelObj, casbinAdapter)
if err != nil {
return false, err
}
affected, err := enforcer.AddPolicy(policy)
if err != nil {
return affected, err
}
return affected, nil
}
func RemovePolicy(policy []string, casbinAdapter *CasbinAdapter) (bool, error) {
modelObj := getModel(casbinAdapter.Owner, casbinAdapter.Model)
enforcer, err := initEnforcer(modelObj, casbinAdapter)
if err != nil {
return false, err
}
affected, err := enforcer.RemovePolicy(policy)
if err != nil {
return affected, err
}
return affected, nil
}

View File

@ -158,7 +158,7 @@ func initBuiltInApplication() {
},
RedirectUris: []string{},
ExpireInHours: 168,
FormOffset: 8,
FormOffset: 2,
}
AddApplication(application)
}
@ -222,7 +222,7 @@ func initBuiltInLdap() {
}
func initBuiltInProvider() {
provider := GetProvider(util.GetId("provider_captcha_default"))
provider := GetProvider(util.GetId("admin", "provider_captcha_default"))
if provider != nil {
return
}

View File

@ -168,7 +168,7 @@ func initDefinedLdap(ldap *Ldap) {
}
func initDefinedProvider(provider *Provider) {
existed := GetProvider(util.GetId(provider.Name))
existed := GetProvider(util.GetId("admin", provider.Name))
if existed != nil {
return
}

View File

@ -111,7 +111,27 @@ func GetPermission(id string) *Permission {
return getPermission(owner, name)
}
// checkPermissionValid verifies if the permission is valid
func checkPermissionValid(permission *Permission) {
enforcer := getEnforcer(permission)
enforcer.EnableAutoSave(false)
policies, groupingPolicies := getPolicies(permission)
if len(groupingPolicies) > 0 {
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {
panic(err)
}
}
_, err := enforcer.AddPolicies(policies)
if err != nil {
panic(err)
}
}
func UpdatePermission(id string, permission *Permission) bool {
checkPermissionValid(permission)
owner, name := util.GetOwnerAndNameFromId(id)
oldPermission := getPermission(owner, name)
if oldPermission == nil {

View File

@ -29,7 +29,9 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
tableName = permission.Adapter
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName()+conf.GetConfigString("dbName"), tableName, tableNamePrefix, true)
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -155,7 +157,12 @@ func removePolicies(permission *Permission) {
func Enforce(permissionRule *PermissionRule) bool {
permission := GetPermission(permissionRule.Id)
enforcer := getEnforcer(permission)
allow, err := enforcer.Enforce(permissionRule.V0, permissionRule.V1, permissionRule.V2)
request := []interface{}{permissionRule.V0, permissionRule.V1, permissionRule.V2}
if permissionRule.V3 != "" {
request = append(request, permissionRule.V3)
}
allow, err := enforcer.Enforce(request...)
if err != nil {
panic(err)
}
@ -165,7 +172,11 @@ func Enforce(permissionRule *PermissionRule) bool {
func BatchEnforce(permissionRules []PermissionRule) []bool {
var requests [][]interface{}
for _, permissionRule := range permissionRules {
requests = append(requests, []interface{}{permissionRule.V0, permissionRule.V1, permissionRule.V2})
if permissionRule.V3 != "" {
requests = append(requests, []interface{}{permissionRule.V0, permissionRule.V1, permissionRule.V2, permissionRule.V3})
} else {
requests = append(requests, []interface{}{permissionRule.V0, permissionRule.V1, permissionRule.V2})
}
}
permission := GetPermission(permissionRules[0].Id)
enforcer := getEnforcer(permission)

View File

@ -54,7 +54,7 @@ func escapePath(path string) string {
return res
}
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath))
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
@ -93,7 +93,7 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
UpdateProvider(provider.GetId(), provider)
}
fileUrl, objectKey := getUploadFileUrl(provider, fullFilePath, true)
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
_, err := storageProvider.Put(objectKey, fileBuffer)
if err != nil {

View File

@ -678,7 +678,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
ErrorDescription: "the application does not support wechat mini program",
}
}
provider := GetProvider(util.GetId(mpProvider.Name))
provider := GetProvider(util.GetId("admin", mpProvider.Name))
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {

View File

@ -82,6 +82,7 @@ func initAPI() {
beego.Router("/api/get-permissions", &controllers.ApiController{}, "GET:GetPermissions")
beego.Router("/api/get-permissions-by-submitter", &controllers.ApiController{}, "GET:GetPermissionsBySubmitter")
beego.Router("/api/get-permissions-by-role", &controllers.ApiController{}, "GET:GetPermissionsByRole")
beego.Router("/api/get-permission", &controllers.ApiController{}, "GET:GetPermission")
beego.Router("/api/update-permission", &controllers.ApiController{}, "POST:UpdatePermission")
beego.Router("/api/add-permission", &controllers.ApiController{}, "POST:AddPermission")
@ -105,6 +106,9 @@ func initAPI() {
beego.Router("/api/add-adapter", &controllers.ApiController{}, "POST:AddCasbinAdapter")
beego.Router("/api/delete-adapter", &controllers.ApiController{}, "POST:DeleteCasbinAdapter")
beego.Router("/api/sync-policies", &controllers.ApiController{}, "GET:SyncPolicies")
beego.Router("/api/update-policy", &controllers.ApiController{}, "POST:UpdatePolicy")
beego.Router("/api/add-policy", &controllers.ApiController{}, "POST:AddPolicy")
beego.Router("/api/remove-policy", &controllers.ApiController{}, "POST:RemovePolicy")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")

View File

@ -123,8 +123,8 @@ func GenerateSimpleTimeId() string {
return t
}
func GetId(name string) string {
return fmt.Sprintf("admin/%s", name)
func GetId(owner, name string) string {
return fmt.Sprintf("%s/%s", owner, name)
}
func GetMd5Hash(text string) string {

View File

@ -137,16 +137,16 @@ func TestGenerateId(t *testing.T) {
func TestGetId(t *testing.T) {
scenarios := []struct {
description string
input string
input []string
expected interface{}
}{
{"Scenery one", "casdoor", "admin/casdoor"},
{"Scenery two", "casbin", "admin/casbin"},
{"Scenery three", "lorem ipsum", "admin/lorem ipsum"},
{"Scenery one", []string{"admin", "casdoor"}, "admin/casdoor"},
{"Scenery two", []string{"admin", "casbin"}, "admin/casbin"},
{"Scenery three", []string{"test", "lorem ipsum"}, "test/lorem ipsum"},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetId(scenery.input)
actual := GetId(scenery.input[0], scenery.input[1])
assert.Equal(t, scenery.expected, actual, "This not is a valid MD5")
})
}

29
util/struct.go Normal file
View File

@ -0,0 +1,29 @@
// 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 util
import xormadapter "github.com/casbin/xorm-adapter/v3"
func CasbinToSlice(casbinRule xormadapter.CasbinRule) []string {
s := []string{
casbinRule.V0,
casbinRule.V1,
casbinRule.V2,
casbinRule.V3,
casbinRule.V4,
casbinRule.V5,
}
return s
}

View File

@ -9,7 +9,7 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"antd": "^4.22.8",
"antd": "5.0.3",
"codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0",

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch, Table, Tooltip} from "antd";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
@ -21,7 +21,7 @@ import i18next from "i18next";
import "codemirror/lib/codemirror.css";
import * as ModelBackend from "./backend/ModelBackend";
import {EditOutlined, MinusOutlined} from "@ant-design/icons";
import PolicyTable from "./common/PoliciyTable";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
@ -32,12 +32,11 @@ class AdapterEditPage extends React.Component {
super(props);
this.state = {
classes: props,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
adapterName: props.match.params.adapterName,
adapter: null,
organizations: [],
models: [],
policyLists: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
@ -48,7 +47,7 @@ class AdapterEditPage extends React.Component {
}
getAdapter() {
AdapterBackend.getAdapter(this.state.organizationName, this.state.adapterName)
AdapterBackend.getAdapter(this.state.owner, this.state.adapterName)
.then((adapter) => {
this.setState({
adapter: adapter,
@ -93,93 +92,6 @@ class AdapterEditPage extends React.Component {
});
}
synPolicies() {
this.setState({loading: true});
AdapterBackend.syncPolicies(this.state.adapter.owner, this.state.adapter.name)
.then((res) => {
this.setState({loading: false, policyLists: res});
})
.catch(error => {
this.setState({loading: false});
Setting.showMessage("error", `Adapter failed to get policies: ${error}`);
});
}
renderTable(table) {
const columns = [
{
title: "Rule Type",
dataIndex: "PType",
key: "PType",
width: "100px",
},
{
title: "V0",
dataIndex: "V0",
key: "V0",
width: "100px",
},
{
title: "V1",
dataIndex: "V1",
key: "V1",
width: "100px",
},
{
title: "V2",
dataIndex: "V2",
key: "V2",
width: "100px",
},
{
title: "V3",
dataIndex: "V3",
key: "V3",
width: "100px",
},
{
title: "V4",
dataIndex: "V4",
key: "V4",
width: "100px",
},
{
title: "V5",
dataIndex: "V5",
key: "V5",
width: "100px",
},
{
title: "Option",
key: "option",
width: "100px",
render: (text, record, index) => {
return (
<div>
<Tooltip placement="topLeft" title="Edit">
<Button style={{marginRight: "0.5rem"}} icon={<EditOutlined />} size="small" />
</Tooltip>
<Tooltip placement="topLeft" title="Delete">
<Button icon={<MinusOutlined />} size="small" />
</Tooltip>
</div>
);
},
}];
return (
<div>
<Table
pagination={{
defaultPageSize: 10,
}}
columns={columns} dataSource={table} rowKey="name" size="middle" bordered
loading={this.state.loading}
/>
</div>
);
}
renderAdapter() {
return (
<Card size="small" title={
@ -329,19 +241,8 @@ class AdapterEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("adapter:Policies"), i18next.t("adapter:Policies - Tooltip"))} :
</Col>
<Col span={2}>
<Button type="primary" onClick={() => {this.synPolicies();}}>
{i18next.t("adapter:Sync")}
</Button>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
</Col>
<Col span={22} >
{
this.renderTable(this.state.policyLists)
}
<Col span={22}>
<PolicyTable owner={this.state.owner} name={this.state.adapterName} mode={this.state.mode} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
@ -362,8 +263,8 @@ class AdapterEditPage extends React.Component {
const adapter = Setting.deepCopy(this.state.adapter);
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
adapterName: this.state.adapter.name,
});
@ -371,25 +272,29 @@ class AdapterEditPage extends React.Component {
if (willExist) {
this.props.history.push("/adapters");
} else {
this.props.history.push(`/adapters/${this.state.adapter.name}`);
this.props.history.push(`/adapters/${this.state.owner}/${this.state.adapter.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateAdapterField("name", this.state.adapterName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteAdapter() {
AdapterBackend.deleteAdapter(this.state.adapter)
.then(() => {
this.props.history.push("/adapters");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/adapters");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `adapter failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -45,26 +45,33 @@ class AdapterListPage extends BaseListPage {
const newAdapter = this.newAdapter();
AdapterBackend.addAdapter(newAdapter)
.then((res) => {
this.props.history.push({pathname: `/adapters/${newAdapter.owner}/${newAdapter.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/adapters/${newAdapter.organization}/${newAdapter.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Adapter failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteAdapter(i) {
AdapterBackend.deleteAdapter(this.state.data[i])
.then((res) => {
Setting.showMessage("success", "Adapter deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Adapter failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -201,7 +208,7 @@ class AdapterListPage extends BaseListPage {
title={`Sure to delete adapter: ${record.name} ?`}
onConfirm={() => this.deleteAdapter(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Button, Card, Drawer, Dropdown, Layout, Menu, Result} from "antd";
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
@ -295,23 +295,16 @@ class App extends Component {
}
renderRightDropdown() {
const menu = (
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account">
<SettingOutlined />
&nbsp;&nbsp;
{i18next.t("account:My Account")}
</Menu.Item>
<Menu.Item key="/logout">
<LogoutOutlined />
&nbsp;&nbsp;
{i18next.t("account:Logout")}
</Menu.Item>
</Menu>
);
const items = [];
items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>,
"/account"
));
items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>,
"/logout"));
const onClick = this.handleRightDropdownClick.bind(this);
return (
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
<Dropdown key="/rightDropDown" menu={{items, onClick}} className="rightDropDown">
<div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
&nbsp;
&nbsp;
@ -363,155 +356,89 @@ class App extends Component {
return [];
}
res.push(
<Menu.Item key="/">
<Link to="/">
{i18next.t("general:Home")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/organizations">
<Link to="/organizations">
{i18next.t("general:Organizations")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
"/organizations"));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/users">
<Link to="/users">
{i18next.t("general:Users")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/roles">
<Link to="/roles">
{i18next.t("general:Roles")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/permissions">
<Link to="/permissions">
{i18next.t("general:Permissions")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>,
"/users"
));
res.push(Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>,
"/roles"
));
res.push(Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>,
"/permissions"
));
}
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/models">
<Link to="/models">
{i18next.t("general:Models")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/adapters">
<Link to="/adapters">
{i18next.t("general:Adapters")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>,
"/models"
));
res.push(Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>,
"/adapters"
));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/applications">
<Link to="/applications">
{i18next.t("general:Applications")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/providers">
<Link to="/providers">
{i18next.t("general:Providers")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/resources">
<Link to="/resources">
{i18next.t("general:Resources")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/records">
<Link to="/records">
{i18next.t("general:Records")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>,
"/applications"
));
res.push(Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>,
"/providers"
));
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
"/resources"
));
res.push(Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>,
"/records"
));
}
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/tokens">
<Link to="/tokens">
{i18next.t("general:Tokens")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/webhooks">
<Link to="/webhooks">
{i18next.t("general:Webhooks")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/syncers">
<Link to="/syncers">
{i18next.t("general:Syncers")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/certs">
<Link to="/certs">
{i18next.t("general:Certs")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>,
"/tokens"
));
res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>,
"/webhooks"
));
res.push(Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>,
"/syncers"
));
res.push(Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>,
"/certs"
));
if (Conf.EnableExtraPages) {
res.push(
<Menu.Item key="/products">
<Link to="/products">
{i18next.t("general:Products")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/payments">
<Link to="/payments">
{i18next.t("general:Payments")}
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/sysinfo">
<Link to="/sysinfo">
{i18next.t("general:SysInfo")}
</Link>
</Menu.Item>
);
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
"/products"
));
res.push(Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>,
"/payments"
));
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:SysInfo")}</Link>,
"/sysinfo"
));
}
res.push(
<Menu.Item key="/swagger">
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
{i18next.t("general:Swagger")}
</a>
</Menu.Item>
);
res.push(Setting.getItem(<a target="_blank" rel="noreferrer"
href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>,
"/swagger"
));
}
return res;
@ -623,13 +550,11 @@ class App extends Component {
<div>
<Menu
// theme="dark"
items={this.renderMenu()}
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
>
{
this.renderMenu()
}
</Menu>
{
this.renderAccount()
@ -661,14 +586,12 @@ class App extends Component {
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
<Menu
// theme="dark"
items={this.renderMenu()}
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
onClick={this.onClose}
>
{
this.renderMenu()
}
</Menu>
</Drawer>
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
@ -754,7 +677,7 @@ class App extends Component {
return (
<div id="parent-area">
<BackTop />
<FloatButton.BackTop />
<CustomGithubCorner />
<div id="content-wrap" style={{flexDirection: "column"}}>
{
@ -775,9 +698,16 @@ class App extends Component {
<Helmet>
<link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
</Helmet>
{
this.renderPage()
}
<ConfigProvider theme={{
token: {
colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)",
},
}}>
{
this.renderPage()
}
</ConfigProvider>
</React.Fragment>
);
}
@ -789,9 +719,16 @@ class App extends Component {
<title>{organization.displayName}</title>
<link rel="icon" href={organization.favicon} />
</Helmet>
{
this.renderPage()
}
<ConfigProvider theme={{
token: {
colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)",
},
}}>
{
this.renderPage()
}
</ConfigProvider>
</React.Fragment>
);
}

View File

@ -1,6 +1,5 @@
/* stylelint-disable at-rule-name-case */
/* stylelint-disable selector-class-pattern */
@import "~antd/dist/antd.less";
@StaticBaseUrl: "https://cdn.casbin.org";
@ -32,6 +31,11 @@
height: 100%;
}
img {
border-style: none;
vertical-align: middle;
}
#parent-area {
display: flex;
flex-direction: column;
@ -56,7 +60,7 @@
#footer {
bottom: 0;
width: 100%;
height: 70px; /* Footer height */
padding: 24px 50px;
}
.language-box {

View File

@ -49,6 +49,9 @@ const template = `<style>
}
</style>`;
const previewGrid = Setting.isMobile() ? 22 : 11;
const previewWidth = Setting.isMobile() ? "110%" : "90%";
const sideTemplate = `<style>
.left-model{
text-align: center;
@ -188,7 +191,7 @@ class ApplicationEditPage extends React.Component {
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
this.updateApplicationField("termsOfUse", res.data);
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
}
}).finally(() => {
this.setState({uploading: false});
@ -720,7 +723,7 @@ class ApplicationEditPage extends React.Component {
return (
<React.Fragment>
<Col span={11}>
<Col span={previewGrid}>
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}${signUpUrl}`);
Setting.showMessage("success", i18next.t("application:Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
@ -729,7 +732,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signup page URL")}
</Button>
<br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
{
this.state.application.enablePassword ? (
<SignupPage application={this.state.application} />
@ -740,8 +743,8 @@ class ApplicationEditPage extends React.Component {
<div style={maskStyle} />
</div>
</Col>
<Col span={11}>
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
<Col span={previewGrid}>
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}${signInUrl}`);
Setting.showMessage("success", i18next.t("application:Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
}}
@ -749,7 +752,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signin page URL")}
</Button>
<br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
<div style={{position: "relative", width: previewWidth, 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>
@ -762,7 +765,7 @@ class ApplicationEditPage extends React.Component {
const promptUrl = `/prompt/${this.state.application.name}`;
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
return (
<Col span={11}>
<Col span={previewGrid}>
<Button style={{marginBottom: "10px"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
copy(`${window.location.origin}${promptUrl}`);
Setting.showMessage("success", i18next.t("application:Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser"));
@ -771,7 +774,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy prompt page URL")}
</Button>
<br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
<PromptPage application={this.state.application} account={this.props.account} />
<div style={maskStyle} />
</div>
@ -783,8 +786,8 @@ class ApplicationEditPage extends React.Component {
const application = Setting.deepCopy(this.state.application);
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
applicationName: this.state.application.name,
});
@ -795,22 +798,26 @@ class ApplicationEditPage extends React.Component {
this.props.history.push(`/applications/${this.state.application.organization}/${this.state.application.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateApplicationField("name", this.state.applicationName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteApplication() {
ApplicationBackend.deleteApplication(this.state.application)
.then(() => {
this.props.history.push("/applications");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/applications");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Application failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -70,7 +70,7 @@ class ApplicationListPage extends BaseListPage {
redirectUris: ["http://localhost:9000/callback"],
tokenFormat: "JWT",
expireInHours: 24 * 7,
formOffset: 8,
formOffset: 2,
};
}
@ -78,26 +78,33 @@ class ApplicationListPage extends BaseListPage {
const newApplication = this.newApplication();
ApplicationBackend.addApplication(newApplication)
.then((res) => {
this.props.history.push({pathname: `/applications/${newApplication.organization}/${newApplication.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/applications/${newApplication.organization}/${newApplication.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Application failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Application failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -236,7 +243,7 @@ class ApplicationListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -87,7 +87,7 @@ class BaseListPage extends React.Component {
record[dataIndex]
? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
: "",
onFilterDropdownVisibleChange: visible => {
onFilterDropdownOpenChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select(), 100);
}

View File

@ -65,6 +65,7 @@ class CertEditPage extends React.Component {
}
renderCert() {
const editorWidth = Setting.isMobile() ? 22 : 9;
return (
<Card size="small" title={
<div>
@ -166,7 +167,7 @@ class CertEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col>
<Col span={9} >
<Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
@ -189,7 +190,7 @@ class CertEditPage extends React.Component {
<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} >
<Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.privateKey);
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
@ -217,8 +218,8 @@ class CertEditPage extends React.Component {
const cert = Setting.deepCopy(this.state.cert);
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
certName: this.state.cert.name,
});
@ -229,22 +230,26 @@ class CertEditPage extends React.Component {
this.props.history.push(`/certs/${this.state.cert.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateCertField("name", this.state.certName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteCert() {
CertBackend.deleteCert(this.state.cert)
.then(() => {
this.props.history.push("/certs");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/certs");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Cert failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -43,26 +43,33 @@ class CertListPage extends BaseListPage {
const newCert = this.newCert();
CertBackend.addCert(newCert)
.then((res) => {
this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Cert failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Cert failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -165,7 +172,7 @@ class CertListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -30,6 +30,7 @@ export const CropperDiv = (props) => {
const {title} = props;
const {user} = props;
const {buttonText} = props;
const {organization} = props;
let uploadButton;
const onChange = (e) => {
@ -92,9 +93,8 @@ export const CropperDiv = (props) => {
const getOptions = (data) => {
const options = [];
if (props.account.organization.defaultAvatar !== null) {
options.push({value: props.account.organization.defaultAvatar});
}
options.push({value: organization?.defaultAvatar});
for (let i = 0; i < data.length; i++) {
if (data[i].fileType === "image") {
const url = `${data[i].url}`;
@ -125,7 +125,7 @@ export const CropperDiv = (props) => {
useEffect(() => {
setLoading(true);
ResourceBackend.getResources(props.account.owner, props.account.name, "", "", "", "", "", "")
ResourceBackend.getResources(user.owner, user.name, "", "", "", "", "", "")
.then((res) => {
setLoading(false);
setOptions(getOptions(res));
@ -140,7 +140,7 @@ export const CropperDiv = (props) => {
<Modal
maskClosable={false}
title={title}
visible={visible}
open={visible}
okText={i18next.t("user:Upload a photo")}
confirmLoading={confirmLoading}
onCancel={handleCancel}

View File

@ -144,7 +144,7 @@ class LdapListPage extends React.Component {
onConfirm={() => this.deleteLdap(index)}
>
<Button style={{marginBottom: "10px"}}
type="danger">{i18next.t("general:Delete")}</Button>
type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -81,7 +81,7 @@ class LdapTable extends React.Component {
table = Setting.deleteRow(table, i);
this.updateTable(table);
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
}
}
)
@ -162,7 +162,7 @@ class LdapTable extends React.Component {
onConfirm={() => this.deleteRow(table, index)}
>
<Button style={{marginBottom: "10px"}}
type="danger">{i18next.t("general:Delete")}</Button>
type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -158,8 +158,8 @@ class ModelEditPage extends React.Component {
const model = Setting.deepCopy(this.state.model);
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
modelName: this.state.model.name,
});
@ -170,22 +170,26 @@ class ModelEditPage extends React.Component {
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateModelField("name", this.state.modelName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteModel() {
ModelBackend.deleteModel(this.state.model)
.then(() => {
this.props.history.push("/models");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/models");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Model failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -38,26 +38,33 @@ class ModelListPage extends BaseListPage {
const newModel = this.newModel();
ModelBackend.addModel(newModel)
.then((res) => {
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/models/${newModel.owner}/${newModel.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Model failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Model failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -139,7 +146,7 @@ class ModelListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -335,8 +335,8 @@ class OrganizationEditPage extends React.Component {
const organization = Setting.deepCopy(this.state.organization);
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
organizationName: this.state.organization.name,
});
@ -347,22 +347,26 @@ class OrganizationEditPage extends React.Component {
this.props.history.push(`/organizations/${this.state.organization.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateOrganizationField("name", this.state.organizationName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteOrganization() {
OrganizationBackend.deleteOrganization(this.state.organization)
.then(() => {
this.props.history.push("/organizations");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/organizations");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -75,26 +75,33 @@ class OrganizationListPage extends BaseListPage {
const newOrganization = this.newOrganization();
OrganizationBackend.addOrganization(newOrganization)
.then((res) => {
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/organizations/${newOrganization.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Organization failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Organization failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -224,7 +231,7 @@ class OrganizationListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -64,7 +64,7 @@ export const PasswordModal = (props) => {
<Modal
maskClosable={false}
title={i18next.t("user:Password")}
visible={visible}
open={visible}
okText={i18next.t("user:Set Password")}
cancelText={i18next.t("user:Cancel")}
confirmLoading={confirmLoading}

View File

@ -78,7 +78,7 @@ class PaymentEditPage extends React.Component {
this.setState({
isInvoiceLoading: false,
});
if (res.msg === "") {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully invoiced");
Setting.openLinkSafe(res.data);
this.getPayment();
@ -90,7 +90,7 @@ class PaymentEditPage extends React.Component {
this.setState({
isInvoiceLoading: false,
});
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -117,7 +117,7 @@ class PaymentEditPage extends React.Component {
{" " + i18next.t("payment:Confirm your invoice information")}
</div>
}
visible={this.state.isModalVisible}
open={this.state.isModalVisible}
onOk={handleIssueInvoice}
onCancel={handleCancel}
okText={i18next.t("payment:Issue Invoice")}
@ -443,8 +443,8 @@ class PaymentEditPage extends React.Component {
const payment = Setting.deepCopy(this.state.payment);
PaymentBackend.updatePayment(this.state.payment.owner, this.state.paymentName, payment)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
paymentName: this.state.payment.name,
});
@ -455,22 +455,26 @@ class PaymentEditPage extends React.Component {
this.props.history.push(`/payments/${this.state.payment.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updatePaymentField("name", this.state.paymentName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deletePayment() {
PaymentBackend.deletePayment(this.state.payment)
.then(() => {
this.props.history.push("/payments");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/payments");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Payment failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -51,26 +51,34 @@ class PaymentListPage extends BaseListPage {
const newPayment = this.newPayment();
PaymentBackend.addPayment(newPayment)
.then((res) => {
this.props.history.push({pathname: `/payments/${newPayment.name}`, mode: "add"});
if (res.status === "ok") {
this.props.history.push({pathname: `/payments/${newPayment.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
}
)
.catch(error => {
Setting.showMessage("error", `Payment failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Payment failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -218,7 +226,7 @@ class PaymentListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -417,8 +417,8 @@ class PermissionEditPage extends React.Component {
const permission = Setting.deepCopy(this.state.permission);
PermissionBackend.updatePermission(this.state.organizationName, this.state.permissionName, permission)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
permissionName: this.state.permission.name,
});
@ -429,22 +429,26 @@ class PermissionEditPage extends React.Component {
this.props.history.push(`/permissions/${this.state.permission.owner}/${this.state.permission.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updatePermissionField("name", this.state.permissionName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deletePermission() {
PermissionBackend.deletePermission(this.state.permission)
.then(() => {
this.props.history.push("/permissions");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/permissions");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Permission failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -48,30 +48,33 @@ class PermissionListPage extends BaseListPage {
const newPermission = this.newPermission();
PermissionBackend.addPermission(newPermission)
.then((res) => {
if (res.msg !== "") {
Setting.showMessage("error", res.msg);
return;
if (res.status === "ok") {
this.props.history.push({pathname: `/permissions/${newPermission.owner}/${newPermission.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
this.props.history.push({pathname: `/permissions/${newPermission.owner}/${newPermission.name}`, mode: "add"});
}
)
})
.catch(error => {
Setting.showMessage("error", `Permission failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Permission failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -301,7 +304,7 @@ class PermissionListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -81,11 +81,11 @@ class ProductBuyPage extends React.Component {
ProductBackend.buyProduct(this.state.product.owner, this.state.productName, provider.name)
.then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
const payUrl = res.data;
Setting.goToLink(payUrl);
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.setState({
isPlacingOrder: false,
@ -93,7 +93,7 @@ class ProductBuyPage extends React.Component {
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -271,8 +271,8 @@ class ProductEditPage extends React.Component {
const product = Setting.deepCopy(this.state.product);
ProductBackend.updateProduct(this.state.product.owner, this.state.productName, product)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
productName: this.state.product.name,
});
@ -283,22 +283,26 @@ class ProductEditPage extends React.Component {
this.props.history.push(`/products/${this.state.product.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateProductField("name", this.state.productName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteProduct() {
ProductBackend.deleteProduct(this.state.product)
.then(() => {
this.props.history.push("/products");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/products");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Product failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -45,26 +45,33 @@ class ProductListPage extends BaseListPage {
const newProduct = this.newProduct();
ProductBackend.addProduct(newProduct)
.then((res) => {
this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/products/${newProduct.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Product failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Product failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -236,7 +243,7 @@ class ProductListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -257,7 +257,9 @@ class ProviderEditPage extends React.Component {
{id: "SAML", name: "SAML"},
{id: "Payment", name: "Payment"},
{id: "Captcha", name: "Captcha"},
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
]
.sort((a, b) => a.name.localeCompare(b.name))
.map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
}
</Select>
</Col>
@ -280,7 +282,9 @@ class ProviderEditPage extends React.Component {
}
})}>
{
Setting.getProviderTypeOptions(this.state.provider.category).map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
Setting.getProviderTypeOptions(this.state.provider.category)
.sort((a, b) => a.name.localeCompare(b.name))
.map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
}
</Select>
</Col>
@ -789,8 +793,8 @@ class ProviderEditPage extends React.Component {
const provider = Setting.deepCopy(this.state.provider);
ProviderBackend.updateProvider(this.state.owner, this.state.providerName, provider)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
owner: this.state.provider.owner,
providerName: this.state.provider.name,
@ -802,22 +806,26 @@ class ProviderEditPage extends React.Component {
this.props.history.push(`/providers/${this.state.provider.owner}/${this.state.provider.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateProviderField("name", this.state.providerName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteProvider() {
ProviderBackend.deleteProvider(this.state.provider)
.then(() => {
this.props.history.push("/providers");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/providers");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Provider failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -61,26 +61,33 @@ class ProviderListPage extends BaseListPage {
const newProvider = this.newProvider();
ProviderBackend.addProvider(newProvider)
.then((res) => {
this.props.history.push({pathname: `/providers/${newProvider.owner}/${newProvider.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/providers/${newProvider.owner}/${newProvider.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Provider failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Provider failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -205,7 +212,7 @@ class ProviderListPage extends BaseListPage {
title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)}
>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -71,7 +71,7 @@ export const ResetModal = (props) => {
<Modal
maskClosable={false}
title={buttonText}
visible={visible}
open={visible}
okText={buttonText}
cancelText={i18next.t("user:Cancel")}
confirmLoading={confirmLoading}

View File

@ -43,15 +43,18 @@ 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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Resource failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -207,16 +210,14 @@ class ResourceListPage extends BaseListPage {
if (record.fileType === "image") {
return (
<a target="_blank" rel="noreferrer" href={record.url}>
<img src={record.url} alt={record.name} width={100} />
<img src={record.url} alt={record.name} width={200} />
</a>
);
} else if (record.fileType === "video") {
return (
<div>
<video width={100} controls>
<source src={text} type="video/mp4" />
</video>
</div>
<video width={200} controls>
<source src={record.url} type="video/mp4" />
</video>
);
}
},
@ -256,7 +257,7 @@ class ResourceListPage extends BaseListPage {
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
>
<Button type="danger">{i18next.t("general:Delete")}</Button>
<Button type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -196,8 +196,8 @@ class RoleEditPage extends React.Component {
const role = Setting.deepCopy(this.state.role);
RoleBackend.updateRole(this.state.organizationName, this.state.roleName, role)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
roleName: this.state.role.name,
});
@ -208,22 +208,26 @@ class RoleEditPage extends React.Component {
this.props.history.push(`/roles/${this.state.role.owner}/${this.state.role.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateRoleField("name", this.state.roleName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteRole() {
RoleBackend.deleteRole(this.state.role)
.then(() => {
this.props.history.push("/roles");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/roles");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Role failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -40,26 +40,33 @@ class RoleListPage extends BaseListPage {
const newRole = this.newRole();
RoleBackend.addRole(newRole)
.then((res) => {
this.props.history.push({pathname: `/roles/${newRole.owner}/${newRole.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/roles/${newRole.owner}/${newRole.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Role failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Role failed to delete: ${error}`);
});
}
@ -172,7 +179,7 @@ class RoleListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -33,14 +33,14 @@ class SelectLanguageBox extends React.Component {
}
items = [
this.getItem("English", "en", flagIcon("US", "English")),
this.getItem("简体中文", "zh", flagIcon("CN", "简体中文")),
this.getItem("Español", "es", flagIcon("ES", "Español")),
this.getItem("Français", "fr", flagIcon("FR", "Français")),
this.getItem("Deutsch", "de", flagIcon("DE", "Deutsch")),
this.getItem("日本語", "ja", flagIcon("JP", "日本語")),
this.getItem("한국어", "ko", flagIcon("KR", "한국어")),
this.getItem("Русский", "ru", flagIcon("RU", "Русский")),
Setting.getItem("English", "en", flagIcon("US", "English")),
Setting.getItem("简体中文", "zh", flagIcon("CN", "简体中文")),
Setting.getItem("Español", "es", flagIcon("ES", "Español")),
Setting.getItem("Français", "fr", flagIcon("FR", "Français")),
Setting.getItem("Deutsch", "de", flagIcon("DE", "Deutsch")),
Setting.getItem("日本語", "ja", flagIcon("JP", "日本語")),
Setting.getItem("한국어", "ko", flagIcon("KR", "한국어")),
Setting.getItem("Русский", "ru", flagIcon("RU", "Русский")),
];
getOrganizationLanguages(languages) {
@ -51,10 +51,6 @@ class SelectLanguageBox extends React.Component {
return select;
}
getItem(label, key, icon) {
return {key, icon, label};
}
render() {
const languageItems = this.getOrganizationLanguages(this.state.languages);
const menu = (

View File

@ -145,6 +145,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_geetest.png`,
url: "https://www.geetest.com",
},
"Cloudflare Turnstile": {
logo: `${StaticBaseUrl}/img/social_cloudflare.png`,
url: "https://www.cloudflare.com/products/turnstile/",
},
},
};
@ -416,9 +420,7 @@ export function goToLinkSoft(ths, link) {
}
export function showMessage(type, text) {
if (type === "") {
return;
} else if (type === "success") {
if (type === "success") {
message.success(text);
} else if (type === "error") {
message.error(text);
@ -445,8 +447,8 @@ export function deepCopy(obj) {
return Object.assign({}, obj);
}
export function addRow(array, row) {
return [...array, row];
export function addRow(array, row, position = "end") {
return position === "end" ? [...array, row] : [row, ...array];
}
export function prependRow(array, row) {
@ -697,6 +699,7 @@ export function getProviderTypeOptions(category) {
{id: "hCaptcha", name: "hCaptcha"},
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
{id: "GEETEST", name: "GEETEST"},
{id: "Cloudflare Turnstile", name: "Cloudflare Turnstile"},
]);
} else {
return [];
@ -855,6 +858,15 @@ export function getLabel(text, tooltip) {
);
}
export function getItem(label, key, icon, children, type) {
return {
key,
icon,
children,
label,
type,
};
}
function repeat(str, len) {
while (str.length < len) {
str += str.substr(0, len - str.length);

View File

@ -298,8 +298,8 @@ class SyncerEditPage extends React.Component {
const syncer = Setting.deepCopy(this.state.syncer);
SyncerBackend.updateSyncer(this.state.syncer.owner, this.state.syncerName, syncer)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
syncerName: this.state.syncer.name,
});
@ -310,22 +310,26 @@ class SyncerEditPage extends React.Component {
this.props.history.push(`/syncers/${this.state.syncer.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateSyncerField("name", this.state.syncerName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteSyncer() {
SyncerBackend.deleteSyncer(this.state.syncer)
.then(() => {
this.props.history.push("/syncers");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/syncers");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Syncer failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -49,26 +49,33 @@ class SyncerListPage extends BaseListPage {
const newSyncer = this.newSyncer();
SyncerBackend.addSyncer(newSyncer)
.then((res) => {
this.props.history.push({pathname: `/syncers/${newSyncer.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/syncers/${newSyncer.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Syncer failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
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},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Syncer failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -229,7 +236,7 @@ class SyncerListPage extends BaseListPage {
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="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -13,32 +13,33 @@
// limitations under the License.
import * as Setting from "./Setting";
import i18next from "i18next";
export function sendTestEmail(provider, email) {
testEmailProvider(provider, email)
.then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully send email");
} else {
Setting.showMessage("error", res.msg);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
export function connectSmtpServer(provider) {
testEmailProvider(provider)
.then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully connecting smtp server");
} else {
Setting.showMessage("error", res.msg);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -167,8 +167,8 @@ class TokenEditPage extends React.Component {
const token = Setting.deepCopy(this.state.token);
TokenBackend.updateToken(this.state.token.owner, this.state.tokenName, token)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
tokenName: this.state.token.name,
});
@ -179,22 +179,26 @@ class TokenEditPage extends React.Component {
this.props.history.push(`/tokens/${this.state.token.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateTokenField("name", this.state.tokenName);
}
})
.catch(error => {
Setting.showMessage("error", `failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteToken() {
TokenBackend.deleteToken(this.state.token)
.then(() => {
this.props.history.push("/tokens");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/tokens");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Token failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -42,26 +42,33 @@ class TokenListPage extends BaseListPage {
const newToken = this.newToken();
TokenBackend.addToken(newToken)
.then((res) => {
this.props.history.push({pathname: `/tokens/${newToken.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/tokens/${newToken.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Token failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteToken(i) {
TokenBackend.deleteToken(this.state.data[i])
.then((res) => {
Setting.showMessage("success", "Token deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Token failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -198,7 +205,7 @@ class TokenListPage extends BaseListPage {
title={`Sure to delete token: ${record.name} ?`}
onConfirm={() => this.deleteToken(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -49,6 +49,7 @@ class UserEditPage extends React.Component {
applications: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
loading: true,
returnUrl: null,
};
}
@ -57,6 +58,7 @@ class UserEditPage extends React.Component {
this.getOrganizations();
this.getApplicationsByOrganization(this.state.organizationName);
this.getUserApplication();
this.setReturnUrl();
}
getUser() {
@ -100,9 +102,14 @@ class UserEditPage extends React.Component {
});
}
getReturnUrl() {
setReturnUrl() {
const searchParams = new URLSearchParams(this.props.location.search);
return searchParams.get("returnUrl");
const returnUrl = searchParams.get("returnUrl");
if (returnUrl !== null) {
this.setState({
returnUrl: returnUrl,
});
}
}
parseUserField(key, value) {
@ -242,7 +249,7 @@ class UserEditPage extends React.Component {
</Col>
</Row>
<Row style={{marginTop: "20px"}}>
<CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} account={this.props.account} />
<CropperDiv buttonText={`${i18next.t("user:Upload a photo")}...`} title={i18next.t("user:Upload a photo")} user={this.state.user} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />
</Row>
</Col>
</Row>
@ -604,8 +611,8 @@ class UserEditPage extends React.Component {
const user = Setting.deepCopy(this.state.user);
UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
organizationName: this.state.user.owner,
userName: this.state.user.name,
@ -619,30 +626,33 @@ class UserEditPage extends React.Component {
}
} else {
if (willExist) {
const returnUrl = this.getReturnUrl();
if (returnUrl) {
window.location.href = returnUrl;
if (this.state.returnUrl) {
window.location.href = this.state.returnUrl;
}
}
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateUserField("owner", this.state.organizationName);
this.updateUserField("name", this.state.userName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteUser() {
UserBackend.deleteUser(this.state.user)
.then(() => {
this.props.history.push("/users");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/users");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -72,26 +72,33 @@ class UserListPage extends BaseListPage {
const newUser = this.newUser();
UserBackend.addUser(newUser)
.then((res) => {
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `User failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteUser(i) {
UserBackend.deleteUser(this.state.data[i])
.then((res) => {
Setting.showMessage("success", "User deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -346,7 +353,7 @@ class UserListPage extends BaseListPage {
title={`Sure to delete user: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)}
>
<Button disabled={disabled} style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button disabled={disabled} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -26,7 +26,7 @@ class WebAuthnCredentialTable extends React.Component {
registerWebAuthn() {
UserWebauthnBackend.registerWebauthnCredential().then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
Setting.showMessage("success", "Successfully added webauthn credentials");
} else {
Setting.showMessage("error", res.msg);
@ -34,7 +34,7 @@ class WebAuthnCredentialTable extends React.Component {
this.props.refresh();
}).catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -51,7 +51,7 @@ class WebAuthnCredentialTable extends React.Component {
width: "170px",
render: (text, record, index) => {
return (
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="primary" danger onClick={() => {this.deleteRow(this.props.table, index);}}>
{i18next.t("general:Delete")}
</Button>
);

View File

@ -296,8 +296,8 @@ class WebhookEditPage extends React.Component {
const webhook = Setting.deepCopy(this.state.webhook);
WebhookBackend.updateWebhook(this.state.webhook.owner, this.state.webhookName, webhook)
.then((res) => {
if (res.msg === "") {
Setting.showMessage("success", "Successfully saved");
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
webhookName: this.state.webhook.name,
});
@ -308,22 +308,26 @@ class WebhookEditPage extends React.Component {
this.props.history.push(`/webhooks/${this.state.webhook.name}`);
}
} else {
Setting.showMessage("error", res.msg);
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateWebhookField("name", this.state.webhookName);
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteWebhook() {
WebhookBackend.deleteWebhook(this.state.webhook)
.then(() => {
this.props.history.push("/webhooks");
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/webhooks");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Webhook failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}

View File

@ -42,26 +42,33 @@ class WebhookListPage extends BaseListPage {
const newWebhook = this.newWebhook();
WebhookBackend.addWebhook(newWebhook)
.then((res) => {
this.props.history.push({pathname: `/webhooks/${newWebhook.name}`, mode: "add"});
}
)
if (res.status === "ok") {
this.props.history.push({pathname: `/webhooks/${newWebhook.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Webhook failed to add: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteWebhook(i) {
WebhookBackend.deleteWebhook(this.state.data[i])
.then((res) => {
Setting.showMessage("success", "Webhook deleted successfully");
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
}
)
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `Webhook failed to delete: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
@ -194,7 +201,7 @@ class WebhookListPage extends BaseListPage {
title={`Sure to delete webhook: ${record.name} ?`}
onConfirm={() => this.deleteWebhook(index)}
>
<Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);

View File

@ -126,7 +126,7 @@ class AuthCallback extends React.Component {
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
msg += "Now you can visit apps protected by Casdoor.";
}
Util.showMessage("success", msg);
Setting.showMessage("success", msg);
if (casService !== "") {
const st = res.data;
@ -135,7 +135,7 @@ class AuthCallback extends React.Component {
window.location.href = newUrl.toString();
}
} else {
Util.showMessage("error", `Failed to log in: ${res.msg}`);
Setting.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
return;
@ -148,7 +148,7 @@ class AuthCallback extends React.Component {
if (res.status === "ok") {
const responseType = this.getResponseType();
if (responseType === "login") {
Util.showMessage("success", "Logged in successfully");
Setting.showMessage("success", "Logged in successfully");
// Setting.goToLinkSoft(this, "/");
const link = Setting.getFromLink();
@ -156,7 +156,7 @@ class AuthCallback extends React.Component {
} else if (responseType === "code") {
const code = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
// Util.showMessage("success", `Authorization code: ${res.data}`);
// Setting.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token") {
const token = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);

View File

@ -61,10 +61,7 @@ class ForgetPage extends React.Component {
if (this.state.applicationName !== undefined) {
this.getApplication();
} else {
Util.showMessage(
"error",
i18next.t("forget:Unknown forget type: ") + this.state.type
);
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
}
}
@ -102,6 +99,13 @@ class ForgetPage extends React.Component {
if (res.status === "ok") {
const phone = res.data.phone;
const email = res.data.email;
const saveFields = () => {
if (this.state.isFixed) {
forms.step2.setFieldsValue({email: this.state.fixedContent});
this.setState({username: this.state.fixedContent});
}
this.setState({current: 1});
};
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
if (phone !== "" && email === "") {
@ -116,19 +120,15 @@ class ForgetPage extends React.Component {
switch (res.data2) {
case "email":
this.setState({isFixed: true, fixedContent: email, verifyType: "email"});
this.setState({isFixed: true, fixedContent: email, verifyType: "email"}, () => {saveFields();});
break;
case "phone":
this.setState({isFixed: true, fixedContent: phone, verifyType: "phone"});
this.setState({isFixed: true, fixedContent: phone, verifyType: "phone"}, () => {saveFields();});
break;
default:
saveFields();
break;
}
if (this.state.isFixed) {
forms.step2.setFieldsValue({email: this.state.fixedContent});
this.setState({username: this.state.fixedContent});
}
this.setState({current: 1});
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
@ -136,26 +136,28 @@ class ForgetPage extends React.Component {
break;
case "step2":
const oAuthParams = Util.getOAuthGetParameters();
const login = () => {
AuthBackend.login({
application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"),
username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login",
}, oAuthParams).then(res => {
if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
});
};
if (this.state.verifyType === "email") {
this.setState({username: this.state.email});
this.setState({username: this.state.email}, () => {login();});
} else if (this.state.verifyType === "phone") {
this.setState({username: this.state.phone});
this.setState({username: this.state.phone}, () => {login();});
}
AuthBackend.login({
application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"),
username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login",
}, oAuthParams).then(res => {
if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
} else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
}
});
break;
default:
break;

View File

@ -30,8 +30,6 @@ import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
import {CaptchaModal} from "../common/CaptchaModal";
const {TabPane} = Tabs;
class LoginPage extends React.Component {
constructor(props) {
super(props);
@ -67,7 +65,7 @@ class LoginPage extends React.Component {
} else if (this.state.type === "saml") {
this.getSamlApplication();
} else {
Util.showMessage("error", `Unknown authentication type: ${this.state.type}`);
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
}
@ -92,7 +90,7 @@ class LoginPage extends React.Component {
application: res.data,
});
} else {
// Util.showMessage("error", res.msg);
// Setting.showMessage("error", res.msg);
this.setState({
application: res.data,
msg: res.msg,
@ -122,7 +120,7 @@ class LoginPage extends React.Component {
applicationName: res.data.name,
});
} else {
Util.showMessage("error", res.msg);
Setting.showMessage("error", res.msg);
}
});
}
@ -269,7 +267,7 @@ class LoginPage extends React.Component {
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
msg += "Now you can visit apps protected by Casdoor.";
}
Util.showMessage("success", msg);
Setting.showMessage("success", msg);
this.setState({openCaptchaModal: false});
@ -281,7 +279,7 @@ class LoginPage extends React.Component {
}
} else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`);
Setting.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
} else {
@ -295,13 +293,13 @@ class LoginPage extends React.Component {
const responseType = values["type"];
if (responseType === "login") {
Util.showMessage("success", "Logged in successfully");
Setting.showMessage("success", "Logged in successfully");
const link = Setting.getFromLink();
Setting.goToLink(link);
} else if (responseType === "code") {
this.postCodeLoginAction(res);
// Util.showMessage("success", `Authorization code: ${res.data}`);
// Setting.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token") {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
@ -312,7 +310,7 @@ class LoginPage extends React.Component {
}
} else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`);
Setting.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
}
@ -335,8 +333,8 @@ class LoginPage extends React.Component {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
title={i18next.t("application:Sign Up Error")}
subTitle={i18next.t("application:The application does not allow to sign up new account")}
extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{
@ -368,7 +366,7 @@ class LoginPage extends React.Component {
rules={[
{
required: true,
message: "Please input your application!",
message: i18next.t("application:Please input your application!"),
},
]}
>
@ -379,7 +377,7 @@ class LoginPage extends React.Component {
rules={[
{
required: true,
message: "Please input your organization!",
message: i18next.t("application:Please input your organization!"),
},
]}
>
@ -673,7 +671,7 @@ class LoginPage extends React.Component {
}),
})
.then(res => res.json()).then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
const responseType = values["type"];
if (responseType === "code") {
this.postCodeLoginAction(res);
@ -681,7 +679,7 @@ class LoginPage extends React.Component {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
} else {
Setting.showMessage("success", "Successfully logged in with webauthn credentials");
Setting.showMessage("success", i18next.t("login:Successfully logged in with webauthn credentials"));
Setting.goToLink("/");
}
} else {
@ -689,7 +687,7 @@ class LoginPage extends React.Component {
}
})
.catch(error => {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
});
});
}
@ -734,21 +732,16 @@ class LoginPage extends React.Component {
renderMethodChoiceBox() {
const application = this.getApplicationObj();
const items = [
{label: i18next.t("login:Password"), key: "password"},
];
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification Code"), key: "verificationCode"}) : null;
application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
if (application.enableCodeSignin || application.enableWebAuthn) {
return (
<div>
<Tabs size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
<TabPane tab={i18next.t("login:Password")} key="password" />
{
!application.enableCodeSignin ? null : (
<TabPane tab={i18next.t("login:Verification Code")} key="verificationCode" />
)
}
{
!application.enableWebAuthn ? null : (
<TabPane tab={i18next.t("login:WebAuthn")} key="webAuthn" />
)
}
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
</Tabs>
</div>
);
@ -781,7 +774,7 @@ class LoginPage extends React.Component {
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel">
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />

View File

@ -203,9 +203,9 @@ class PromptPage extends React.Component {
const user = Setting.deepCopy(this.state.user);
UserBackend.updateUser(this.state.user.owner, this.state.user.name, user)
.then((res) => {
if (res.msg === "") {
if (res.status === "ok") {
if (isFinal) {
Setting.showMessage("success", "Successfully saved");
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.logout();
}
@ -217,7 +217,7 @@ class PromptPage extends React.Component {
})
.catch(error => {
if (isFinal) {
Setting.showMessage("error", `Failed to connect to server: ${error}`);
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
}
});
}
@ -232,8 +232,8 @@ class PromptPage extends React.Component {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"You are unexpected to see this prompt page"}
title={i18next.t("application:Sign Up Error")}
subTitle={i18next.t("application:You are unexpected to see this prompt page")}
extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{

View File

@ -16,7 +16,6 @@ import React from "react";
import {Button, Result} from "antd";
import i18next from "i18next";
import {authConfig} from "./Auth";
import * as Util from "./Util";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Setting from "../Setting";
@ -34,7 +33,7 @@ class ResultPage extends React.Component {
if (this.state.applicationName !== undefined) {
this.getApplication();
} else {
Util.showMessage("error", `Unknown application name: ${this.state.applicationName}`);
Setting.showMessage("error", `Unknown application name: ${this.state.applicationName}`);
}
}

View File

@ -79,7 +79,7 @@ class SamlCallback extends React.Component {
if (res.status === "ok") {
const responseType = this.getResponseType(redirectUri);
if (responseType === "login") {
Util.showMessage("success", "Logged in successfully");
Setting.showMessage("success", "Logged in successfully");
Setting.goToLink("/");
} else if (responseType === "code") {
const code = res.data;

View File

@ -94,7 +94,7 @@ class SignupPage extends React.Component {
if (applicationName !== undefined) {
this.getApplication(applicationName);
} else {
Util.showMessage("error", `Unknown application name: ${applicationName}`);
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
}
}
@ -510,7 +510,7 @@ class SignupPage extends React.Component {
return (
<Modal
title={i18next.t("signup:Terms of Use")}
visible={this.state.isTermsOfUseVisible}
open={this.state.isTermsOfUseVisible}
width={"55vw"}
closable={false}
okText={i18next.t("signup:Accept")}
@ -539,8 +539,8 @@ class SignupPage extends React.Component {
return (
<Result
status="error"
title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"}
title={i18next.t("application:Sign Up Error")}
subTitle={i18next.t("application:The application does not allow to sign up new account")}
extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{
@ -632,7 +632,7 @@ class SignupPage extends React.Component {
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel" >
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />

View File

@ -13,19 +13,11 @@
// limitations under the License.
import React from "react";
import {Alert, Button, Result, message} from "antd";
import {Alert, Button, Result} from "antd";
import {getWechatMessageEvent} from "./AuthBackend";
import * as Setting from "../Setting";
import * as Provider from "./Provider";
export function showMessage(type, text) {
if (type === "success") {
message.success(text);
} else if (type === "error") {
message.error(text);
}
}
export function renderMessage(msg) {
if (msg !== null) {
return (
@ -36,7 +28,7 @@ export function renderMessage(msg) {
description={msg}
type="error"
action={
<Button size="small" danger>
<Button size="small" type="primary" danger>
Detail
</Button>
}

View File

@ -70,6 +70,41 @@ export function deleteAdapter(Adapter) {
}).then(res => res.json());
}
export function UpdatePolicy(owner, name, policy) {
// eslint-disable-next-line no-console
console.log(policy);
return fetch(`${Setting.ServerUrl}/api/update-policy?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(policy),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function AddPolicy(owner, name, policy) {
return fetch(`${Setting.ServerUrl}/api/add-policy?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(policy),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function RemovePolicy(owner, name, policy) {
return fetch(`${Setting.ServerUrl}/api/remove-policy?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(policy),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function syncPolicies(owner, name) {
return fetch(`${Setting.ServerUrl}/api/sync-policies?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",

View File

@ -148,7 +148,7 @@ export const CaptchaModal = ({
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
visible={visible}
open={visible}
width={348}
footer={renderFooter()}
>

View File

@ -105,6 +105,21 @@ export const CaptchaWidget = ({captchaType, subType, siteKey, clientSecret, onCh
}, 500);
break;
}
case "Cloudflare Turnstile": {
const tTimer = setInterval(() => {
if (!window.turnstile) {
loadScript("https://challenges.cloudflare.com/turnstile/v0/api.js");
}
if (window.turnstile && window.turnstile.render) {
window.turnstile.render("#captcha", {
sitekey: siteKey,
callback: onChange,
});
clearInterval(tTimer);
}
}, 300);
break;
}
default:
break;
}

View File

@ -156,7 +156,7 @@ export const CountDownInput = (props) => {
maskClosable={false}
destroyOnClose={true}
title={i18next.t("general:Captcha")}
visible={visible}
open={visible}
okText={i18next.t("user:OK")}
cancelText={i18next.t("user:Cancel")}
onOk={handleOk}

View File

@ -0,0 +1,307 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {DeleteOutlined, EditOutlined} from "@ant-design/icons";
import {Button, Input, Popconfirm, Table, Tooltip} from "antd";
import * as Setting from "../Setting";
import * as AdapterBackend from "../backend/AdapterBackend";
import i18next from "i18next";
class PolicyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
policyLists: [],
loading: false,
editingIndex: "",
oldPolicy: "",
add: false,
};
}
UNSAFE_componentWillMount() {
if (this.props.mode === "edit") {
this.synPolicies();
}
}
isEditing = (index) => {
return index === this.state.editingIndex;
};
edit = (record, index) => {
this.setState({editingIndex: index, oldPolicy: Setting.deepCopy(record)});
};
cancel = (table, index) => {
Object.keys(table[index]).forEach((key) => {
table[index][key] = this.state.oldPolicy[key];
});
this.updateTable(table);
this.setState({editingIndex: "", oldPolicy: ""});
if (this.state.add) {
this.deleteRow(this.state.policyLists, index);
this.setState({add: false});
}
};
updateTable(table) {
this.setState({policyLists: table});
}
updateField(table, index, key, value) {
table[index][key] = value;
this.updateTable(table);
}
addRow(table) {
const row = {Ptype: "p"};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row, "top");
this.updateTable(table);
this.edit(row, 0);
this.setState({add: true});
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.updateTable(table);
}
save(table, i) {
this.state.add ? this.addPolicy(table, i) : this.updatePolicy(table, i);
}
synPolicies() {
this.setState({loading: true});
AdapterBackend.syncPolicies(this.props.owner, this.props.name)
.then((res) => {
if (res.status === "ok") {
this.setState({policyLists: res});
} else {
Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`);
}
this.setState({loading: false});
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
updatePolicy(table, i) {
AdapterBackend.UpdatePolicy(this.props.owner, this.props.name, [this.state.oldPolicy, table[i]]).then(res => {
if (res.status === "ok") {
this.setState({editingIndex: "", oldPolicy: ""});
Setting.showMessage("success", i18next.t("general:Successfully saved"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
}
});
}
addPolicy(table, i) {
AdapterBackend.AddPolicy(this.props.owner, this.props.name, table[i]).then(res => {
if (res.status === "ok") {
this.setState({editingIndex: "", oldPolicy: "", add: false});
if (res.data !== "Affected") {
Setting.showMessage("info", i18next.t("adapter:Repeated policy rules"));
} else {
Setting.showMessage("success", i18next.t("general:Successfully added"));
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
});
}
deletePolicy(table, i) {
AdapterBackend.RemovePolicy(this.props.owner, this.props.name, table[i]).then(res => {
if (res.status === "ok") {
table = Setting.deleteRow(table, i);
this.updateTable(table);
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
} else {
Setting.showMessage("error", i18next.t("general:Failed to delete"));
}
});
}
renderTable(table) {
const columns = [
{
title: "Rule Type",
dataIndex: "Ptype",
width: "100px",
// render: (text, record, index) => {
// const editing = this.isEditing(index);
// return (
// editing ?
// <Input value={text} onChange={e => {
// this.updateField(table, index, "Ptype", e.target.value);
// }} />
// : text
// );
// },
},
{
title: "V0",
dataIndex: "V0",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V0", e.target.value);
}} />
: text
);
},
},
{
title: "V1",
dataIndex: "V1",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V1", e.target.value);
}} />
: text
);
},
},
{
title: "V2",
dataIndex: "V2",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V2", e.target.value);
}} />
: text
);
},
},
{
title: "V3",
dataIndex: "V3",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V3", e.target.value);
}} />
: text
);
},
},
{
title: "V4",
dataIndex: "V4",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V4", e.target.value);
}} />
: text
);
},
},
{
title: "V5",
dataIndex: "V5",
width: "100px",
render: (text, record, index) => {
const editing = this.isEditing(index);
return (
editing ?
<Input value={text} onChange={e => {
this.updateField(table, index, "V5", e.target.value);
}} />
: text
);
},
},
{
title: "Option",
key: "option",
width: "100px",
render: (text, record, index) => {
const editable = this.isEditing(index);
return editable ? (
<span>
<Button style={{marginRight: 8}} onClick={() => this.save(table, index)}>
Save
</Button>
<Popconfirm title="Sure to cancel?" onConfirm={() => this.cancel(table, index)}>
<a>Cancel</a>
</Popconfirm>
</span>
) : (
<div>
<Tooltip placement="topLeft" title="Edit">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => this.edit(record, index)} />
</Tooltip>
<Tooltip placement="topLeft" title="Delete">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" onClick={() => this.deletePolicy(table, index)} />
</Tooltip>
</div>
);
},
}];
return (
<Table
pagination={{
defaultPageSize: 10,
}}
columns={columns} dataSource={table} rowKey="index" size="middle" bordered
loading={this.state.loading}
title={() => (
<div>
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>
);
}
render() {
return (<>
<Button type="primary" onClick={() => {this.synPolicies();}}>
{i18next.t("adapter:Sync")}
</Button>
{
this.renderTable(this.state.policyLists)
}
</>
);
}
}
export default PolicyTable;

View File

@ -89,7 +89,7 @@ i18n.use(initReactI18next).init({
keySeparator: false,
interpolation: {
escapeValue: false,
escapeValue: true,
},
// debug: true,
saveMissing: true,

View File

@ -18,6 +18,7 @@ import "react-app-polyfill/stable";
import React from "react";
import {createRoot} from "react-dom/client";
import "./index.css";
import "./App.less";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import {BrowserRouter} from "react-router-dom";

View File

@ -7,9 +7,11 @@
},
"adapter": {
"Edit Adapter": "Edit Adapter",
"Failed to sync policies: ": "Failed to sync policies: ",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules",
"Sync": "Sync"
},
"application": {
@ -47,6 +49,8 @@
"None": "None",
"Password ON": "Passwort AN",
"Password ON - Tooltip": "Whether to allow password login",
"Please input your application!": "Please input your application!",
"Please input your organization!": "Please input your organization!",
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "Weiterleitungs-URL",
@ -62,15 +66,18 @@
"Side panel HTML": "Side panel HTML",
"Side panel HTML - Edit": "Side panel HTML - Edit",
"Side panel HTML - Tooltip": "Side panel HTML - Tooltip",
"Sign Up Error": "Sign Up Error",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Anmeldesitzung",
"Signup items": "Artikel registrieren",
"Signup items - Tooltip": "Signup items that need to be filled in when users register",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
"Token expire": "Token läuft ab",
"Token expire - Tooltip": "Token läuft ab - Tooltip",
"Token format": "Token-Format",
"Token format - Tooltip": "Token-Format - Tooltip"
"Token format - Tooltip": "Token-Format - Tooltip",
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
},
"cert": {
"Bit size": "Bitgröße",
@ -118,7 +125,7 @@
"Please input your username!": "Bitte geben Sie Ihren Benutzernamen ein!",
"Reset": "Reset",
"Retrieve password": "Passwort abrufen",
"Unknown forget type": "Unknown forget type",
"Unknown forget type: ": "Unknown forget type: ",
"Verify": "Überprüfen"
},
"general": {
@ -157,6 +164,10 @@
"Edit": "Bearbeiten",
"Email": "E-Mail",
"Email - Tooltip": "email",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
"Failed to save": "Failed to save",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
@ -227,6 +238,9 @@
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",
@ -292,6 +306,7 @@
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Mit {type} anmelden",
"Signing in...": "Anmelden...",
"Successfully logged in with webauthn credentials": "Successfully logged in with webauthn credentials",
"The input is not valid Email or Phone!": "Die Eingabe ist keine gültige E-Mail oder Telefon!",
"To access": "Zu Zugriff",
"Verification Code": "Verification Code",
@ -555,6 +570,7 @@
"UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"Visible": "Visible",
"admin (share)": "admin (share)",
"alertType": "alarmtyp"
},
"record": {

View File

@ -7,9 +7,11 @@
},
"adapter": {
"Edit Adapter": "Edit Adapter",
"Failed to sync policies: ": "Failed to sync policies: ",
"New Adapter": "New Adapter",
"Policies": "Policies",
"Policies - Tooltip": "Policies - Tooltip",
"Repeated policy rules": "Repeated policy rules",
"Sync": "Sync"
},
"application": {
@ -47,6 +49,8 @@
"None": "None",
"Password ON": "Password ON",
"Password ON - Tooltip": "Password ON - Tooltip",
"Please input your application!": "Please input your application!",
"Please input your organization!": "Please input your organization!",
"Please select a HTML file": "Please select a HTML file",
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Redirect URL": "Redirect URL",
@ -62,15 +66,18 @@
"Side panel HTML": "Side panel HTML",
"Side panel HTML - Edit": "Side panel HTML - Edit",
"Side panel HTML - Tooltip": "Side panel HTML - Tooltip",
"Sign Up Error": "Sign Up Error",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Signin session",
"Signup items": "Signup items",
"Signup items - Tooltip": "Signup items - Tooltip",
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
"Token expire": "Token expire",
"Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip"
"Token format - Tooltip": "Token format - Tooltip",
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
},
"cert": {
"Bit size": "Bit size",
@ -118,7 +125,7 @@
"Please input your username!": "Please input your username!",
"Reset": "Reset",
"Retrieve password": "Retrieve password",
"Unknown forget type": "Unknown forget type",
"Unknown forget type: ": "Unknown forget type: ",
"Verify": "Verify"
},
"general": {
@ -157,6 +164,10 @@
"Edit": "Edit",
"Email": "Email",
"Email - Tooltip": "Email - Tooltip",
"Failed to add": "Failed to add",
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
"Failed to save": "Failed to save",
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon - Tooltip",
"First name": "First name",
@ -227,6 +238,9 @@
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.",
"State": "State",
"State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",
@ -292,6 +306,7 @@
"Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}",
"Signing in...": "Signing in...",
"Successfully logged in with webauthn credentials": "Successfully logged in with webauthn credentials",
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
"To access": "To access",
"Verification Code": "Verification Code",
@ -555,6 +570,7 @@
"UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"Visible": "Visible",
"admin (share)": "admin (share)",
"alertType": "alertType"
},
"record": {

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