mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-30 08:10:29 +08:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
984a69cb4b | ||
![]() |
098a1ece68 | ||
![]() |
ad6f2ad2e1 | ||
![]() |
2d55252261 | ||
![]() |
30ea3a1335 | ||
![]() |
b7d78d1e27 | ||
![]() |
3d5a645a3b | ||
![]() |
4ad21e7781 | ||
![]() |
b99a0c3ca2 | ||
![]() |
e1842f6b80 | ||
![]() |
0781a3835d | ||
![]() |
98a99f0215 | ||
![]() |
681b086de0 | ||
![]() |
cdcc0b39e2 | ||
![]() |
8eb68ba817 | ||
![]() |
8d1ae4ea08 | ||
![]() |
9c8ea027ef | ||
![]() |
aaa56d3354 | ||
![]() |
b45c49d3a4 | ||
![]() |
5b3202cc89 | ||
![]() |
5280f872dc | ||
![]() |
fd61b963d5 | ||
![]() |
a8937d3046 | ||
![]() |
32b05047dc | ||
![]() |
117ee509cf | ||
![]() |
daf3d374b5 | ||
![]() |
337ee2faef | ||
![]() |
989fec72bf | ||
![]() |
76eb606335 | ||
![]() |
c6146a9149 |
91
.github/workflows/build.yml
vendored
91
.github/workflows/build.yml
vendored
@@ -9,18 +9,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: Tests
|
||||
run: |
|
||||
go test -v $(go list ./...) -tags skipCi
|
||||
@@ -31,14 +32,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
# cache
|
||||
- uses: c-hive/gha-yarn-cache@v2
|
||||
with:
|
||||
directory: ./web
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install && CI=false yarn run build
|
||||
working-directory: ./web
|
||||
|
||||
@@ -47,10 +46,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
- run: go version
|
||||
- name: Build
|
||||
run: |
|
||||
@@ -63,13 +63,14 @@ jobs:
|
||||
needs: [ go-tests ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
cache-dependency-path: ./go.mod
|
||||
|
||||
# gen a dummy config file
|
||||
- run: touch dummy.yml
|
||||
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
@@ -82,35 +83,35 @@ jobs:
|
||||
needs: [ go-tests ]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
image: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: casdoor
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: back start
|
||||
cache-dependency-path: ./go.mod
|
||||
- name: start backend
|
||||
run: nohup go run ./main.go &
|
||||
working-directory: ./
|
||||
- name: front install
|
||||
run: yarn install
|
||||
working-directory: ./web
|
||||
- name: front start
|
||||
run: nohup yarn start &
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
working-directory: ./web
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install
|
||||
working-directory: ./web
|
||||
- uses: cypress-io/github-action@v5
|
||||
with:
|
||||
start: yarn start
|
||||
wait-on: 'http://localhost:7001'
|
||||
wait-on-timeout: 180
|
||||
working-directory: ./web
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
@@ -121,7 +122,7 @@ jobs:
|
||||
if: always()
|
||||
with:
|
||||
name: cypress-videos
|
||||
path: ./web/cypress/videos
|
||||
path: ./web/cypress/videos
|
||||
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
@@ -130,11 +131,11 @@ jobs:
|
||||
needs: [ frontend, backend, linter, e2e ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: -1
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
@@ -166,10 +167,10 @@ jobs:
|
||||
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||
then
|
||||
echo ::set-output name=push::'true'
|
||||
|
||||
|
||||
else
|
||||
echo ::set-output name=push::'false'
|
||||
|
||||
|
||||
fi
|
||||
|
||||
- name: Set up QEMU
|
||||
|
@@ -87,6 +87,7 @@ p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
p, *, *, POST, /api/webhook, *, *
|
||||
p, *, *, GET, /api/get-webhook-event, *, *
|
||||
p, *, *, *, /api/login/oauth, *, *
|
||||
@@ -107,6 +108,7 @@ p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
p, *, *, GET, /api/get-captcha, *, *
|
||||
p, *, *, POST, /api/verify-captcha, *, *
|
||||
p, *, *, POST, /api/verify-code, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
|
@@ -20,5 +20,5 @@ staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
ldapServerPort = 389
|
||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vi
|
||||
languages = en,zh,es,fr,de,id,ja,ko,ru,vn
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
|
@@ -50,6 +50,7 @@ type RequestForm struct {
|
||||
Region string `json:"region"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
@@ -369,6 +370,43 @@ func (c *ApiController) GetUserinfo() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserinfo2
|
||||
// LaravelResponse
|
||||
// @Title UserInfo2
|
||||
// @Tag Account API
|
||||
// @Description return Laravel compatible user information according to OAuth 2.0
|
||||
// @Success 200 {object} LaravelResponse The Response object
|
||||
// @router /user [get]
|
||||
func (c *ApiController) GetUserinfo2() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
response := LaravelResponse{
|
||||
Id: user.Id,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
EmailVerifiedAt: user.CreatedTime,
|
||||
CreatedAt: user.CreatedTime,
|
||||
UpdatedAt: user.UpdatedTime,
|
||||
}
|
||||
|
||||
c.Data["json"] = response
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetCaptcha ...
|
||||
// @Tag Login API
|
||||
// @Title GetCaptcha
|
||||
|
@@ -246,33 +246,14 @@ func (c *ApiController) Login() {
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
var verificationCodeType string
|
||||
var checkResult string
|
||||
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
var checkDest string
|
||||
if strings.Contains(form.Username, "@") {
|
||||
verificationCodeType = "email"
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
}
|
||||
checkDest = form.Username
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
return
|
||||
}
|
||||
if verificationCodeType == "phone" {
|
||||
|
||||
verificationCodeType := object.GetVerifyType(form.Username)
|
||||
var checkDest string
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
@@ -281,7 +262,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
|
||||
checkResult = object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, form.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
return
|
||||
@@ -334,7 +316,13 @@ func (c *ApiController) Login() {
|
||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
var application *object.Application
|
||||
if form.ClientId != "" {
|
||||
application = object.GetApplicationByClientId(form.ClientId)
|
||||
} else {
|
||||
application = object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
|
||||
return
|
||||
|
123
controllers/chat.go
Normal file
123
controllers/chat.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetChats
|
||||
// @Title GetChats
|
||||
// @Tag Chat API
|
||||
// @Description get chats
|
||||
// @Param owner query string true "The owner of chats"
|
||||
// @Success 200 {array} object.Chat The Response object
|
||||
// @router /get-chats [get]
|
||||
func (c *ApiController) GetChats() {
|
||||
owner := c.Input().Get("owner")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedChats(object.GetChats(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetChatCount(owner, field, value)))
|
||||
chats := object.GetMaskedChats(object.GetPaginationChats(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(chats, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetChat
|
||||
// @Title GetChat
|
||||
// @Tag Chat API
|
||||
// @Description get chat
|
||||
// @Param id query string true "The id ( owner/name ) of the chat"
|
||||
// @Success 200 {object} object.Chat The Response object
|
||||
// @router /get-chat [get]
|
||||
func (c *ApiController) GetChat() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedChat(object.GetChat(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateChat
|
||||
// @Title UpdateChat
|
||||
// @Tag Chat API
|
||||
// @Description update chat
|
||||
// @Param id query string true "The id ( owner/name ) of the chat"
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-chat [post]
|
||||
func (c *ApiController) UpdateChat() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateChat(id, &chat))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddChat
|
||||
// @Title AddChat
|
||||
// @Tag Chat API
|
||||
// @Description add chat
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-chat [post]
|
||||
func (c *ApiController) AddChat() {
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddChat(&chat))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteChat
|
||||
// @Title DeleteChat
|
||||
// @Tag Chat API
|
||||
// @Description delete chat
|
||||
// @Param body body object.Chat true "The details of the chat"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-chat [post]
|
||||
func (c *ApiController) DeleteChat() {
|
||||
var chat object.Chat
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &chat)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteChat(&chat))
|
||||
c.ServeJSON()
|
||||
}
|
123
controllers/message.go
Normal file
123
controllers/message.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetMessages
|
||||
// @Title GetMessages
|
||||
// @Tag Message API
|
||||
// @Description get messages
|
||||
// @Param owner query string true "The owner of messages"
|
||||
// @Success 200 {array} object.Message The Response object
|
||||
// @router /get-messages [get]
|
||||
func (c *ApiController) GetMessages() {
|
||||
owner := c.Input().Get("owner")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedMessages(object.GetMessages(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetMessageCount(owner, field, value)))
|
||||
messages := object.GetMaskedMessages(object.GetPaginationMessages(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(messages, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetMessage
|
||||
// @Title GetMessage
|
||||
// @Tag Message API
|
||||
// @Description get message
|
||||
// @Param id query string true "The id ( owner/name ) of the message"
|
||||
// @Success 200 {object} object.Message The Response object
|
||||
// @router /get-message [get]
|
||||
func (c *ApiController) GetMessage() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessage(object.GetMessage(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateMessage
|
||||
// @Title UpdateMessage
|
||||
// @Tag Message API
|
||||
// @Description update message
|
||||
// @Param id query string true "The id ( owner/name ) of the message"
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-message [post]
|
||||
func (c *ApiController) UpdateMessage() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateMessage(id, &message))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddMessage
|
||||
// @Title AddMessage
|
||||
// @Tag Message API
|
||||
// @Description add message
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-message [post]
|
||||
func (c *ApiController) AddMessage() {
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddMessage(&message))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteMessage
|
||||
// @Title DeleteMessage
|
||||
// @Tag Message API
|
||||
// @Description delete message
|
||||
// @Param body body object.Message true "The details of the message"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-message [post]
|
||||
func (c *ApiController) DeleteMessage() {
|
||||
var message object.Message
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &message)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteMessage(&message))
|
||||
c.ServeJSON()
|
||||
}
|
@@ -21,9 +21,8 @@ import (
|
||||
// GetSystemInfo
|
||||
// @Title GetSystemInfo
|
||||
// @Tag System API
|
||||
// @Description get user's system info
|
||||
// @Param id query string true "The id ( owner/name ) of the user"
|
||||
// @Success 200 {object} object.SystemInfo The Response object
|
||||
// @Description get system info like CPU and memory usage
|
||||
// @Success 200 {object} util.SystemInfo The Response object
|
||||
// @router /get-system-info [get]
|
||||
func (c *ApiController) GetSystemInfo() {
|
||||
_, ok := c.RequireAdmin()
|
||||
@@ -43,8 +42,8 @@ func (c *ApiController) GetSystemInfo() {
|
||||
// GetVersionInfo
|
||||
// @Title GetVersionInfo
|
||||
// @Tag System API
|
||||
// @Description get local git repo's latest release version info
|
||||
// @Success 200 {string} local latest version hash of Casdoor
|
||||
// @Description get version info like Casdoor release version and commit ID
|
||||
// @Success 200 {object} util.VersionInfo The Response object
|
||||
// @router /get-version-info [get]
|
||||
func (c *ApiController) GetVersionInfo() {
|
||||
versionInfo, err := util.GetVersionInfo()
|
||||
|
@@ -84,6 +84,7 @@ func (c *ApiController) GetUsers() {
|
||||
// @Param owner query string false "The owner of the user"
|
||||
// @Param email query string false "The email of the user"
|
||||
// @Param phone query string false "The phone of the user"
|
||||
// @Param userId query string false "The userId of the user"
|
||||
// @Success 200 {object} object.User The Response object
|
||||
// @router /get-user [get]
|
||||
func (c *ApiController) GetUser() {
|
||||
@@ -94,13 +95,13 @@ func (c *ApiController) GetUser() {
|
||||
|
||||
owner := c.Input().Get("owner")
|
||||
if owner == "" {
|
||||
owner, _ = util.GetOwnerAndNameFromId(id)
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
||||
if !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false, c.GetAcceptLanguage())
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -137,10 +138,6 @@ func (c *ApiController) UpdateUser() {
|
||||
id := c.Input().Get("id")
|
||||
columnsStr := c.Input().Get("columns")
|
||||
|
||||
if id == "" {
|
||||
id = c.GetSessionUsername()
|
||||
}
|
||||
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
@@ -148,10 +145,27 @@ func (c *ApiController) UpdateUser() {
|
||||
return
|
||||
}
|
||||
|
||||
if msg := object.CheckUpdateUser(object.GetUser(id), &user, c.GetAcceptLanguage()); msg != "" {
|
||||
if id == "" {
|
||||
id = c.GetSessionUsername()
|
||||
if id == "" {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
}
|
||||
oldUser := object.GetUser(id)
|
||||
if oldUser == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
if pass, err := checkPermissionForUpdateUser(oldUser, &user, c); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
|
||||
columns := []string{}
|
||||
if columnsStr != "" {
|
||||
@@ -160,11 +174,6 @@ func (c *ApiController) UpdateUser() {
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
|
||||
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
@@ -275,14 +284,34 @@ func (c *ApiController) SetPassword() {
|
||||
userName := c.Ctx.Request.Form.Get("userName")
|
||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
|
||||
if strings.Contains(newPassword, " ") {
|
||||
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
||||
return
|
||||
}
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError(c.T("user:New password must have at least 6 characters"))
|
||||
return
|
||||
}
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
userId := util.GetId(userOwner, userName)
|
||||
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" && code == "" {
|
||||
return
|
||||
} else if code == "" {
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if code != c.GetSession("verifiedCode") {
|
||||
c.ResponseError("")
|
||||
return
|
||||
}
|
||||
c.SetSession("verifiedCode", "")
|
||||
}
|
||||
|
||||
targetUser := object.GetUser(userId)
|
||||
@@ -295,16 +324,6 @@ func (c *ApiController) SetPassword() {
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(newPassword, " ") {
|
||||
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
||||
return
|
||||
}
|
||||
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError(c.T("user:New password must have at least 6 characters"))
|
||||
return
|
||||
}
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.ResponseOk()
|
||||
|
@@ -20,8 +20,7 @@ import (
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiController) (bool, string) {
|
||||
oldUser := object.GetUser(userId)
|
||||
func checkPermissionForUpdateUser(oldUser, newUser *object.User, c *ApiController) (bool, string) {
|
||||
organization := object.GetOrganizationByUser(oldUser)
|
||||
var itemsChanged []*object.AccountItem
|
||||
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -110,7 +111,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
sendResp := errors.New("invalid dest type")
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
case object.VerifyTypeEmail:
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError(c.T("check:Email is invalid"))
|
||||
return
|
||||
@@ -132,7 +133,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
case object.VerifyTypePhone:
|
||||
if method == LoginVerification || method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||
dest = user.Phone
|
||||
@@ -187,7 +188,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
|
||||
checkDest := dest
|
||||
organization := object.GetOrganizationByUser(user)
|
||||
if destType == "phone" {
|
||||
if destType == object.VerifyTypePhone {
|
||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||
c.ResponseError(c.T("check:Phone already exists"))
|
||||
return
|
||||
@@ -207,7 +208,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||
return
|
||||
}
|
||||
} else if destType == "email" {
|
||||
} else if destType == object.VerifyTypeEmail {
|
||||
if object.HasUserByField(user.Owner, "email", dest) {
|
||||
c.ResponseError(c.T("check:Email already exists"))
|
||||
return
|
||||
@@ -230,10 +231,10 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
}
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
case object.VerifyTypeEmail:
|
||||
user.Email = dest
|
||||
object.SetUserField(user, "email", user.Email)
|
||||
case "phone":
|
||||
case object.VerifyTypePhone:
|
||||
user.Phone = dest
|
||||
object.SetUserField(user, "phone", user.Phone)
|
||||
default:
|
||||
@@ -245,6 +246,60 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCode
|
||||
// @Tag Account API
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
if form.Name != "" {
|
||||
user = object.GetUserByFields(form.Organization, form.Name)
|
||||
}
|
||||
|
||||
var checkDest string
|
||||
if strings.Contains(form.Username, "@") {
|
||||
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||
form.Username = user.Email
|
||||
}
|
||||
checkDest = form.Username
|
||||
} else {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||
form.Username = user.Phone
|
||||
}
|
||||
}
|
||||
|
||||
if user = object.GetUserByFields(form.Organization, form.Username); user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(form.Username)
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
form.CountryCode = user.GetCountryCode(form.CountryCode)
|
||||
var ok bool
|
||||
if checkDest, ok = util.GetE164Number(form.Username, form.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), form.CountryCode))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result := object.CheckVerificationCode(checkDest, form.Code, c.GetAcceptLanguage()); result.Code != object.VerificationSuccess {
|
||||
c.ResponseError(result.Msg)
|
||||
return
|
||||
}
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.SetSession("verifiedCode", form.Code)
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// VerifyCaptcha ...
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
|
2
go.mod
2
go.mod
@@ -23,7 +23,7 @@ require (
|
||||
github.com/go-mysql-org/go-mysql v1.7.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-webauthn/webauthn v0.8.2
|
||||
github.com/go-webauthn/webauthn v0.5.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
|
10
go.sum
10
go.sum
@@ -222,10 +222,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0=
|
||||
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
|
||||
github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA=
|
||||
github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ=
|
||||
github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU=
|
||||
github.com/go-webauthn/revoke v0.1.6/go.mod h1:TB4wuW4tPlwgF3znujA96F70/YSQXHPPWl7vgY09Iy8=
|
||||
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
|
||||
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
|
||||
github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
|
||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -234,6 +234,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
@@ -658,6 +659,7 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
|
@@ -31,7 +31,7 @@ func TestGenerateI18nFrontend(t *testing.T) {
|
||||
applyToOtherLanguage("frontend", "ja", data)
|
||||
applyToOtherLanguage("frontend", "ko", data)
|
||||
applyToOtherLanguage("frontend", "ru", data)
|
||||
applyToOtherLanguage("frontend", "vi", data)
|
||||
applyToOtherLanguage("frontend", "vn", data)
|
||||
}
|
||||
|
||||
func TestGenerateI18nBackend(t *testing.T) {
|
||||
@@ -46,5 +46,5 @@ func TestGenerateI18nBackend(t *testing.T) {
|
||||
applyToOtherLanguage("backend", "ja", data)
|
||||
applyToOtherLanguage("backend", "ko", data)
|
||||
applyToOtherLanguage("backend", "ru", data)
|
||||
applyToOtherLanguage("backend", "vi", data)
|
||||
applyToOtherLanguage("backend", "vn", data)
|
||||
}
|
||||
|
@@ -108,8 +108,8 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
|
||||
type CasdoorUserInfo struct {
|
||||
Id string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"preferred_username"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
|
@@ -61,8 +61,8 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
|
||||
type CustomUserInfo struct {
|
||||
Id string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"preferred_username"`
|
||||
Name string `json:"preferred_username,omitempty"`
|
||||
DisplayName string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"picture"`
|
||||
Status string `json:"status"`
|
||||
|
@@ -88,7 +88,7 @@ type GothIdProvider struct {
|
||||
Session goth.Session
|
||||
}
|
||||
|
||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) *GothIdProvider {
|
||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider {
|
||||
var idp GothIdProvider
|
||||
switch providerType {
|
||||
case "Amazon":
|
||||
@@ -102,8 +102,13 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &apple.Session{},
|
||||
}
|
||||
case "AzureAD":
|
||||
domain := "common"
|
||||
if hostUrl != "" {
|
||||
domain = hostUrl
|
||||
}
|
||||
|
||||
idp = GothIdProvider{
|
||||
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: "common"}),
|
||||
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: azureadv2.TenantType(domain)}),
|
||||
Session: &azureadv2.Session{},
|
||||
}
|
||||
case "Auth0":
|
||||
|
@@ -90,7 +90,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
||||
} else if typ == "Douyin" {
|
||||
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if isGothSupport(typ) {
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl, hostUrl)
|
||||
} else if typ == "Bilibili" {
|
||||
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"defaultAvatar": "",
|
||||
"defaultApplication": "",
|
||||
"tags": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"],
|
||||
"masterPassword": "",
|
||||
"initScore": 2000,
|
||||
"enableSoftDeletion": false,
|
||||
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package controllers
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -20,76 +20,78 @@ import (
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/forestmgy/ldapserver"
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
"github.com/lor00x/goldap/message"
|
||||
)
|
||||
|
||||
func StartLdapServer() {
|
||||
server := ldapserver.NewServer()
|
||||
routes := ldapserver.NewRouteMux()
|
||||
server := ldap.NewServer()
|
||||
routes := ldap.NewRouteMux()
|
||||
|
||||
routes.Bind(handleBind)
|
||||
routes.Search(handleSearch).Label(" SEARCH****")
|
||||
|
||||
server.Handle(routes)
|
||||
server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
r := m.GetBindRequest()
|
||||
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
|
||||
res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||
if err != "" {
|
||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
bindpassword := string(r.AuthenticationSimple())
|
||||
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword, "en")
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(object.CasdoorOrganization, bindUsername, bindPassword, "en")
|
||||
if err != "" {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
if bindorg == "built-in" {
|
||||
|
||||
if bindOrg == "built-in" || bindUser.IsGlobalAdmin {
|
||||
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
|
||||
} else if binduser.IsAdmin {
|
||||
} else if bindUser.IsAdmin {
|
||||
m.Client.IsOrgAdmin = true
|
||||
}
|
||||
|
||||
m.Client.IsAuthenticated = true
|
||||
m.Client.UserName = bindusername
|
||||
m.Client.OrgName = bindorg
|
||||
m.Client.UserName = bindUsername
|
||||
m.Client.OrgName = bindOrg
|
||||
} else {
|
||||
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
|
||||
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
|
||||
func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
|
||||
if !m.Client.IsAuthenticated {
|
||||
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
|
||||
res.SetResultCode(ldap.LDAPResultUnwillingToPerform)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
r := m.GetSearchRequest()
|
||||
if r.FilterString() == "(objectClass=*)" {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
|
||||
if errCode != ldapserver.LDAPResultSuccess {
|
||||
res.SetResultCode(errCode)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
||||
select {
|
||||
case <-m.Done:
|
||||
@@ -97,16 +99,17 @@ func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
return
|
||||
default:
|
||||
}
|
||||
users, errCode := object.GetFilteredUsers(m, name, org)
|
||||
if errCode != ldapserver.LDAPResultSuccess {
|
||||
res.SetResultCode(errCode)
|
||||
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(users); i++ {
|
||||
user := users[i]
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldapserver.NewSearchResultEntry(dn)
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("email", message.AttributeValue(user.Email))
|
||||
@@ -117,22 +120,3 @@ func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
func getUserPasswordWithType(user *object.User) string {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
if org.PasswordType == "" || org.PasswordType == "plain" {
|
||||
return user.Password
|
||||
}
|
||||
prefix := org.PasswordType
|
||||
if prefix == "salt" {
|
||||
prefix = "sha256"
|
||||
} else if prefix == "md5-salt" {
|
||||
prefix = "md5"
|
||||
} else if prefix == "pbkdf2-salt" {
|
||||
prefix = "pbkdf2"
|
||||
}
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
116
ldap/util.go
Normal file
116
ldap/util.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
)
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
DNFields := strings.Split(DN, ",")
|
||||
params := make(map[string]string, len(DNFields))
|
||||
for _, field := range DNFields {
|
||||
if strings.Contains(field, "=") {
|
||||
k := strings.Split(field, "=")
|
||||
params[k[0]] = k[1]
|
||||
}
|
||||
}
|
||||
|
||||
if params["cn"] == "" {
|
||||
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
}
|
||||
if params["ou"] == "" {
|
||||
return params["cn"], object.CasdoorOrganization, ""
|
||||
}
|
||||
return params["cn"], params["ou"], ""
|
||||
}
|
||||
|
||||
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
if !strings.Contains(baseDN, "ou=") {
|
||||
return "", "", ldap.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
|
||||
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
return name, org, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
func getUsername(filter string) string {
|
||||
nameIndex := strings.Index(filter, "cn=")
|
||||
if nameIndex == -1 {
|
||||
return "*"
|
||||
}
|
||||
|
||||
var name string
|
||||
for i := nameIndex + 3; filter[i] != ')'; i++ {
|
||||
name = name + string(filter[i])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
r := m.GetSearchRequest()
|
||||
name, org, code := getNameAndOrgFromFilter(string(r.BaseObject()), r.FilterString())
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
return nil, code
|
||||
}
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
filteredUsers = object.GetGlobalUsers()
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||
filteredUsers = object.GetUsers(org)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
} else {
|
||||
hasPermission, err := object.CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), true, "en")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
user := object.GetUser(util.GetId(org, name))
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
func getUserPasswordWithType(user *object.User) string {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
if org.PasswordType == "" || org.PasswordType == "plain" {
|
||||
return user.Password
|
||||
}
|
||||
prefix := org.PasswordType
|
||||
if prefix == "salt" {
|
||||
prefix = "sha256"
|
||||
} else if prefix == "md5-salt" {
|
||||
prefix = "md5"
|
||||
} else if prefix == "pbkdf2-salt" {
|
||||
prefix = "pbkdf2"
|
||||
}
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
4
main.go
4
main.go
@@ -23,7 +23,7 @@ import (
|
||||
_ "github.com/beego/beego/session/redis"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/ldap"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
@@ -81,7 +81,7 @@ func main() {
|
||||
// logs.SetLevel(logs.LevelInformational)
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
go controllers.StartLdapServer()
|
||||
go ldap.StartLdapServer()
|
||||
|
||||
beego.Run(fmt.Sprintf(":%v", port))
|
||||
}
|
||||
|
@@ -201,6 +201,16 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Chat))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Message))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Product))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
141
object/chat.go
Normal file
141
object/chat.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
User1 string `xorm:"varchar(100)" json:"user1"`
|
||||
User2 string `xorm:"varchar(100)" json:"user2"`
|
||||
Users []string `xorm:"varchar(100)" json:"users"`
|
||||
MessageCount int `json:"messageCount"`
|
||||
}
|
||||
|
||||
func GetMaskedChat(chat *Chat) *Chat {
|
||||
if chat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return chat
|
||||
}
|
||||
|
||||
func GetMaskedChats(chats []*Chat) []*Chat {
|
||||
for _, chat := range chats {
|
||||
chat = GetMaskedChat(chat)
|
||||
}
|
||||
return chats
|
||||
}
|
||||
|
||||
func GetChatCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Chat{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetChats(owner string) []*Chat {
|
||||
chats := []*Chat{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&chats, &Chat{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return chats
|
||||
}
|
||||
|
||||
func GetPaginationChats(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Chat {
|
||||
chats := []*Chat{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&chats)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return chats
|
||||
}
|
||||
|
||||
func getChat(owner string, name string) *Chat {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
chat := Chat{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &chat
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetChat(id string) *Chat {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getChat(owner, name)
|
||||
}
|
||||
|
||||
func UpdateChat(id string, chat *Chat) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getChat(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddChat(chat *Chat) bool {
|
||||
affected, err := adapter.Engine.Insert(chat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteChat(chat *Chat) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{chat.Owner, chat.Name}).Delete(&Chat{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Chat) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
@@ -250,11 +250,13 @@ func filterField(field string) bool {
|
||||
return reFieldWhiteList.MatchString(field)
|
||||
}
|
||||
|
||||
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool, lang string) (bool, error) {
|
||||
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
||||
if requestUserId == "" {
|
||||
return false, fmt.Errorf(i18n.Translate(lang, "general:Please login first"))
|
||||
}
|
||||
|
||||
userOwner := util.GetOwnerFromId(userId)
|
||||
|
||||
if userId != "" {
|
||||
targetUser := GetUser(userId)
|
||||
if targetUser == nil {
|
||||
@@ -340,7 +342,7 @@ func CheckUsername(username string, lang string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckUpdateUser(oldUser *User, user *User, lang string) string {
|
||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||
if user.DisplayName == "" {
|
||||
return i18n.Translate(lang, "user:Display name cannot be empty")
|
||||
}
|
||||
|
@@ -89,7 +89,7 @@ func initBuiltInOrganization() bool {
|
||||
CountryCodes: []string{"US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"},
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"},
|
||||
Languages: []string{"en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vn"},
|
||||
InitScore: 2000,
|
||||
AccountItems: getBuiltInAccountItems(),
|
||||
EnableSoftDeletion: false,
|
||||
|
@@ -115,7 +115,7 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectclass=*)"
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest("",
|
||||
@@ -126,7 +126,7 @@ func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return false, errors.New("no result")
|
||||
return false, nil
|
||||
}
|
||||
isMicrosoft := false
|
||||
var ldapServerType ldapServerType
|
||||
|
@@ -1,74 +0,0 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/forestmgy/ldapserver"
|
||||
)
|
||||
|
||||
func GetNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
DNValue := strings.Split(DN, ",")
|
||||
if len(DNValue) == 1 || strings.ToLower(DNValue[0])[0] != 'c' || strings.ToLower(DNValue[1])[0] != 'o' {
|
||||
return "", "", "please use correct Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
}
|
||||
return DNValue[0][3:], DNValue[1][3:], ""
|
||||
}
|
||||
|
||||
func GetUserNameAndOrgFromBaseDnAndFilter(baseDN, filter string) (string, string, int) {
|
||||
if !strings.Contains(baseDN, "ou=") || !strings.Contains(filter, "cn=") {
|
||||
return "", "", ldapserver.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
name := getUserNameFromFilter(filter)
|
||||
_, org, _ := GetNameAndOrgFromDN(fmt.Sprintf("cn=%s,", name) + baseDN)
|
||||
errCode := ldapserver.LDAPResultSuccess
|
||||
return name, org, errCode
|
||||
}
|
||||
|
||||
func getUserNameFromFilter(filter string) string {
|
||||
nameIndex := strings.Index(filter, "cn=")
|
||||
var name string
|
||||
for i := nameIndex + 3; filter[i] != ')'; i++ {
|
||||
name = name + string(filter[i])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldapserver.Message, name, org string) ([]*User, int) {
|
||||
var filteredUsers []*User
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.OrgName == "built-in" && org == "*" {
|
||||
filteredUsers = GetGlobalUsers()
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
} else if m.Client.OrgName == "built-in" || org == m.Client.OrgName {
|
||||
filteredUsers = GetUsers(org)
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
} else {
|
||||
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true, "en")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
user := getUser(org, name)
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||
}
|
||||
}
|
138
object/message.go
Normal file
138
object/message.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Chat string `xorm:"varchar(100)" json:"chat"`
|
||||
Author string `xorm:"varchar(100)" json:"author"`
|
||||
Text string `xorm:"mediumtext" json:"text"`
|
||||
}
|
||||
|
||||
func GetMaskedMessage(message *Message) *Message {
|
||||
if message == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func GetMaskedMessages(messages []*Message) []*Message {
|
||||
for _, message := range messages {
|
||||
message = GetMaskedMessage(message)
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetMessageCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Message{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetMessages(owner string) []*Message {
|
||||
messages := []*Message{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&messages, &Message{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func GetPaginationMessages(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
|
||||
messages := []*Message{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&messages)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
func getMessage(owner string, name string) *Message {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
message := Message{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &message
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetMessage(id string) *Message {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getMessage(owner, name)
|
||||
}
|
||||
|
||||
func UpdateMessage(id string, message *Message) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getMessage(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddMessage(message *Message) bool {
|
||||
affected, err := adapter.Engine.Insert(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteMessage(message *Message) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{message.Owner, message.Name}).Delete(&Message{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Message) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
@@ -30,7 +30,7 @@ func TestProduct(t *testing.T) {
|
||||
product := GetProduct("admin/product_123")
|
||||
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
|
@@ -256,7 +256,11 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||
}
|
||||
}
|
||||
|
||||
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2)
|
||||
if err != nil {
|
||||
return nil, cert, err
|
||||
}
|
||||
|
||||
if pProvider == nil {
|
||||
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
@@ -86,19 +86,30 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
|
||||
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
||||
|
||||
attributes := assertion.CreateElement("saml:AttributeStatement")
|
||||
|
||||
email := attributes.CreateElement("saml:Attribute")
|
||||
email.CreateAttr("Name", "Email")
|
||||
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
|
||||
|
||||
name := attributes.CreateElement("saml:Attribute")
|
||||
name.CreateAttr("Name", "Name")
|
||||
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
|
||||
|
||||
displayName := attributes.CreateElement("saml:Attribute")
|
||||
displayName.CreateAttr("Name", "DisplayName")
|
||||
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
||||
|
||||
roles := attributes.CreateElement("saml:Attribute")
|
||||
roles.CreateAttr("Name", "Roles")
|
||||
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
ExtendUserWithRolesAndPermissions(user)
|
||||
for _, role := range user.Roles {
|
||||
roles.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(role.Name)
|
||||
}
|
||||
|
||||
return samlResponse, nil
|
||||
}
|
||||
|
||||
|
@@ -36,13 +36,11 @@ func ParseSamlResponse(samlResponse string, providerType string) (string, error)
|
||||
return "", err
|
||||
}
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return assertionInfo.NameID, nil
|
||||
|
||||
return assertionInfo.NameID, err
|
||||
}
|
||||
|
||||
func GenerateSamlLoginUrl(id, relayState, lang string) (string, string, error) {
|
||||
func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method string, err error) {
|
||||
provider := GetProvider(id)
|
||||
if provider.Category != "SAML" {
|
||||
return "", "", fmt.Errorf(i18n.Translate(lang, "saml_sp:provider %s's category is not SAML"), provider.Name)
|
||||
@@ -51,8 +49,7 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (string, string, error) {
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
auth := ""
|
||||
method := ""
|
||||
|
||||
if provider.EnableSignAuthnRequest {
|
||||
post, err := sp.BuildAuthBodyPost(relayState)
|
||||
if err != nil {
|
||||
|
@@ -51,7 +51,7 @@ type Token struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
@@ -362,7 +362,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
}
|
||||
|
||||
token.CodeIsUsed = true
|
||||
updateUsedByCode(token)
|
||||
go updateUsedByCode(token)
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
IdToken: token.AccessToken,
|
||||
@@ -613,7 +614,8 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
nullUser := &User{
|
||||
Owner: application.Owner,
|
||||
Id: application.GetId(),
|
||||
Name: fmt.Sprintf("app/%s", application.Name),
|
||||
Name: application.Name,
|
||||
Type: "application",
|
||||
}
|
||||
|
||||
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -38,6 +39,11 @@ const (
|
||||
timeoutError = 3
|
||||
)
|
||||
|
||||
const (
|
||||
VerifyTypePhone = "phone"
|
||||
VerifyTypeEmail = "email"
|
||||
)
|
||||
|
||||
type VerificationRecord struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@@ -213,6 +219,14 @@ func CheckSigninCode(user *User, dest, code, lang string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func GetVerifyType(username string) (verificationCodeType string) {
|
||||
if strings.Contains(username, "@") {
|
||||
return VerifyTypeEmail
|
||||
} else {
|
||||
return VerifyTypeEmail
|
||||
}
|
||||
}
|
||||
|
||||
// From Casnode/object/validateCode.go line 116
|
||||
var stdNums = []byte("0123456789")
|
||||
|
||||
|
@@ -28,21 +28,21 @@ type AlipayPaymentProvider struct {
|
||||
Client *alipay.Client
|
||||
}
|
||||
|
||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
|
||||
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
|
||||
pp := &AlipayPaymentProvider{}
|
||||
|
||||
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pp.Client = client
|
||||
return pp
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
|
@@ -22,11 +22,23 @@ type PaymentProvider interface {
|
||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||
}
|
||||
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
|
||||
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
|
||||
if typ == "Alipay" {
|
||||
return NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
newAlipayPaymentProvider, err := NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAlipayPaymentProvider, nil
|
||||
} else if typ == "GC" {
|
||||
return NewGcPaymentProvider(appId, clientSecret, host)
|
||||
return NewGcPaymentProvider(appId, clientSecret, host), nil
|
||||
} else if typ == "WeChat Pay" {
|
||||
// appId, mchId, mchCertSerialNumber, apiV3Key, privateKey
|
||||
newWechatPaymentProvider, err := NewWechatPaymentProvider(clientId2, appId, authorityPublicKey, clientSecret, appPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWechatPaymentProvider, nil
|
||||
}
|
||||
return nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
102
pp/wechatpay.go
Normal file
102
pp/wechatpay.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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 pp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/wechat/v3"
|
||||
)
|
||||
|
||||
type WechatPaymentProvider struct {
|
||||
ClientV3 *wechat.ClientV3
|
||||
appId string
|
||||
}
|
||||
|
||||
func NewWechatPaymentProvider(appId string, mchId string, mchCertSerialNumber string, apiV3Key string, privateKey string) (*WechatPaymentProvider, error) {
|
||||
pp := &WechatPaymentProvider{appId: appId}
|
||||
|
||||
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = clientV3.AutoVerifySign()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pp.ClientV3 = clientV3
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
|
||||
bm.Set("providerName", providerName)
|
||||
bm.Set("productName", productName)
|
||||
|
||||
bm.Set("return_url", returnUrl)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
|
||||
bm.Set("body", productDisplayName)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("total_fee", getPriceString(price))
|
||||
|
||||
wechatRsp, err := pp.ClientV3.V3TransactionJsapi(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
payUrl := fmt.Sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect", pp.appId, wechatRsp.Response.PrepayId)
|
||||
return payUrl, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
bm, err := wechat.V3ParseNotifyToBodyMap(request)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
providerName := bm.Get("providerName")
|
||||
productName := bm.Get("productName")
|
||||
|
||||
productDisplayName := bm.Get("body")
|
||||
paymentName := bm.Get("out_trade_no")
|
||||
price := util.ParseFloat(bm.Get("total_fee"))
|
||||
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cert := pp.ClientV3.WxPublicKey()
|
||||
|
||||
err = notifyReq.VerifySignByPK(cert)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
return "", nil
|
||||
}
|
@@ -50,6 +50,7 @@ func initAPI() {
|
||||
beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout")
|
||||
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
||||
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
|
||||
beego.Router("/api/user", &controllers.ApiController{}, "GET:GetUserinfo2")
|
||||
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
||||
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
|
||||
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
|
||||
@@ -114,6 +115,7 @@ func initAPI() {
|
||||
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
|
||||
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||
@@ -187,6 +189,18 @@ func initAPI() {
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||
|
||||
beego.Router("/api/get-chats", &controllers.ApiController{}, "GET:GetChats")
|
||||
beego.Router("/api/get-chat", &controllers.ApiController{}, "GET:GetChat")
|
||||
beego.Router("/api/update-chat", &controllers.ApiController{}, "POST:UpdateChat")
|
||||
beego.Router("/api/add-chat", &controllers.ApiController{}, "POST:AddChat")
|
||||
beego.Router("/api/delete-chat", &controllers.ApiController{}, "POST:DeleteChat")
|
||||
|
||||
beego.Router("/api/get-messages", &controllers.ApiController{}, "GET:GetMessages")
|
||||
beego.Router("/api/get-message", &controllers.ApiController{}, "GET:GetMessage")
|
||||
beego.Router("/api/update-message", &controllers.ApiController{}, "POST:UpdateMessage")
|
||||
beego.Router("/api/add-message", &controllers.ApiController{}, "POST:AddMessage")
|
||||
beego.Router("/api/delete-message", &controllers.ApiController{}, "POST:DeleteMessage")
|
||||
|
||||
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||
|
@@ -2058,22 +2058,13 @@
|
||||
"tags": [
|
||||
"System API"
|
||||
],
|
||||
"description": "get user's system info",
|
||||
"description": "get system info like CPU and memory usage",
|
||||
"operationId": "ApiController.GetSystemInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.SystemInfo"
|
||||
"$ref": "#/definitions/util.SystemInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2180,6 +2171,12 @@
|
||||
"name": "phone",
|
||||
"description": "The phone of the user",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "userId",
|
||||
"description": "The userId of the user",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -2323,11 +2320,14 @@
|
||||
"tags": [
|
||||
"System API"
|
||||
],
|
||||
"description": "get local git repo's latest release version info",
|
||||
"description": "get version info like Casdoor release version and commit ID",
|
||||
"operationId": "ApiController.GetVersionInfo",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{string} local latest version hash of Casdoor"
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/util.VersionInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3494,6 +3494,23 @@
|
||||
"operationId": "ApiController.UploadResource"
|
||||
}
|
||||
},
|
||||
"/api/user": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Account API"
|
||||
],
|
||||
"description": "return Laravel compatible user information according to OAuth 2.0",
|
||||
"operationId": "ApiController.UserInfo2",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/LaravelResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/userinfo": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3627,14 +3644,18 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"2268.0xc000528cf0.false": {
|
||||
"2306.0xc0003a4480.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"2302.0xc000528d20.false": {
|
||||
"2340.0xc0003a44b0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"LaravelResponse": {
|
||||
"title": "LaravelResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"Response": {
|
||||
"title": "Response",
|
||||
"type": "object"
|
||||
@@ -3682,6 +3703,9 @@
|
||||
"captchaType": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3758,10 +3782,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/2268.0xc000528cf0.false"
|
||||
"$ref": "#/definitions/2306.0xc0003a4480.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/2302.0xc000528d20.false"
|
||||
"$ref": "#/definitions/2340.0xc0003a44b0.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@@ -4830,10 +4854,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.SystemInfo": {
|
||||
"title": "SystemInfo",
|
||||
"type": "object"
|
||||
},
|
||||
"object.TableColumn": {
|
||||
"title": "TableColumn",
|
||||
"type": "object",
|
||||
@@ -5475,6 +5495,43 @@
|
||||
"title": "CredentialCreationResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"util.SystemInfo": {
|
||||
"title": "SystemInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpuUsage": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"memoryTotal": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"memoryUsed": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"util.VersionInfo": {
|
||||
"title": "VersionInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"commitId": {
|
||||
"type": "string"
|
||||
},
|
||||
"commitOffset": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"webauthn.Credential": {
|
||||
"title": "Credential",
|
||||
"type": "object"
|
||||
|
@@ -1340,19 +1340,13 @@ paths:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
description: get user's system info
|
||||
description: get system info like CPU and memory usage
|
||||
operationId: ApiController.GetSystemInfo
|
||||
parameters:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the user
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/object.SystemInfo'
|
||||
$ref: '#/definitions/util.SystemInfo'
|
||||
/api/get-token:
|
||||
get:
|
||||
tags:
|
||||
@@ -1423,6 +1417,10 @@ paths:
|
||||
name: phone
|
||||
description: The phone of the user
|
||||
type: string
|
||||
- in: query
|
||||
name: userId
|
||||
description: The userId of the user
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
@@ -1515,11 +1513,13 @@ paths:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
description: get local git repo's latest release version info
|
||||
description: get version info like Casdoor release version and commit ID
|
||||
operationId: ApiController.GetVersionInfo
|
||||
responses:
|
||||
"200":
|
||||
description: '{string} local latest version hash of Casdoor'
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/util.VersionInfo'
|
||||
/api/get-webhook:
|
||||
get:
|
||||
tags:
|
||||
@@ -2288,6 +2288,17 @@ paths:
|
||||
tags:
|
||||
- Resource API
|
||||
operationId: ApiController.UploadResource
|
||||
/api/user:
|
||||
get:
|
||||
tags:
|
||||
- Account API
|
||||
description: return Laravel compatible user information according to OAuth 2.0
|
||||
operationId: ApiController.UserInfo2
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/LaravelResponse'
|
||||
/api/userinfo:
|
||||
get:
|
||||
tags:
|
||||
@@ -2374,12 +2385,15 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
definitions:
|
||||
2268.0xc000528cf0.false:
|
||||
2306.0xc0003a4480.false:
|
||||
title: "false"
|
||||
type: object
|
||||
2302.0xc000528d20.false:
|
||||
2340.0xc0003a44b0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
LaravelResponse:
|
||||
title: LaravelResponse
|
||||
type: object
|
||||
Response:
|
||||
title: Response
|
||||
type: object
|
||||
@@ -2413,6 +2427,8 @@ definitions:
|
||||
type: string
|
||||
captchaType:
|
||||
type: string
|
||||
clientId:
|
||||
type: string
|
||||
clientSecret:
|
||||
type: string
|
||||
code:
|
||||
@@ -2464,9 +2480,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/2268.0xc000528cf0.false'
|
||||
$ref: '#/definitions/2306.0xc0003a4480.false'
|
||||
data2:
|
||||
$ref: '#/definitions/2302.0xc000528d20.false'
|
||||
$ref: '#/definitions/2340.0xc0003a44b0.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@@ -3183,9 +3199,6 @@ definitions:
|
||||
type: string
|
||||
user:
|
||||
type: string
|
||||
object.SystemInfo:
|
||||
title: SystemInfo
|
||||
type: object
|
||||
object.TableColumn:
|
||||
title: TableColumn
|
||||
type: object
|
||||
@@ -3617,6 +3630,32 @@ definitions:
|
||||
protocol.CredentialCreationResponse:
|
||||
title: CredentialCreationResponse
|
||||
type: object
|
||||
util.SystemInfo:
|
||||
title: SystemInfo
|
||||
type: object
|
||||
properties:
|
||||
cpuUsage:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
format: double
|
||||
memoryTotal:
|
||||
type: integer
|
||||
format: int64
|
||||
memoryUsed:
|
||||
type: integer
|
||||
format: int64
|
||||
util.VersionInfo:
|
||||
title: VersionInfo
|
||||
type: object
|
||||
properties:
|
||||
commitId:
|
||||
type: string
|
||||
commitOffset:
|
||||
type: integer
|
||||
format: int64
|
||||
version:
|
||||
type: string
|
||||
webauthn.Credential:
|
||||
title: Credential
|
||||
type: object
|
||||
|
@@ -95,6 +95,15 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
||||
return tokens[0], tokens[1]
|
||||
}
|
||||
|
||||
func GetOwnerFromId(id string) string {
|
||||
tokens := strings.Split(id, "/")
|
||||
if len(tokens) != 2 {
|
||||
panic(errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id))
|
||||
}
|
||||
|
||||
return tokens[0]
|
||||
}
|
||||
|
||||
func GetOwnerAndNameFromIdNoCheck(id string) (string, string) {
|
||||
tokens := strings.SplitN(id, "/", 2)
|
||||
return tokens[0], tokens[1]
|
||||
|
@@ -59,7 +59,7 @@ func getMemoryUsage() (uint64, uint64, error) {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
return m.TotalAlloc, virtualMem.Total, nil
|
||||
return m.Alloc, virtualMem.Total, nil
|
||||
}
|
||||
|
||||
func GetSystemInfo() (*SystemInfo, error) {
|
||||
|
@@ -21,7 +21,7 @@ import i18next from "i18next";
|
||||
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import * as ModelBackend from "./backend/ModelBackend";
|
||||
import PolicyTable from "./common/PoliciyTable";
|
||||
import PolicyTable from "./table/PoliciyTable";
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
|
@@ -44,6 +44,10 @@ import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import ChatEditPage from "./ChatEditPage";
|
||||
import ChatListPage from "./ChatListPage";
|
||||
import MessageEditPage from "./MessageEditPage";
|
||||
import MessageListPage from "./MessageListPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
@@ -52,7 +56,7 @@ import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import HomePage from "./basic/HomePage";
|
||||
import CustomGithubCorner from "./CustomGithubCorner";
|
||||
import CustomGithubCorner from "./common/CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
import * as Auth from "./auth/Auth";
|
||||
@@ -60,7 +64,7 @@ import EntryPage from "./EntryPage";
|
||||
import ResultPage from "./auth/ResultPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from "./SelectLanguageBox";
|
||||
import LanguageSelect from "./common/select/LanguageSelect";
|
||||
import i18next from "i18next";
|
||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||
import SamlCallback from "./auth/SamlCallback";
|
||||
@@ -70,7 +74,7 @@ import SystemInfo from "./SystemInfo";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import {withTranslation} from "react-i18next";
|
||||
import SelectThemeBox from "./SelectThemeBox";
|
||||
import ThemeSelect from "./common/select/ThemeSelect";
|
||||
import SessionListPage from "./SessionListPage";
|
||||
|
||||
const {Header, Footer, Content} = Layout;
|
||||
@@ -135,16 +139,22 @@ class App extends Component {
|
||||
this.setState({selectedMenuKey: "/applications"});
|
||||
} else if (uri.includes("/resources")) {
|
||||
this.setState({selectedMenuKey: "/resources"});
|
||||
} else if (uri.includes("/tokens")) {
|
||||
this.setState({selectedMenuKey: "/tokens"});
|
||||
} else if (uri.includes("/records")) {
|
||||
this.setState({selectedMenuKey: "/records"});
|
||||
} else if (uri.includes("/tokens")) {
|
||||
this.setState({selectedMenuKey: "/tokens"});
|
||||
} else if (uri.includes("/sessions")) {
|
||||
this.setState({selectedMenuKey: "/sessions"});
|
||||
} else if (uri.includes("/webhooks")) {
|
||||
this.setState({selectedMenuKey: "/webhooks"});
|
||||
} else if (uri.includes("/syncers")) {
|
||||
this.setState({selectedMenuKey: "/syncers"});
|
||||
} else if (uri.includes("/certs")) {
|
||||
this.setState({selectedMenuKey: "/certs"});
|
||||
} else if (uri.includes("/chats")) {
|
||||
this.setState({selectedMenuKey: "/chats"});
|
||||
} else if (uri.includes("/messages")) {
|
||||
this.setState({selectedMenuKey: "/messages"});
|
||||
} else if (uri.includes("/products")) {
|
||||
this.setState({selectedMenuKey: "/products"});
|
||||
} else if (uri.includes("/payments")) {
|
||||
@@ -352,7 +362,7 @@ class App extends Component {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.renderRightDropdown()}
|
||||
<SelectThemeBox
|
||||
<ThemeSelect
|
||||
themeAlgorithm={this.state.themeAlgorithm}
|
||||
onChange={(nextThemeAlgorithm) => {
|
||||
this.setState({
|
||||
@@ -360,7 +370,7 @@ class App extends Component {
|
||||
logo: this.getLogo(nextThemeAlgorithm),
|
||||
});
|
||||
}} />
|
||||
<SelectLanguageBox languages={this.state.account.organization.languages} />
|
||||
<LanguageSelect languages={this.state.account.organization.languages} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -413,6 +423,14 @@ class App extends Component {
|
||||
"/providers"
|
||||
));
|
||||
|
||||
res.push(Setting.getItem(<Link to="/chats">{i18next.t("general:Chats")}</Link>,
|
||||
"/chats"
|
||||
));
|
||||
|
||||
res.push(Setting.getItem(<Link to="/messages">{i18next.t("general:Messages")}</Link>,
|
||||
"/messages"
|
||||
));
|
||||
|
||||
res.push(Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>,
|
||||
"/resources"
|
||||
));
|
||||
@@ -527,6 +545,10 @@ class App extends Component {
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/messages" render={(props) => this.renderLoginIfNotLoggedIn(<MessageListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/messages/:messageName" render={(props) => this.renderLoginIfNotLoggedIn(<MessageEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
|
@@ -25,9 +25,9 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
||||
import SignupPage from "./auth/SignupPage";
|
||||
import LoginPage from "./auth/LoginPage";
|
||||
import i18next from "i18next";
|
||||
import UrlTable from "./UrlTable";
|
||||
import ProviderTable from "./ProviderTable";
|
||||
import SignupTable from "./SignupTable";
|
||||
import UrlTable from "./table/UrlTable";
|
||||
import ProviderTable from "./table/ProviderTable";
|
||||
import SignupTable from "./table/SignupTable";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
@@ -867,6 +867,8 @@ class ApplicationEditPage extends React.Component {
|
||||
|
||||
submitApplicationEdit(willExist) {
|
||||
const application = Setting.deepCopy(this.state.application);
|
||||
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
|
||||
|
||||
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
|
217
web/src/ChatEditPage.js
Normal file
217
web/src/ChatEditPage.js
Normal file
@@ -0,0 +1,217 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select} from "antd";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class ChatEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
chatName: props.match.params.chatName,
|
||||
chat: null,
|
||||
organizations: [],
|
||||
users: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getChat();
|
||||
this.getOrganizations();
|
||||
}
|
||||
|
||||
getChat() {
|
||||
ChatBackend.getChat("admin", this.state.chatName)
|
||||
.then((chat) => {
|
||||
this.setState({
|
||||
chat: chat,
|
||||
});
|
||||
|
||||
this.getUsers(chat.organization);
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: (res.msg === undefined) ? res : [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parseChatField(key, value) {
|
||||
if ([].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
updateChatField(key, value) {
|
||||
value = this.parseChatField(key, value);
|
||||
|
||||
const chat = this.state.chat;
|
||||
chat[key] = value;
|
||||
this.setState({
|
||||
chat: chat,
|
||||
});
|
||||
}
|
||||
|
||||
renderChat() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("chat:New Chat") : i18next.t("chat:Edit Chat")}
|
||||
<Button onClick={() => this.submitChatEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitChatEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteChat()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}
|
||||
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.chat.name} onChange={e => {
|
||||
this.updateChatField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.chat.displayName} onChange={e => {
|
||||
this.updateChatField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("general:User1 - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user1} onChange={(value => {this.updateChatField("user1", value);})}
|
||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:User2"), i18next.t("general:User2 - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.user2} onChange={(value => {this.updateChatField("user2", value);})}
|
||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("chat:Sub users"), i18next.t("chat:Sub users - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select mode="tags" style={{width: "100%"}} value={this.state.chat.users}
|
||||
onChange={(value => {this.updateChatField("users", value);})}
|
||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
submitChatEdit(willExist) {
|
||||
const chat = Setting.deepCopy(this.state.chat);
|
||||
ChatBackend.updateChat(this.state.chat.owner, this.state.chatName, chat)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
this.setState({
|
||||
chatName: this.state.chat.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push("/chats");
|
||||
} else {
|
||||
this.props.history.push(`/chats/${this.state.chat.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
this.updateChatField("name", this.state.chatName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteChat() {
|
||||
ChatBackend.deleteChat(this.state.chat)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push("/chats");
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.chat !== null ? this.renderChat() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitChatEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitChatEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteChat()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatEditPage;
|
268
web/src/ChatListPage.js
Normal file
268
web/src/ChatListPage.js
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright 2023 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 {Link} from "react-router-dom";
|
||||
import {Button, Table} from "antd";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
|
||||
class ChatListPage extends BaseListPage {
|
||||
newChat() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.applicationName,
|
||||
name: `chat_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
displayName: `New Chat - ${randomName}`,
|
||||
user1: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
user2: "",
|
||||
users: [`${this.props.account.owner}/${this.props.account.name}`],
|
||||
messageCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
addChat() {
|
||||
const newChat = this.newChat();
|
||||
ChatBackend.addChat(newChat)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push({pathname: `/chats/${newChat.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", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteChat(i) {
|
||||
ChatBackend.deleteChat(this.state.data[i])
|
||||
.then((res) => {
|
||||
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", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(chats) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/chats/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
key: "createdTime",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Updated time"),
|
||||
dataIndex: "updatedTime",
|
||||
key: "updatedTime",
|
||||
width: "15 0px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Display name"),
|
||||
dataIndex: "displayName",
|
||||
key: "displayName",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("displayName"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("chat:User1"),
|
||||
dataIndex: "user1",
|
||||
key: "user1",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("user1"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("chat:User2"),
|
||||
dataIndex: "user2",
|
||||
key: "user2",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("user2"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Users"),
|
||||
dataIndex: "users",
|
||||
key: "users",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("users"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text, "users");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("chat:Message count"),
|
||||
dataIndex: "messageCount",
|
||||
key: "messageCount",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("messageCount"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "170px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/chats/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteChat(index)}
|
||||
>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.pagination.total,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={chats} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Chats")}
|
||||
<Button type="primary" size="small" onClick={this.addChat.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.category !== undefined && params.category !== null) {
|
||||
field = "category";
|
||||
value = params.category;
|
||||
} else if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
pagination: {
|
||||
...params.pagination,
|
||||
total: res.data2,
|
||||
},
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ChatListPage;
|
220
web/src/MessageEditPage.js
Normal file
220
web/src/MessageEditPage.js
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select} from "antd";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import * as MessageBackend from "./backend/MessageBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
class MessageEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
messageName: props.match.params.messageName,
|
||||
message: null,
|
||||
organizations: [],
|
||||
chats: [],
|
||||
users: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getMessage();
|
||||
this.getOrganizations();
|
||||
this.getChats();
|
||||
}
|
||||
|
||||
getMessage() {
|
||||
MessageBackend.getMessage("admin", this.state.messageName)
|
||||
.then((message) => {
|
||||
this.setState({
|
||||
message: message,
|
||||
});
|
||||
|
||||
this.getUsers(message.organization);
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: (res.msg === undefined) ? res : [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getChats() {
|
||||
ChatBackend.getChats("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
chats: (res.msg === undefined) ? res : [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getUsers(organizationName) {
|
||||
UserBackend.getUsers(organizationName)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
users: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
parseMessageField(key, value) {
|
||||
if ([].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
updateMessageField(key, value) {
|
||||
value = this.parseMessageField(key, value);
|
||||
|
||||
const message = this.state.message;
|
||||
message[key] = value;
|
||||
this.setState({
|
||||
message: message,
|
||||
});
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("message:New Message") : i18next.t("message:Edit Message")}
|
||||
<Button onClick={() => this.submitMessageEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitMessageEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteMessage()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.message.organization} onChange={(value => {this.updateMessageField("organization", value);})}
|
||||
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.message.name} onChange={e => {
|
||||
this.updateMessageField("name", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("message:Chat"), i18next.t("message:Chat - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.message.chat} onChange={(value => {this.updateMessageField("chat", value);})}
|
||||
options={this.state.chats.map((chat) => Setting.getOption(chat.name, chat.name))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("message:Author"), i18next.t("general:Author - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.message.author} onChange={(value => {this.updateMessageField("author", value);})}
|
||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))
|
||||
} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("message:Text"), i18next.t("message:Text - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<TextArea rows={10} value={this.state.message.text} onChange={e => {
|
||||
this.updateMessageField("text", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
submitMessageEdit(willExist) {
|
||||
const message = Setting.deepCopy(this.state.message);
|
||||
MessageBackend.updateMessage(this.state.message.owner, this.state.messageName, message)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
this.setState({
|
||||
messageName: this.state.message.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
this.props.history.push("/messages");
|
||||
} else {
|
||||
this.props.history.push(`/messages/${this.state.message.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
this.updateMessageField("name", this.state.messageName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteMessage() {
|
||||
MessageBackend.deleteMessage(this.state.message)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push("/messages");
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.state.message !== null ? this.renderMessage() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitMessageEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitMessageEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteMessage()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MessageEditPage;
|
236
web/src/MessageListPage.js
Normal file
236
web/src/MessageListPage.js
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2023 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 {Link} from "react-router-dom";
|
||||
import {Button, Table} from "antd";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as MessageBackend from "./backend/MessageBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
|
||||
class MessageListPage extends BaseListPage {
|
||||
newMessage() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.messagename,
|
||||
name: `message_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
chat: "",
|
||||
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
text: "",
|
||||
};
|
||||
}
|
||||
|
||||
addMessage() {
|
||||
const newMessage = this.newMessage();
|
||||
MessageBackend.addMessage(newMessage)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push({pathname: `/messages/${newMessage.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", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteMessage(i) {
|
||||
MessageBackend.deleteMessage(this.state.data[i])
|
||||
.then((res) => {
|
||||
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", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(messages) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/messages/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
key: "createdTime",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("message:Chat"),
|
||||
dataIndex: "chat",
|
||||
key: "chat",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("chat"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/chats/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("message:Author"),
|
||||
dataIndex: "author",
|
||||
key: "author",
|
||||
width: "120px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("author"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("message:Text"),
|
||||
dataIndex: "text",
|
||||
key: "text",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("text"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "170px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/messages/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteMessage(index)}
|
||||
>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.pagination.total,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={messages} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Messages")}
|
||||
<Button type="primary" size="small" onClick={this.addMessage.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
let field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
if (params.category !== undefined && params.category !== null) {
|
||||
field = "category";
|
||||
value = params.category;
|
||||
} else if (params.type !== undefined && params.type !== null) {
|
||||
field = "type";
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
MessageBackend.getMessages("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
pagination: {
|
||||
...params.pagination,
|
||||
total: res.data2,
|
||||
},
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default MessageListPage;
|
@@ -21,8 +21,8 @@ import * as Setting from "./Setting";
|
||||
import * as Conf from "./Conf";
|
||||
import i18next from "i18next";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import LdapTable from "./LdapTable";
|
||||
import AccountTable from "./AccountTable";
|
||||
import LdapTable from "./table/LdapTable";
|
||||
import AccountTable from "./table/AccountTable";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
const {Option} = Select;
|
||||
|
@@ -139,7 +139,7 @@ class PermissionListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("users"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text);
|
||||
return Setting.getTags(text, "users");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -150,7 +150,7 @@ class PermissionListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("roles"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text);
|
||||
return Setting.getTags(text, "roles");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -19,12 +19,12 @@ import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import * as ProviderEditTestEmail from "./TestEmailWidget";
|
||||
import * as ProviderEditTestSms from "./TestSmsWidget";
|
||||
import * as ProviderEditTestEmail from "./common/TestEmailWidget";
|
||||
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import {CountryCodeSelect} from "./common/CountryCodeSelect";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
|
||||
const {Option} = Select;
|
||||
const {TextArea} = Input;
|
||||
@@ -110,7 +110,7 @@ class ProviderEditPage extends React.Component {
|
||||
getClientSecretLabel(provider) {
|
||||
switch (provider.category) {
|
||||
case "Email":
|
||||
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
|
||||
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
|
||||
case "SMS":
|
||||
if (provider.type === "Volc Engine SMS") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
|
||||
@@ -130,6 +130,24 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getProviderSubTypeOptions(type) {
|
||||
if (type === "WeCom" || type === "Infoflow") {
|
||||
return (
|
||||
[
|
||||
{id: "Internal", name: i18next.t("provider:Internal")},
|
||||
{id: "Third-party", name: i18next.t("provider:Third-party")},
|
||||
]
|
||||
);
|
||||
} else if (type === "Aliyun Captcha") {
|
||||
return [
|
||||
{id: "nc", name: i18next.t("provider:Sliding Validation")},
|
||||
{id: "ic", name: i18next.t("provider:Intelligent Validation")},
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getAppIdRow(provider) {
|
||||
let text = "";
|
||||
let tooltip = "";
|
||||
@@ -315,7 +333,7 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("subType", value);
|
||||
}}>
|
||||
{
|
||||
Setting.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
||||
this.getProviderSubTypeOptions(this.state.provider.type).map((providerSubType, index) => <Option key={index} value={providerSubType.id}>{providerSubType.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
@@ -331,7 +349,10 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
||||
[
|
||||
{id: "Normal", name: i18next.t("provider:Normal")},
|
||||
{id: "Silent", name: i18next.t("provider:Silent")},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
@@ -440,13 +461,15 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" ? null : (
|
||||
this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.state.provider.type === "Aliyun Captcha"
|
||||
? Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
|
||||
: this.state.provider.type === "WeChat Pay"
|
||||
? Setting.getLabel("appId", "appId")
|
||||
: Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientId2} onChange={e => {
|
||||
@@ -454,18 +477,22 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.state.provider.type === "Aliyun Captcha"
|
||||
? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret2} onChange={e => {
|
||||
this.updateProviderField("clientSecret2", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.provider.type === "WeChat Pay" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.state.provider.type === "Aliyun Captcha"
|
||||
? Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"))}
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.clientSecret2} onChange={e => {
|
||||
this.updateProviderField("clientSecret2", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@@ -484,7 +511,7 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
||||
this.state.provider.type !== "Adfs" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
|
@@ -130,7 +130,7 @@ class RoleListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("users"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text);
|
||||
return Setting.getTags(text, "users");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -141,7 +141,7 @@ class RoleListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("roles"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getTags(text);
|
||||
return Setting.getTags(text, "roles");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Checkbox, Form, Modal, Select, Tag, Tooltip, message, theme} from "antd";
|
||||
import {Select, Tag, Tooltip, message, theme} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
@@ -42,7 +42,7 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
{label: "TiếngViệt", key: "vi", country: "VI", alt: "TiếngViệt"},
|
||||
{label: "TiếngViệt", key: "vn", country: "VN", alt: "TiếngViệt"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
@@ -552,10 +552,6 @@ export function addRow(array, row, position = "end") {
|
||||
return position === "end" ? [...array, row] : [row, ...array];
|
||||
}
|
||||
|
||||
export function prependRow(array, row) {
|
||||
return [row, ...array];
|
||||
}
|
||||
|
||||
export function deleteRow(array, i) {
|
||||
// return array = array.slice(0, i).concat(array.slice(i + 1));
|
||||
return [...array.slice(0, i), ...array.slice(i + 1)];
|
||||
@@ -585,76 +581,6 @@ export function isMobile() {
|
||||
return isMobileDevice;
|
||||
}
|
||||
|
||||
export function getTermsOfUseContent(url, setTermsOfUseContent) {
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
}).then(r => {
|
||||
r.text().then(setTermsOfUseContent);
|
||||
});
|
||||
}
|
||||
|
||||
export function isAgreementRequired(application) {
|
||||
if (application) {
|
||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||
if (!agreementItem || agreementItem.rule === "None" || !agreementItem.rule) {
|
||||
return false;
|
||||
}
|
||||
if (agreementItem.required) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isDefaultTrue(application) {
|
||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||
|
||||
return isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)";
|
||||
}
|
||||
|
||||
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
|
||||
return (
|
||||
<Form.Item
|
||||
name="agreement"
|
||||
key="agreement"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please accept the agreement!"),
|
||||
},
|
||||
]}
|
||||
{...layout}
|
||||
noStyle={noStyle}
|
||||
initialValue={initialValue}
|
||||
>
|
||||
<Checkbox style={{float: "left"}}>
|
||||
{i18next.t("signup:Accept")}
|
||||
<a onClick={onClick}>
|
||||
{i18next.t("signup:Terms of Use")}
|
||||
</a>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderModal(isOpen, onOk, onCancel, doc) {
|
||||
return (
|
||||
<Modal
|
||||
title={i18next.t("signup:Terms of Use")}
|
||||
open={isOpen}
|
||||
width={"55vw"}
|
||||
closable={false}
|
||||
okText={i18next.t("signup:Accept")}
|
||||
cancelText={i18next.t("signup:Decline")}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function getFormattedDate(date) {
|
||||
if (date === undefined) {
|
||||
return null;
|
||||
@@ -731,14 +657,6 @@ export function getAvatarColor(s) {
|
||||
return colorList[hash % 4];
|
||||
}
|
||||
|
||||
export function getLogo(theme) {
|
||||
if (theme === "Dark") {
|
||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
|
||||
} else {
|
||||
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLanguageText(text) {
|
||||
if (!text.includes("|")) {
|
||||
return text;
|
||||
@@ -763,11 +681,6 @@ export function setLanguage(language) {
|
||||
i18next.changeLanguage(language);
|
||||
}
|
||||
|
||||
export function setTheme(themeKey) {
|
||||
localStorage.setItem("theme", themeKey);
|
||||
dispatchEvent(new Event("changeTheme"));
|
||||
}
|
||||
|
||||
export function getAcceptLanguage() {
|
||||
if (i18next.language === null || i18next.language === "") {
|
||||
return "en;q=0.9,en;q=0.8";
|
||||
@@ -948,24 +861,6 @@ export function getProviderTypeOptions(category) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getProviderSubTypeOptions(type) {
|
||||
if (type === "WeCom" || type === "Infoflow") {
|
||||
return (
|
||||
[
|
||||
{id: "Internal", name: "Internal"},
|
||||
{id: "Third-party", name: "Third-party"},
|
||||
]
|
||||
);
|
||||
} else if (type === "Aliyun Captcha") {
|
||||
return [
|
||||
{id: "nc", name: "Sliding Validation"},
|
||||
{id: "ic", name: "Intelligent Validation"},
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function renderLogo(application) {
|
||||
if (application === null) {
|
||||
return null;
|
||||
@@ -1175,18 +1070,28 @@ export function getTagColor(s) {
|
||||
return "processing";
|
||||
}
|
||||
|
||||
export function getTags(tags) {
|
||||
export function getTags(tags, urlPrefix = null) {
|
||||
const res = [];
|
||||
if (!tags) {
|
||||
return res;
|
||||
}
|
||||
|
||||
tags.forEach((tag, i) => {
|
||||
res.push(
|
||||
<Tag color={getTagColor(tag)}>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
if (urlPrefix === null) {
|
||||
res.push(
|
||||
<Tag color={getTagColor(tag)}>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
} else {
|
||||
res.push(
|
||||
<Link to={`/${urlPrefix}/${tag}`}>
|
||||
<Tag color={getTagColor(tag)}>
|
||||
{tag}
|
||||
</Tag>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@@ -1239,94 +1144,3 @@ export function inIframe() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSyncerTableColumns(syncer) {
|
||||
switch (syncer.type) {
|
||||
case "Keycloak":
|
||||
return [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": "string",
|
||||
"casdoorName": "Id",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "USERNAME",
|
||||
"type": "string",
|
||||
"casdoorName": "Name",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "LAST_NAME+FIRST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "DisplayName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "EMAIL",
|
||||
"type": "string",
|
||||
"casdoorName": "Email",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "EMAIL_VERIFIED",
|
||||
"type": "boolean",
|
||||
"casdoorName": "EmailVerified",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "FIRST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "FirstName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "LAST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "LastName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "CREATED_TIMESTAMP",
|
||||
"type": "string",
|
||||
"casdoorName": "CreatedTime",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "ENABLED",
|
||||
"type": "boolean",
|
||||
"casdoorName": "IsForbidden",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import * as SyncerBackend from "./backend/SyncerBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import SyncerTableColumnTable from "./SyncerTableColumnTable";
|
||||
import SyncerTableColumnTable from "./table/SyncerTableColumnTable";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
@@ -80,6 +80,97 @@ class SyncerEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getSyncerTableColumns(syncer) {
|
||||
switch (syncer.type) {
|
||||
case "Keycloak":
|
||||
return [
|
||||
{
|
||||
"name": "ID",
|
||||
"type": "string",
|
||||
"casdoorName": "Id",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "USERNAME",
|
||||
"type": "string",
|
||||
"casdoorName": "Name",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "LAST_NAME+FIRST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "DisplayName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "EMAIL",
|
||||
"type": "string",
|
||||
"casdoorName": "Email",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "EMAIL_VERIFIED",
|
||||
"type": "boolean",
|
||||
"casdoorName": "EmailVerified",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "FIRST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "FirstName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "LAST_NAME",
|
||||
"type": "string",
|
||||
"casdoorName": "LastName",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "CREATED_TIMESTAMP",
|
||||
"type": "string",
|
||||
"casdoorName": "CreatedTime",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "ENABLED",
|
||||
"type": "boolean",
|
||||
"casdoorName": "IsForbidden",
|
||||
"isHashed": true,
|
||||
"values": [
|
||||
|
||||
],
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
renderSyncer() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
@@ -120,7 +211,7 @@ class SyncerEditPage extends React.Component {
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.type} onChange={(value => {
|
||||
this.updateSyncerField("type", value);
|
||||
const syncer = this.state.syncer;
|
||||
syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
|
||||
syncer["tableColumns"] = this.getSyncerTableColumns(this.state.syncer);
|
||||
syncer.table = (value === "Keycloak") ? "user_entity" : this.state.syncer.table;
|
||||
this.setState({
|
||||
syncer: syncer,
|
||||
|
@@ -45,7 +45,7 @@ class SystemInfo extends React.Component {
|
||||
}).catch(error => {
|
||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||
});
|
||||
}, 1000 * 3);
|
||||
}, 1000 * 2);
|
||||
this.setState({intervalId: id});
|
||||
}).catch(error => {
|
||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||
|
@@ -18,18 +18,18 @@ import * as UserBackend from "./backend/UserBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import CropperDiv from "./CropperDiv.js";
|
||||
import CropperDivModal from "./common/modal/CropperDivModal.js";
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||
import PasswordModal from "./PasswordModal";
|
||||
import ResetModal from "./ResetModal";
|
||||
import AffiliationSelect from "./common/AffiliationSelect";
|
||||
import PasswordModal from "./common/modal/PasswordModal";
|
||||
import ResetModal from "./common/modal/ResetModal";
|
||||
import AffiliationSelect from "./common/select/AffiliationSelect";
|
||||
import OAuthWidget from "./common/OAuthWidget";
|
||||
import SamlWidget from "./common/SamlWidget";
|
||||
import SelectRegionBox from "./SelectRegionBox";
|
||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./ManagedAccountTable";
|
||||
import PropertyTable from "./propertyTable";
|
||||
import {CountryCodeSelect} from "./common/CountryCodeSelect";
|
||||
import RegionSelect from "./common/select/RegionSelect";
|
||||
import WebAuthnCredentialTable from "./table/WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./table/ManagedAccountTable";
|
||||
import PropertyTable from "./table/propertyTable";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@@ -253,7 +253,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} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />
|
||||
<CropperDivModal 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>
|
||||
@@ -341,7 +341,7 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("user:Country/Region"), i18next.t("user:Country/Region - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SelectRegionBox defaultValue={this.state.user.region} onChange={(value) => {
|
||||
<RegionSelect defaultValue={this.state.user.region} onChange={(value) => {
|
||||
this.updateUserField("region", value);
|
||||
}} />
|
||||
</Col>
|
||||
@@ -614,7 +614,7 @@ class UserEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitUserEdit(willExist) {
|
||||
submitUserEdit(needExit) {
|
||||
const user = Setting.deepCopy(this.state.user);
|
||||
UserBackend.updateUser(this.state.organizationName, this.state.userName, user)
|
||||
.then((res) => {
|
||||
@@ -626,13 +626,18 @@ class UserEditPage extends React.Component {
|
||||
});
|
||||
|
||||
if (this.props.history !== undefined) {
|
||||
if (willExist) {
|
||||
this.props.history.push("/users");
|
||||
if (needExit) {
|
||||
const userListUrl = sessionStorage.getItem("userListUrl");
|
||||
if (userListUrl !== null) {
|
||||
this.props.history.push(userListUrl);
|
||||
} else {
|
||||
this.props.history.push("/users");
|
||||
}
|
||||
} else {
|
||||
this.props.history.push(`/users/${this.state.user.owner}/${this.state.user.name}`);
|
||||
}
|
||||
} else {
|
||||
if (willExist) {
|
||||
if (needExit) {
|
||||
if (this.state.returnUrl) {
|
||||
window.location.href = this.state.returnUrl;
|
||||
}
|
||||
|
@@ -70,6 +70,7 @@ class UserListPage extends BaseListPage {
|
||||
UserBackend.addUser(newUser)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||
this.props.history.push({pathname: `/users/${newUser.owner}/${newUser.name}`, mode: "add"});
|
||||
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||
} else {
|
||||
@@ -341,7 +342,10 @@ class UserListPage extends BaseListPage {
|
||||
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name);
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => {
|
||||
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||
this.props.history.push(`/users/${record.owner}/${record.name}`);
|
||||
}}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteUser(index)}
|
||||
@@ -402,6 +406,8 @@ class UserListPage extends BaseListPage {
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
} else {
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
@@ -430,6 +436,8 @@ class UserListPage extends BaseListPage {
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
} else {
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
|
@@ -19,7 +19,7 @@ import * as WebhookBackend from "./backend/WebhookBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import WebhookHeaderTable from "./WebhookHeaderTable";
|
||||
import WebhookHeaderTable from "./table/WebhookHeaderTable";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
|
@@ -22,7 +22,7 @@ import i18next from "i18next";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||
import {withRouter} from "react-router-dom";
|
||||
const {Option} = Select;
|
||||
|
||||
@@ -32,11 +32,9 @@ class ForgetPage extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
||||
application: null,
|
||||
msg: null,
|
||||
userId: "",
|
||||
username: "",
|
||||
name: "",
|
||||
username: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
dest: "",
|
||||
@@ -49,7 +47,7 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (this.getApplicationObj() === undefined) {
|
||||
if (this.state.applicationName !== undefined) {
|
||||
this.getApplication();
|
||||
} else {
|
||||
@@ -66,14 +64,11 @@ class ForgetPage extends React.Component {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
return this.props.application ?? this.state.application;
|
||||
return this.props.application;
|
||||
}
|
||||
|
||||
onUpdateApplication(application) {
|
||||
@@ -90,7 +85,7 @@ class ForgetPage extends React.Component {
|
||||
const phone = res.data.phone;
|
||||
const email = res.data.email;
|
||||
|
||||
if (phone === "" && email === "") {
|
||||
if (!phone && !email) {
|
||||
Setting.showMessage("error", "no verification method!");
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -128,18 +123,16 @@ class ForgetPage extends React.Component {
|
||||
});
|
||||
break;
|
||||
case "step2":
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
|
||||
AuthBackend.login({
|
||||
UserBackend.verifyCode({
|
||||
application: forms.step2.getFieldValue("application"),
|
||||
organization: forms.step2.getFieldValue("organization"),
|
||||
username: forms.step2.getFieldValue("dest"),
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("code"),
|
||||
type: "login",
|
||||
}, oAuthParams).then(res => {
|
||||
}).then(res => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({current: 2, userId: res.data});
|
||||
this.setState({current: 2, code: forms.step2.getFieldValue("code")});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
@@ -154,7 +147,7 @@ class ForgetPage extends React.Component {
|
||||
onFinish(values) {
|
||||
values.username = this.state.name;
|
||||
values.userOwner = this.getApplicationObj()?.organizationObj.name;
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||
} else {
|
||||
@@ -391,7 +384,6 @@ class ForgetPage extends React.Component {
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={i18next.t("general:Password")}
|
||||
/>
|
||||
@@ -418,14 +410,13 @@ class ForgetPage extends React.Component {
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
disabled={this.state.userId === ""}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
placeholder={i18next.t("signup:Confirm")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<br />
|
||||
<Form.Item hidden={this.state.current !== 2}>
|
||||
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
|
||||
<Button block type="primary" htmlType="submit">
|
||||
{i18next.t("forget:Change Password")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
@@ -436,6 +427,9 @@ class ForgetPage extends React.Component {
|
||||
|
||||
render() {
|
||||
const application = this.getApplicationObj();
|
||||
if (application === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (application === null) {
|
||||
return Util.renderMessageLarge(this, this.state.msg);
|
||||
}
|
||||
|
@@ -14,11 +14,10 @@
|
||||
|
||||
import i18next from "i18next";
|
||||
import {createButton} from "react-social-login-buttons";
|
||||
import {StaticBaseUrl} from "../Setting";
|
||||
|
||||
function LoginButton({type, align = "center", style = {background: "#ffffff", color: "#000000"}, activeStyle = {background: "#ededee"}}) {
|
||||
function LoginButton({type, logoUrl, align = "center", style = {background: "#ffffff", color: "#000000"}, activeStyle = {background: "#ededee"}}) {
|
||||
function Icon({width = 24, height = 24, color}) {
|
||||
return <img src={`${StaticBaseUrl}/buttons/${type.toLowerCase()}.svg`} alt={`Sign in with ${type}`} style={{width: width, height: height}} />;
|
||||
return <img src={logoUrl} alt={`Sign in with ${type}`} style={{width: width, height: height}} />;
|
||||
}
|
||||
const config = {
|
||||
text: `Sign in with ${type}`,
|
||||
|
@@ -24,12 +24,13 @@ import * as Provider from "./Provider";
|
||||
import * as ProviderButton from "./ProviderButton";
|
||||
import * as Util from "./Util";
|
||||
import * as Setting from "../Setting";
|
||||
import * as AgreementModal from "../common/modal/AgreementModal";
|
||||
import SelfLoginButton from "./SelfLoginButton";
|
||||
import i18next from "i18next";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import {CaptchaModal} from "../common/CaptchaModal";
|
||||
import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
@@ -38,10 +39,9 @@ class LoginPage extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
type: props.type,
|
||||
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName),
|
||||
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
|
||||
application: null,
|
||||
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
|
||||
applicationName: props.applicationName ?? (props.match?.params?.applicationName ?? null),
|
||||
owner: props.owner ?? (props.match?.params?.owner ?? null),
|
||||
mode: props.mode ?? (props.match?.params?.mode ?? null), // "signup" or "signin"
|
||||
msg: null,
|
||||
username: null,
|
||||
validEmailOrPhone: false,
|
||||
@@ -58,21 +58,19 @@ class LoginPage extends React.Component {
|
||||
};
|
||||
|
||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||
this.state.owner = props.match?.params.owner;
|
||||
this.state.applicationName = props.match?.params.casApplicationName;
|
||||
this.state.owner = props.match?.params?.owner;
|
||||
this.state.applicationName = props.match?.params?.casApplicationName;
|
||||
}
|
||||
|
||||
this.form = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (this.state.type === "login" || this.state.type === "cas") {
|
||||
if (this.getApplicationObj() === undefined) {
|
||||
if (this.state.type === "login" || this.state.type === "cas" || this.state.type === "saml") {
|
||||
this.getApplication();
|
||||
} else if (this.state.type === "code") {
|
||||
this.getApplicationLogin();
|
||||
} else if (this.state.type === "saml") {
|
||||
this.getSamlApplication();
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||
}
|
||||
@@ -80,14 +78,35 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (this.state.application && !prevState.application) {
|
||||
const captchaProviderItems = this.getCaptchaProviderItems(this.state.application);
|
||||
|
||||
if (!captchaProviderItems) {
|
||||
return;
|
||||
if (prevProps.application !== this.props.application) {
|
||||
const captchaProviderItems = this.getCaptchaProviderItems(this.props.application);
|
||||
if (captchaProviderItems) {
|
||||
this.setState({enableCaptchaModal: captchaProviderItems.some(providerItem => providerItem.rule === "Always")});
|
||||
}
|
||||
|
||||
this.setState({enableCaptchaModal: captchaProviderItems.some(providerItem => providerItem.rule === "Always")});
|
||||
if (this.props.account && this.props.account.owner === this.props.application?.organization) {
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
const silentSignin = params.get("silentSignin");
|
||||
if (silentSignin !== null) {
|
||||
this.sendSilentSigninData("signing-in");
|
||||
|
||||
const values = {};
|
||||
values["application"] = this.props.application.name;
|
||||
this.login(values);
|
||||
}
|
||||
|
||||
if (params.get("popup") === "1") {
|
||||
window.addEventListener("beforeunload", () => {
|
||||
this.sendPopupData({type: "windowClosed"}, params.get("redirect_uri"));
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.application.enableAutoSignin) {
|
||||
const values = {};
|
||||
values["application"] = this.props.application.name;
|
||||
this.login(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,47 +115,37 @@ class LoginPage extends React.Component {
|
||||
AuthBackend.getApplicationLogin(oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.onUpdateApplication(res.data);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
});
|
||||
const application = res.data;
|
||||
this.onUpdateApplication(application);
|
||||
} else {
|
||||
// Setting.showMessage("error", res.msg);
|
||||
this.onUpdateApplication(null);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
msg: res.msg,
|
||||
});
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
getApplication() {
|
||||
if (this.state.applicationName === null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") {
|
||||
if (this.state.owner === null || this.state.type === "saml") {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
}, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
|
||||
this.setState({termsOfUseContent: res});
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.onUpdateApplication(res.data);
|
||||
const application = res.data;
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: res.data,
|
||||
applicationName: res.data.name,
|
||||
}, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
|
||||
this.setState({termsOfUseContent: res});
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
this.onUpdateApplication(null);
|
||||
Setting.showMessage("error", res.msg);
|
||||
@@ -145,21 +154,8 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getSamlApplication() {
|
||||
if (this.state.applicationName === null) {
|
||||
return;
|
||||
}
|
||||
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
return this.props.application ?? this.state.application;
|
||||
return this.props.application;
|
||||
}
|
||||
|
||||
onUpdateAccount(account) {
|
||||
@@ -183,25 +179,19 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
populateOauthValues(values) {
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if (oAuthParams !== null && oAuthParams.responseType !== null && oAuthParams.responseType !== "") {
|
||||
values["type"] = oAuthParams.responseType;
|
||||
} else {
|
||||
values["type"] = this.state.type;
|
||||
}
|
||||
|
||||
if (oAuthParams !== null) {
|
||||
values["samlRequest"] = oAuthParams.samlRequest;
|
||||
}
|
||||
|
||||
if (values["samlRequest"] !== null && values["samlRequest"] !== "" && values["samlRequest"] !== undefined) {
|
||||
values["type"] = "saml";
|
||||
values["relayState"] = oAuthParams.relayState;
|
||||
}
|
||||
|
||||
if (this.getApplicationObj()?.organization) {
|
||||
values["organization"] = this.getApplicationObj().organization;
|
||||
}
|
||||
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
|
||||
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
||||
|
||||
if (oAuthParams?.samlRequest) {
|
||||
values["samlRequest"] = oAuthParams.samlRequest;
|
||||
values["type"] = "saml";
|
||||
values["relayState"] = oAuthParams.relayState;
|
||||
}
|
||||
}
|
||||
|
||||
sendPopupData(message, redirectUri) {
|
||||
@@ -305,7 +295,6 @@ class LoginPage extends React.Component {
|
||||
// OAuth
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
this.populateOauthValues(values);
|
||||
|
||||
AuthBackend.login(values, oAuthParams)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
@@ -318,7 +307,6 @@ class LoginPage extends React.Component {
|
||||
Setting.goToLink(link);
|
||||
} else if (responseType === "code") {
|
||||
this.postCodeLoginAction(res);
|
||||
// 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`);
|
||||
@@ -471,25 +459,17 @@ class LoginPage extends React.Component {
|
||||
this.renderPasswordOrCodeInput()
|
||||
}
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{
|
||||
Setting.isAgreementRequired(application) ?
|
||||
Setting.renderAgreement(true, () => {
|
||||
this.setState({
|
||||
isTermsOfUseVisible: true,
|
||||
});
|
||||
}, true, {}, Setting.isDefaultTrue(application)) : (
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||
{i18next.t("login:Auto sign in")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
<div style={{display: "inline-flex", justifyContent: "space-between", width: "320px", marginBottom: AgreementModal.isAgreementRequired(application) ? "5px" : "25px"}}>
|
||||
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
|
||||
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
|
||||
{i18next.t("login:Auto sign in")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
{
|
||||
Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
|
||||
}
|
||||
</Form.Item>
|
||||
</div>
|
||||
{AgreementModal.isAgreementRequired(application) ? AgreementModal.renderAgreementFormItem(application, true, {}, this) : null}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -634,37 +614,12 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
const application = this.getApplicationObj();
|
||||
if (this.props.account.owner !== application.organization) {
|
||||
if (this.props.account.owner !== application?.organization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
const silentSignin = params.get("silentSignin");
|
||||
if (silentSignin !== null) {
|
||||
this.sendSilentSigninData("signing-in");
|
||||
|
||||
const values = {};
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
}
|
||||
|
||||
if (params.get("popup") === "1") {
|
||||
window.addEventListener("beforeunload", () => {
|
||||
this.sendPopupData({type: "windowClosed"}, params.get("redirect_uri"));
|
||||
});
|
||||
}
|
||||
|
||||
if (application.enableAutoSignin) {
|
||||
const values = {};
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* {*/}
|
||||
{/* JSON.stringify(silentSignin)*/}
|
||||
{/* }*/}
|
||||
<div style={{fontSize: 16, textAlign: "left"}}>
|
||||
{i18next.t("login:Continue with")} :
|
||||
</div>
|
||||
@@ -672,7 +627,7 @@ class LoginPage extends React.Component {
|
||||
<SelfLoginButton account={this.props.account} onClick={() => {
|
||||
const values = {};
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
this.login(values);
|
||||
}} />
|
||||
<br />
|
||||
<br />
|
||||
@@ -814,6 +769,9 @@ class LoginPage extends React.Component {
|
||||
|
||||
render() {
|
||||
const application = this.getApplicationObj();
|
||||
if (application === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (application === null) {
|
||||
return Util.renderMessageLarge(this, this.state.msg);
|
||||
}
|
||||
@@ -856,29 +814,13 @@ class LoginPage extends React.Component {
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
{/* {*/}
|
||||
{/* this.state.clientId !== null ? "Redirect" : null*/}
|
||||
{/* }*/}
|
||||
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
{
|
||||
this.renderSignedInBox()
|
||||
}
|
||||
{
|
||||
this.renderForm(application)
|
||||
}
|
||||
{
|
||||
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
|
||||
this.form.current.setFieldsValue({agreement: true});
|
||||
this.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}, () => {
|
||||
this.form.current.setFieldsValue({agreement: false});
|
||||
this.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}, this.state.termsOfUseContent)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -19,9 +19,9 @@ import * as UserBackend from "../backend/UserBackend";
|
||||
import * as AuthBackend from "./AuthBackend";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import AffiliationSelect from "../common/AffiliationSelect";
|
||||
import AffiliationSelect from "../common/select/AffiliationSelect";
|
||||
import OAuthWidget from "../common/OAuthWidget";
|
||||
import SelectRegionBox from "../SelectRegionBox";
|
||||
import RegionSelect from "../common/select/RegionSelect";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
class PromptPage extends React.Component {
|
||||
@@ -151,7 +151,7 @@ class PromptPage extends React.Component {
|
||||
</span>
|
||||
</Col>
|
||||
<Col >
|
||||
<SelectRegionBox defaultValue={this.state.user.region} onChange={(value) => {
|
||||
<RegionSelect defaultValue={this.state.user.region} onChange={(value) => {
|
||||
this.updateUserFieldWithoutSubmit("region", value);
|
||||
}} />
|
||||
</Col>
|
||||
|
@@ -378,6 +378,12 @@ export function getAuthUrl(application, provider, method) {
|
||||
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
|
||||
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
|
||||
|
||||
if (provider.type === "AzureAD") {
|
||||
if (provider.domain !== "") {
|
||||
endpoint = endpoint.replace("common", provider.domain);
|
||||
}
|
||||
}
|
||||
|
||||
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
|
||||
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|
||||
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|
||||
|
@@ -44,69 +44,70 @@ import * as AuthBackend from "./AuthBackend";
|
||||
import {getEvent} from "./Util";
|
||||
import {Modal} from "antd";
|
||||
|
||||
function getSigninButton(type) {
|
||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
|
||||
if (type === "GitHub") {
|
||||
function getSigninButton(provider) {
|
||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.type);
|
||||
if (provider.type === "GitHub") {
|
||||
return <GithubLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Google") {
|
||||
} else if (provider.type === "Google") {
|
||||
return <GoogleLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "QQ") {
|
||||
} else if (provider.type === "QQ") {
|
||||
return <QqLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Facebook") {
|
||||
} else if (provider.type === "Facebook") {
|
||||
return <FacebookLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Weibo") {
|
||||
} else if (provider.type === "Weibo") {
|
||||
return <WeiboLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Gitee") {
|
||||
} else if (provider.type === "Gitee") {
|
||||
return <GiteeLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "WeChat") {
|
||||
} else if (provider.type === "WeChat") {
|
||||
return <WechatLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "DingTalk") {
|
||||
} else if (provider.type === "DingTalk") {
|
||||
return <DingTalkLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "LinkedIn") {
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return <LinkedInLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "WeCom") {
|
||||
} else if (provider.type === "WeCom") {
|
||||
return <WeComLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Lark") {
|
||||
} else if (provider.type === "Lark") {
|
||||
return <LarkLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "GitLab") {
|
||||
} else if (provider.type === "GitLab") {
|
||||
return <GitLabLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Adfs") {
|
||||
} else if (provider.type === "Adfs") {
|
||||
return <AdfsLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Casdoor") {
|
||||
} else if (provider.type === "Casdoor") {
|
||||
return <CasdoorLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Baidu") {
|
||||
} else if (provider.type === "Baidu") {
|
||||
return <BaiduLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Alipay") {
|
||||
} else if (provider.type === "Alipay") {
|
||||
return <AlipayLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Infoflow") {
|
||||
} else if (provider.type === "Infoflow") {
|
||||
return <InfoflowLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Apple") {
|
||||
} else if (provider.type === "Apple") {
|
||||
return <AppleLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "AzureAD") {
|
||||
} else if (provider.type === "AzureAD") {
|
||||
return <AzureADLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Slack") {
|
||||
} else if (provider.type === "Slack") {
|
||||
return <SlackLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Steam") {
|
||||
} else if (provider.type === "Steam") {
|
||||
return <SteamLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Bilibili") {
|
||||
} else if (provider.type === "Bilibili") {
|
||||
return <BilibiliLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Okta") {
|
||||
} else if (provider.type === "Okta") {
|
||||
return <OktaLoginButton text={text} align={"center"} />;
|
||||
} else if (type === "Douyin") {
|
||||
} else if (provider.type === "Douyin") {
|
||||
return <DouyinLoginButton text={text} align={"center"} />;
|
||||
} else {
|
||||
return <LoginButton key={type} type={type} />;
|
||||
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
||||
}
|
||||
}
|
||||
|
||||
function getSamlUrl(provider, location) {
|
||||
function goToSamlUrl(provider, location) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const clientId = params.get("client_id");
|
||||
const application = params.get("state");
|
||||
const clientId = params.get("client_id") ?? "";
|
||||
const state = params.get("state");
|
||||
const realRedirectUri = params.get("redirect_uri");
|
||||
const redirectUri = `${window.location.origin}/callback/saml`;
|
||||
const providerName = provider.name;
|
||||
const relayState = `${clientId}&${application}&${providerName}&${realRedirectUri}&${redirectUri}`;
|
||||
|
||||
const relayState = `${clientId}&${state}&${providerName}&${realRedirectUri}&${redirectUri}`;
|
||||
AuthBackend.getSamlLogin(`${provider.owner}/${providerName}`, btoa(relayState)).then((res) => {
|
||||
if (res.data2 === "POST") {
|
||||
document.write(res.data);
|
||||
@@ -148,12 +149,11 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
||||
}
|
||||
} else if (provider.category === "SAML") {
|
||||
return (
|
||||
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)}>
|
||||
<a key={provider.displayName} onClick={() => goToSamlUrl(provider, location)}>
|
||||
<img width={width} height={width} src={getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
} else if (provider.type === "Custom") {
|
||||
// style definition
|
||||
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
|
||||
@@ -172,7 +172,7 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
||||
);
|
||||
} else if (provider.category === "SAML") {
|
||||
return (
|
||||
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)} style={customAStyle}>
|
||||
<a key={provider.displayName} onClick={() => goToSamlUrl(provider, location)} style={customAStyle}>
|
||||
<button style={customButtonStyle}>
|
||||
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
|
||||
<span style={customSpanStyle}>{text}</span>
|
||||
@@ -181,14 +181,27 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||
<a href={Provider.getAuthUrl(application, provider, "signup")}>
|
||||
{
|
||||
getSigninButton(provider.type)
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
// big button, for disable password signin
|
||||
if (provider.category === "SAML") {
|
||||
return (
|
||||
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||
<a onClick={() => goToSamlUrl(provider, location)}>
|
||||
{
|
||||
getSigninButton(provider)
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||
<a href={Provider.getAuthUrl(application, provider, "signup")}>
|
||||
{
|
||||
getSigninButton(provider)
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,18 +49,21 @@ class SamlCallback extends React.Component {
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
const relayState = params.get("relayState");
|
||||
const samlResponse = params.get("samlResponse");
|
||||
|
||||
const messages = atob(relayState).split("&");
|
||||
const clientId = messages[0];
|
||||
const applicationName = (messages[1] === "null" || messages[1] === "undefined") ? "app-built-in" : messages[1];
|
||||
const clientId = messages[0] === "" ? "" : messages[0];
|
||||
const application = messages[0] === "" ? "app-built-in" : "";
|
||||
const state = messages[1];
|
||||
const providerName = messages[2];
|
||||
const redirectUri = messages[3];
|
||||
const responseType = this.getResponseType(redirectUri);
|
||||
|
||||
const body = {
|
||||
type: responseType,
|
||||
application: applicationName,
|
||||
clientId: clientId,
|
||||
provider: providerName,
|
||||
state: applicationName,
|
||||
state: state,
|
||||
application: application,
|
||||
redirectUri: `${window.location.origin}/callback`,
|
||||
method: "signup",
|
||||
relayState: relayState,
|
||||
@@ -71,7 +74,7 @@ class SamlCallback extends React.Component {
|
||||
if (clientId === null || clientId === "") {
|
||||
param = "";
|
||||
} else {
|
||||
param = `?clientId=${clientId}&responseType=${responseType}&redirectUri=${redirectUri}&scope=read&state=${applicationName}`;
|
||||
param = `?clientId=${clientId}&responseType=${responseType}&redirectUri=${redirectUri}&scope=read&state=${state}`;
|
||||
}
|
||||
|
||||
AuthBackend.loginWithSaml(body, param)
|
||||
@@ -83,7 +86,7 @@ class SamlCallback extends React.Component {
|
||||
Setting.goToLink("/");
|
||||
} else if (responseType === "code") {
|
||||
const code = res.data;
|
||||
Setting.goToLink(`${redirectUri}?code=${code}&state=${applicationName}`);
|
||||
Setting.goToLink(`${redirectUri}?code=${code}&state=${state}`);
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
|
@@ -40,7 +40,7 @@ class SelfLoginButton extends React.Component {
|
||||
};
|
||||
|
||||
const SelfLoginButton = createButton(config);
|
||||
return <SelfLoginButton text={this.getAccountShowName()} onClick={() => this.props.onClick()} align={"center"} />;
|
||||
return <SelfLoginButton text={this.getAccountShowName()} onClick={this.props.onClick} align={"center"} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,12 +21,13 @@ import i18next from "i18next";
|
||||
import * as Util from "./Util";
|
||||
import {authConfig} from "./Auth";
|
||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||
import * as AgreementModal from "../common/modal/AgreementModal";
|
||||
import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import SelectRegionBox from "../SelectRegionBox";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import RegionSelect from "../common/select/RegionSelect";
|
||||
import CustomGithubCorner from "../common/CustomGithubCorner";
|
||||
import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {CountryCodeSelect} from "../common/CountryCodeSelect";
|
||||
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@@ -47,7 +48,7 @@ const formItemLayout = {
|
||||
},
|
||||
};
|
||||
|
||||
const tailFormItemLayout = {
|
||||
export const tailFormItemLayout = {
|
||||
wrapperCol: {
|
||||
xs: {
|
||||
span: 24,
|
||||
@@ -65,8 +66,7 @@ class SignupPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
applicationName: props.match.params?.applicationName ?? authConfig.appName,
|
||||
application: null,
|
||||
applicationName: props.match?.params?.applicationName ?? authConfig.appName,
|
||||
email: "",
|
||||
phone: "",
|
||||
countryCode: "",
|
||||
@@ -83,20 +83,17 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let applicationName = this.state.applicationName;
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if (oAuthParams !== null) {
|
||||
applicationName = oAuthParams.state;
|
||||
this.setState({applicationName: oAuthParams.state});
|
||||
const signinUrl = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
}
|
||||
|
||||
if (this.getApplicationObj() === null) {
|
||||
if (applicationName !== undefined) {
|
||||
this.getApplication(applicationName);
|
||||
if (this.getApplicationObj() === undefined) {
|
||||
if (this.state.applicationName !== null) {
|
||||
this.getApplication(this.state.applicationName);
|
||||
} else {
|
||||
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
|
||||
Setting.showMessage("error", `Unknown application name: ${this.state.applicationName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,15 +106,6 @@ class SignupPage extends React.Component {
|
||||
ApplicationBackend.getApplication("admin", applicationName)
|
||||
.then((application) => {
|
||||
this.onUpdateApplication(application);
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
|
||||
if (application !== null && application !== undefined) {
|
||||
Setting.getTermsOfUseContent(application.termsOfUse, res => {
|
||||
this.setState({termsOfUseContent: res});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,7 +122,7 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
|
||||
getApplicationObj() {
|
||||
return this.props.application ?? this.state.application;
|
||||
return this.props.application;
|
||||
}
|
||||
|
||||
onUpdateAccount(account) {
|
||||
@@ -318,7 +306,7 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SelectRegionBox onChange={(value) => {this.setState({region: value});}} />
|
||||
<RegionSelect onChange={(value) => {this.setState({region: value});}} />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Email") {
|
||||
@@ -398,11 +386,11 @@ class SignupPage extends React.Component {
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator: (_, value) => {
|
||||
if (!required && value === "") {
|
||||
if (!required && !value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (value !== "" && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
|
||||
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
|
||||
this.setState({validPhone: false});
|
||||
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
||||
}
|
||||
@@ -484,32 +472,10 @@ class SignupPage extends React.Component {
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (signupItem.name === "Agreement") {
|
||||
return (
|
||||
Setting.renderAgreement(Setting.isAgreementRequired(application), () => {
|
||||
this.setState({
|
||||
isTermsOfUseVisible: true,
|
||||
});
|
||||
}, false, tailFormItemLayout, Setting.isDefaultTrue(application))
|
||||
);
|
||||
return AgreementModal.renderAgreementFormItem(application, required, tailFormItemLayout, this);
|
||||
}
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
return (
|
||||
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
|
||||
this.form.current.setFieldsValue({agreement: true});
|
||||
this.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}, () => {
|
||||
this.form.current.setFieldsValue({agreement: false});
|
||||
this.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}, this.state.termsOfUseContent)
|
||||
);
|
||||
}
|
||||
|
||||
renderForm(application) {
|
||||
if (!application.enableSignUp) {
|
||||
return (
|
||||
@@ -596,7 +562,7 @@ class SignupPage extends React.Component {
|
||||
|
||||
render() {
|
||||
const application = this.getApplicationObj();
|
||||
if (application === null) {
|
||||
if (application === undefined || application === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -622,16 +588,13 @@ class SignupPage extends React.Component {
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
<SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
<LanguageSelect languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
{
|
||||
this.renderForm(application)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.renderModal()
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
@@ -43,27 +43,20 @@ export function renderMessage(msg) {
|
||||
export function renderMessageLarge(ths, msg) {
|
||||
if (msg !== null) {
|
||||
return (
|
||||
<div style={{display: "inline"}}>
|
||||
<Result
|
||||
status="error"
|
||||
title="There was a problem signing you in.."
|
||||
subTitle={msg}
|
||||
extra={[
|
||||
<Button type="primary" key="back" onClick={() => {
|
||||
window.history.go(-2);
|
||||
}}>
|
||||
<Result
|
||||
style={{margin: "0px auto"}}
|
||||
status="error"
|
||||
title="There was a problem signing you in.."
|
||||
subTitle={msg}
|
||||
extra={[
|
||||
<Button type="primary" key="back" onClick={() => {
|
||||
window.history.go(-2);
|
||||
}}>
|
||||
Back
|
||||
</Button>,
|
||||
// <Button key="home" onClick={() => Setting.goToLinkSoft(ths, "/")}>
|
||||
// Home
|
||||
// </Button>,
|
||||
// <Button type="primary" key="signup" onClick={() => Setting.goToLinkSoft(ths, "/signup")}>
|
||||
// Sign Up
|
||||
// </Button>,
|
||||
]}
|
||||
>
|
||||
</Result>
|
||||
</div>
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
</Result>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
@@ -71,7 +64,7 @@ export function renderMessageLarge(ths, msg) {
|
||||
}
|
||||
|
||||
function getRefinedValue(value) {
|
||||
return (value === null) ? "" : value;
|
||||
return value ?? "";
|
||||
}
|
||||
|
||||
export function getCasParameters(params) {
|
||||
@@ -100,7 +93,7 @@ export function getOAuthGetParameters(params) {
|
||||
const relayState = getRefinedValue(queries.get("RelayState"));
|
||||
const noRedirect = getRefinedValue(queries.get("noRedirect"));
|
||||
|
||||
if ((clientId === undefined || clientId === null || clientId === "") && (samlRequest === "" || samlRequest === undefined)) {
|
||||
if (clientId === "" && samlRequest === "") {
|
||||
// login
|
||||
return null;
|
||||
} else {
|
||||
|
@@ -55,11 +55,10 @@ export function getUserApplication(owner, name) {
|
||||
}
|
||||
|
||||
export function updateApplication(owner, name, application) {
|
||||
const newApplication = Setting.deepCopy(application);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-application?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newApplication),
|
||||
body: JSON.stringify(application),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
|
71
web/src/backend/ChatBackend.js
Normal file
71
web/src/backend/ChatBackend.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2023 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 * as Setting from "../Setting";
|
||||
|
||||
export function getChats(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-chats?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getChat(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-chat?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateChat(owner, name, chat) {
|
||||
const newChat = Setting.deepCopy(chat);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-chat?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newChat),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addChat(chat) {
|
||||
const newChat = Setting.deepCopy(chat);
|
||||
return fetch(`${Setting.ServerUrl}/api/add-chat`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newChat),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteChat(chat) {
|
||||
const newChat = Setting.deepCopy(chat);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-chat`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newChat),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
71
web/src/backend/MessageBackend.js
Normal file
71
web/src/backend/MessageBackend.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2023 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 * as Setting from "../Setting";
|
||||
|
||||
export function getMessages(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-messages?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getMessage(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-message?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateMessage(owner, name, message) {
|
||||
const newMessage = Setting.deepCopy(message);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-message?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newMessage),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addMessage(message) {
|
||||
const newMessage = Setting.deepCopy(message);
|
||||
return fetch(`${Setting.ServerUrl}/api/add-message`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newMessage),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteMessage(message) {
|
||||
const newMessage = Setting.deepCopy(message);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-message`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newMessage),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
@@ -93,12 +93,16 @@ export function getAffiliationOptions(url, code) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function setPassword(userOwner, userName, oldPassword, newPassword) {
|
||||
export function setPassword(userOwner, userName, oldPassword, newPassword, code = "") {
|
||||
const formData = new FormData();
|
||||
formData.append("userOwner", userOwner);
|
||||
formData.append("userName", userName);
|
||||
formData.append("oldPassword", oldPassword);
|
||||
formData.append("newPassword", newPassword);
|
||||
if (code) {
|
||||
formData.append("code", code);
|
||||
}
|
||||
|
||||
return fetch(`${Setting.ServerUrl}/api/set-password`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
@@ -188,3 +192,14 @@ export function getCaptcha(owner, name, isCurrentProvider) {
|
||||
},
|
||||
}).then(res => res.json()).then(res => res.data);
|
||||
}
|
||||
|
||||
export function verifyCode(values) {
|
||||
return fetch(`${Setting.ServerUrl}/api/verify-code`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(values),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
import {Button} from "antd";
|
||||
import React from "react";
|
||||
import i18next from "i18next";
|
||||
import {CaptchaModal} from "./CaptchaModal";
|
||||
import {CaptchaModal} from "./modal/CaptchaModal";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
|
||||
export const CaptchaPreview = (props) => {
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Conf from "./Conf";
|
||||
import * as Conf from "../Conf";
|
||||
import GithubCorner from "react-github-corner";
|
||||
|
||||
class CustomGithubCorner extends React.Component {
|
@@ -17,7 +17,7 @@ import React from "react";
|
||||
import i18next from "i18next";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
import {CaptchaModal} from "./CaptchaModal";
|
||||
import {CaptchaModal} from "./modal/CaptchaModal";
|
||||
|
||||
const {Search} = Input;
|
||||
|
||||
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function sendTestEmail(provider, email) {
|
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
export function sendTestSms(provider, phone) {
|
126
web/src/common/modal/AgreementModal.js
Normal file
126
web/src/common/modal/AgreementModal.js
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2023 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 {Checkbox, Form, Modal} from "antd";
|
||||
import i18next from "i18next";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
export const AgreementModal = (props) => {
|
||||
const {open, onOk, onCancel, application} = props;
|
||||
const [doc, setDoc] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getTermsOfUseContent(application.termsOfUse).then((data) => {
|
||||
setDoc(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
<Modal
|
||||
title={i18next.t("signup:Terms of Use")}
|
||||
open={open}
|
||||
width={"55vw"}
|
||||
closable={false}
|
||||
okText={i18next.t("signup:Accept")}
|
||||
cancelText={i18next.t("signup:Decline")}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
>
|
||||
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
function getTermsOfUseContent(url) {
|
||||
return fetch(url, {
|
||||
method: "GET",
|
||||
}).then(r => r.text());
|
||||
}
|
||||
|
||||
export function isAgreementRequired(application) {
|
||||
if (application) {
|
||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||
if (!agreementItem || agreementItem.rule === "None" || !agreementItem.rule) {
|
||||
return false;
|
||||
}
|
||||
if (agreementItem.required) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function initDefaultValue(application) {
|
||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||
|
||||
return isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)";
|
||||
}
|
||||
|
||||
export function renderAgreementFormItem(application, required, layout, ths) {
|
||||
return (<React.Fragment>
|
||||
<Form.Item
|
||||
name="agreement"
|
||||
key="agreement"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
},
|
||||
() => ({
|
||||
validator: (_, value) => {
|
||||
if (!required) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return Promise.reject(i18next.t("signup:Please accept the agreement!"));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
{...layout}
|
||||
initialValue={initDefaultValue(application)}
|
||||
>
|
||||
<Checkbox style={{float: "left"}}>
|
||||
{i18next.t("signup:Accept")}
|
||||
<a onClick={() => {
|
||||
ths.setState({
|
||||
isTermsOfUseVisible: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18next.t("signup:Terms of Use")}
|
||||
</a>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
<AgreementModal application={application} layout={layout} open={ths.state.isTermsOfUseVisible}
|
||||
onOk={() => {
|
||||
ths.form.current.setFieldsValue({agreement: true});
|
||||
ths.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
ths.form.current.setFieldsValue({agreement: false});
|
||||
ths.setState({
|
||||
isTermsOfUseVisible: false,
|
||||
});
|
||||
}} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
@@ -15,8 +15,8 @@
|
||||
import {Button, Col, Input, Modal, Row} from "antd";
|
||||
import i18next from "i18next";
|
||||
import React, {useEffect} from "react";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {CaptchaWidget} from "./CaptchaWidget";
|
||||
import * as UserBackend from "../../backend/UserBackend";
|
||||
import {CaptchaWidget} from "../CaptchaWidget";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
|
||||
export const CaptchaModal = (props) => {
|
@@ -15,12 +15,12 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Cropper from "react-cropper";
|
||||
import "cropperjs/dist/cropper.css";
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../../Setting";
|
||||
import {Button, Col, Modal, Row, Select} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as ResourceBackend from "./backend/ResourceBackend";
|
||||
import * as ResourceBackend from "../../backend/ResourceBackend";
|
||||
|
||||
export const CropperDiv = (props) => {
|
||||
export const CropperDivModal = (props) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [options, setOptions] = useState([]);
|
||||
const [image, setImage] = useState("");
|
||||
@@ -60,7 +60,7 @@ export const CropperDiv = (props) => {
|
||||
// Setting.showMessage("success", "uploading...");
|
||||
const extension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64"));
|
||||
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
|
||||
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
|
||||
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDivModal", fullFilePath, blob)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
window.location.href = window.location.pathname;
|
||||
@@ -187,4 +187,4 @@ export const CropperDiv = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CropperDiv;
|
||||
export default CropperDivModal;
|
@@ -15,8 +15,8 @@
|
||||
import {Button, Col, Input, Modal, Row} from "antd";
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import * as UserBackend from "../../backend/UserBackend";
|
||||
import * as Setting from "../../Setting";
|
||||
|
||||
export const PasswordModal = (props) => {
|
||||
const [visible, setVisible] = React.useState(false);
|
@@ -15,9 +15,9 @@
|
||||
import {Button, Col, Input, Modal, Row} from "antd";
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import {SendCodeInput} from "./common/SendCodeInput";
|
||||
import * as Setting from "../../Setting";
|
||||
import * as UserBackend from "../../backend/UserBackend";
|
||||
import {SendCodeInput} from "../SendCodeInput";
|
||||
import {MailOutlined, PhoneOutlined} from "@ant-design/icons";
|
||||
|
||||
export const ResetModal = (props) => {
|
@@ -1,119 +1,119 @@
|
||||
// Copyright 2021 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 {Cascader, Col, Input, Row, Select} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
class AffiliationSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
addressOptions: [],
|
||||
affiliationOptions: [],
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getAddressOptions(this.props.application);
|
||||
this.getAffiliationOptions(this.props.application, this.props.user);
|
||||
}
|
||||
|
||||
getAddressOptions(application) {
|
||||
if (application.affiliationUrl === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const addressUrl = application.affiliationUrl.split("|")[0];
|
||||
UserBackend.getAddressOptions(addressUrl)
|
||||
.then((addressOptions) => {
|
||||
this.setState({
|
||||
addressOptions: addressOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAffiliationOptions(application, user) {
|
||||
if (application.affiliationUrl === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const affiliationUrl = application.affiliationUrl.split("|")[1];
|
||||
const code = user.address[user.address.length - 1];
|
||||
UserBackend.getAffiliationOptions(affiliationUrl, code)
|
||||
.then((affiliationOptions) => {
|
||||
this.setState({
|
||||
affiliationOptions: affiliationOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUserField(key, value) {
|
||||
this.props.onUpdateUserField(key, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
this.props.application?.affiliationUrl === "" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
||||
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={24 - this.props.labelSpan} >
|
||||
<Cascader style={{width: "100%", maxWidth: "400px"}} value={this.props.user.address} options={this.state.addressOptions} onChange={value => {
|
||||
this.updateUserField("address", value);
|
||||
this.updateUserField("affiliation", "");
|
||||
this.updateUserField("score", 0);
|
||||
this.getAffiliationOptions(this.props.application, this.props.user);
|
||||
}} placeholder={i18next.t("signup:Please input your address!")} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
||||
{Setting.getLabel(i18next.t("user:Affiliation"), i18next.t("user:Affiliation - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
{
|
||||
this.props.application?.affiliationUrl === "" ? (
|
||||
<Input value={this.props.user.affiliation} onChange={e => {
|
||||
this.updateUserField("affiliation", e.target.value);
|
||||
}} />
|
||||
) : (
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.props.user.affiliation}
|
||||
onChange={(value => {
|
||||
const name = value;
|
||||
const affiliationOption = Setting.getArrayItem(this.state.affiliationOptions, "name", name);
|
||||
const id = affiliationOption.id;
|
||||
this.updateUserField("affiliation", name);
|
||||
this.updateUserField("score", id);
|
||||
})}
|
||||
options={[Setting.getOption(`(${i18next.t("general:empty")})`, "")].concat(this.state.affiliationOptions.map((affiliationOption) => Setting.getOption(affiliationOption.name, affiliationOption.name))
|
||||
)} />
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AffiliationSelect;
|
||||
// Copyright 2021 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 {Cascader, Col, Input, Row, Select} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as UserBackend from "../../backend/UserBackend";
|
||||
import * as Setting from "../../Setting";
|
||||
|
||||
class AffiliationSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
addressOptions: [],
|
||||
affiliationOptions: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getAddressOptions(this.props.application);
|
||||
this.getAffiliationOptions(this.props.application, this.props.user);
|
||||
}
|
||||
|
||||
getAddressOptions(application) {
|
||||
if (application.affiliationUrl === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const addressUrl = application.affiliationUrl.split("|")[0];
|
||||
UserBackend.getAddressOptions(addressUrl)
|
||||
.then((addressOptions) => {
|
||||
this.setState({
|
||||
addressOptions: addressOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAffiliationOptions(application, user) {
|
||||
if (application.affiliationUrl === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const affiliationUrl = application.affiliationUrl.split("|")[1];
|
||||
const code = user.address[user.address.length - 1];
|
||||
UserBackend.getAffiliationOptions(affiliationUrl, code)
|
||||
.then((affiliationOptions) => {
|
||||
this.setState({
|
||||
affiliationOptions: affiliationOptions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateUserField(key, value) {
|
||||
this.props.onUpdateUserField(key, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
this.props.application?.affiliationUrl === "" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
||||
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={24 - this.props.labelSpan} >
|
||||
<Cascader style={{width: "100%", maxWidth: "400px"}} value={this.props.user.address} options={this.state.addressOptions} onChange={value => {
|
||||
this.updateUserField("address", value);
|
||||
this.updateUserField("affiliation", "");
|
||||
this.updateUserField("score", 0);
|
||||
this.getAffiliationOptions(this.props.application, this.props.user);
|
||||
}} placeholder={i18next.t("signup:Please input your address!")} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={this.props.labelSpan}>
|
||||
{Setting.getLabel(i18next.t("user:Affiliation"), i18next.t("user:Affiliation - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
{
|
||||
this.props.application?.affiliationUrl === "" ? (
|
||||
<Input value={this.props.user.affiliation} onChange={e => {
|
||||
this.updateUserField("affiliation", e.target.value);
|
||||
}} />
|
||||
) : (
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.props.user.affiliation}
|
||||
onChange={(value => {
|
||||
const name = value;
|
||||
const affiliationOption = Setting.getArrayItem(this.state.affiliationOptions, "name", name);
|
||||
const id = affiliationOption.id;
|
||||
this.updateUserField("affiliation", name);
|
||||
this.updateUserField("score", id);
|
||||
})}
|
||||
options={[Setting.getOption(`(${i18next.t("general:empty")})`, "")].concat(this.state.affiliationOptions.map((affiliationOption) => Setting.getOption(affiliationOption.name, affiliationOption.name))
|
||||
)} />
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AffiliationSelect;
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import {Select} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import * as Setting from "../../Setting";
|
||||
import React from "react";
|
||||
|
||||
export const CountryCodeSelect = (props) => {
|
@@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../../Setting";
|
||||
import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
import "../../App.less";
|
||||
import {GlobalOutlined} from "@ant-design/icons";
|
||||
|
||||
function flagIcon(country, alt) {
|
||||
@@ -24,7 +24,7 @@ function flagIcon(country, alt) {
|
||||
);
|
||||
}
|
||||
|
||||
class SelectLanguageBox extends React.Component {
|
||||
class LanguageSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -63,4 +63,4 @@ class SelectLanguageBox extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectLanguageBox;
|
||||
export default LanguageSelect;
|
@@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../../Setting";
|
||||
import {Select} from "antd";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
class SelectRegionBox extends React.Component {
|
||||
class RegionSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -59,4 +59,4 @@ class SelectRegionBox extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectRegionBox;
|
||||
export default RegionSelect;
|
@@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "./Setting";
|
||||
import * as Setting from "../../Setting";
|
||||
import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
import "../../App.less";
|
||||
import i18next from "i18next";
|
||||
import {CheckOutlined} from "@ant-design/icons";
|
||||
import {CompactTheme, DarkTheme, Light} from "antd-token-previewer/es/icons";
|
||||
@@ -34,7 +34,7 @@ function getIcon(themeKey) {
|
||||
}
|
||||
}
|
||||
|
||||
class SelectThemeBox extends React.Component {
|
||||
class ThemeSelect extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
@@ -91,4 +91,4 @@ class SelectThemeBox extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectThemeBox;
|
||||
export default ThemeSelect;
|
@@ -22,7 +22,7 @@ import id from "./locales/id/data.json";
|
||||
import ja from "./locales/ja/data.json";
|
||||
import ko from "./locales/ko/data.json";
|
||||
import ru from "./locales/ru/data.json";
|
||||
import vi from "./locales/vi/data.json";
|
||||
import vn from "./locales/vn/data.json";
|
||||
import * as Conf from "./Conf";
|
||||
import {initReactI18next} from "react-i18next";
|
||||
|
||||
@@ -36,7 +36,7 @@ const resources = {
|
||||
ja: ja,
|
||||
ko: ko,
|
||||
ru: ru,
|
||||
vi: vi,
|
||||
vn: vn,
|
||||
};
|
||||
|
||||
function initLanguage() {
|
||||
@@ -80,8 +80,8 @@ function initLanguage() {
|
||||
case "ru":
|
||||
language = "ru";
|
||||
break;
|
||||
case "vi":
|
||||
language = "vi";
|
||||
case "vn":
|
||||
language = "vn";
|
||||
break;
|
||||
default:
|
||||
language = Conf.DefaultLanguage;
|
||||
|
@@ -1,110 +1,119 @@
|
||||
{
|
||||
"account": {
|
||||
"Logout": "Abmeldung",
|
||||
"Logout": "Abmelden",
|
||||
"My Account": "Mein Konto",
|
||||
"Sign Up": "Anmeldung"
|
||||
"Sign Up": "Anmelden"
|
||||
},
|
||||
"adapter": {
|
||||
"Duplicated policy rules": "Doppelte Richtlinienregeln",
|
||||
"Edit Adapter": "Bearbeiten Sie den Adapter",
|
||||
"Failed to sync policies": "Fehler beim Synchronisieren von Richtlinien",
|
||||
"Edit Adapter": "Adapter bearbeiten",
|
||||
"Failed to sync policies": "Fehler beim Synchronisieren der Richtlinien",
|
||||
"New Adapter": "Neuer Adapter",
|
||||
"Policies": "Richtlinien",
|
||||
"Policies - Tooltip": "Casbin-Richtlinienregeln",
|
||||
"Sync policies successfully": "Synchronisierungspolitiken erfolgreich umgesetzt"
|
||||
"Policies - Tooltip": "Casbin Richtlinienregeln",
|
||||
"Sync policies successfully": "Richtlinien synchronisiert"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Immer",
|
||||
"Auto signin": "Automatische Anmeldung",
|
||||
"Auto signin - Tooltip": "Wenn eine angemeldete Sitzung in Casdoor vorhanden ist, wird sie automatisch für die Anmeldung auf Anwendungsebene verwendet",
|
||||
"Background URL": "Hintergrund-URL",
|
||||
"Background URL - Tooltip": "URL des Hintergrundbildes, das auf der Anmeldeseite verwendet wird",
|
||||
"Auto signin - Tooltip": "Wenn eine angemeldete Session in Casdoor vorhanden ist, wird diese automatisch für die Anmeldung auf Anwendungsebene verwendet",
|
||||
"Background URL": "Background-URL",
|
||||
"Background URL - Tooltip": "URL des Hintergrundbildes, das auf der Anmeldeseite angezeigt wird",
|
||||
"Center": "Zentrum",
|
||||
"Copy SAML metadata URL": "Kopieren Sie die SAML-Metadaten-URL",
|
||||
"Copy prompt page URL": "Kopiere die URL der Prompt-Seite",
|
||||
"Copy signin page URL": "Kopieren Sie die URL der Anmeldeseite",
|
||||
"Copy signup page URL": "Kopieren Sie die URL der Anmeldeseite",
|
||||
"Edit Application": "Bearbeitungsanwendung",
|
||||
"Copy SAML metadata URL": "SAML-Metadaten-URL kopieren",
|
||||
"Copy prompt page URL": "URL der Prompt-Seite kopieren",
|
||||
"Copy signin page URL": "URL der Anmeldeseite kopieren",
|
||||
"Copy signup page URL": "URL der Anmeldeseite kopieren",
|
||||
"Edit Application": "Anwendung bearbeiten",
|
||||
"Enable Email linking": "E-Mail-Verknüpfung aktivieren",
|
||||
"Enable Email linking - Tooltip": "Bei der Verwendung von 3rd-Party-Anbietern zur Anmeldung wird, wenn es in der Organisation einen Benutzer mit der gleichen E-Mail gibt, automatisch die 3rd-Party-Anmelde-Methode mit diesem Benutzer verbunden",
|
||||
"Enable SAML compression": "Aktivieren Sie SAML-Komprimierung",
|
||||
"Enable Email linking - Tooltip": "Bei der Verwendung von Drittanbietern zur Anmeldung wird, wenn es in der Organisation einen Benutzer mit der gleichen E-Mail gibt, automatisch die Drittanbieter-Anmelde-Methode mit diesem Benutzer verbunden",
|
||||
"Enable SAML compression": "SAML-Komprimierung aktivieren",
|
||||
"Enable SAML compression - Tooltip": "Ob SAML-Antwortnachrichten komprimiert werden sollen, wenn Casdoor als SAML-IdP verwendet wird",
|
||||
"Enable WebAuthn signin": "Aktivieren Sie die Anmeldung mit WebAuthn",
|
||||
"Enable WebAuthn signin": "Anmeldung mit WebAuthn aktivieren",
|
||||
"Enable WebAuthn signin - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit WebAuthn anzumelden",
|
||||
"Enable code signin": "Aktivieren Sie die Code-Unterzeichnung",
|
||||
"Enable code signin - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit einer Telefon- oder E-Mail-Bestätigungscode anzumelden",
|
||||
"Enable password": "Aktiviere das Passwort",
|
||||
"Enable code signin": "Code Anmeldung aktivieren",
|
||||
"Enable code signin - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit einem Telefon- oder E-Mail-Bestätigungscode anzumelden",
|
||||
"Enable password": "Passwort aktivieren",
|
||||
"Enable password - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit einem Passwort anzumelden",
|
||||
"Enable side panel": "Aktiviere die Seitenleiste",
|
||||
"Enable side panel": "Sidepanel aktivieren",
|
||||
"Enable signin session - Tooltip": "Ob Casdoor eine Sitzung aufrechterhält, nachdem man sich von der Anwendung aus bei Casdoor angemeldet hat",
|
||||
"Enable signup": "Aktivieren Sie die Registrierung",
|
||||
"Enable signup": "Registrierung aktivieren",
|
||||
"Enable signup - Tooltip": "Ob Benutzern erlaubt werden soll, ein neues Konto zu registrieren",
|
||||
"Failed to sign in": "Fehler bei der Anmeldung",
|
||||
"File uploaded successfully": "Datei erfolgreich hochgeladen",
|
||||
"Follow organization theme": "Folge dem Thema der Organisation",
|
||||
"Form CSS": "Formular CSS",
|
||||
"Form CSS - Edit": "Formular CSS - Bearbeiten",
|
||||
"Form CSS - Tooltip": "CSS-Styling der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare (z. B. Hinzufügen von Rahmen und Schatten)",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Folge dem Theme der Organisation",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Bearbeiten",
|
||||
"Form CSS - Tooltip": "CSS-Styling der Anmelde-, Registrierungs- und Passwort-vergessen-Seite (z. B. Hinzufügen von Rahmen und Schatten)",
|
||||
"Form position": "Formposition",
|
||||
"Form position - Tooltip": "Ort der Anmelde-, Anmelde- und Passwort vergessen-Formulare",
|
||||
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
|
||||
"Grant types": "Grant-Typen",
|
||||
"Grant types - Tooltip": "Wählen Sie aus, welche Förderarten im OAuth-Protokoll zulässig sind",
|
||||
"Grant types - Tooltip": "Wählen Sie aus, welche Grant-Typen im OAuth-Protokoll zulässig sind",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Links",
|
||||
"Logged in successfully": "Erfolgreich eingeloggt",
|
||||
"Logged out successfully": "Erfolgreich ausgeloggt",
|
||||
"New Application": "Neue Anwendung",
|
||||
"None": "kein",
|
||||
"Please input your application!": "Bitte geben Sie Ihre Bewerbung ein!",
|
||||
"No verification": "No verification",
|
||||
"None": "kein(e)",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "Bitte geben Sie Ihre Anwendung ein!",
|
||||
"Please input your organization!": "Bitte geben Sie Ihre Organisation ein!",
|
||||
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei aus",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Seite wurde erfolgreich in die Zwischenablage kopiert. Bitte fügen Sie sie in das Inkognito-Fenster oder einen anderen Browser ein",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Seite wurde erfolgreich in die Zwischenablage kopiert. Bitte fügen Sie sie in einen Inkognito-Tab oder einen anderen Browser ein",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Weiterleitungs-URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Weiterleitungs-URL (Assertion Consumer Service POST Binding URL)",
|
||||
"Redirect URLs": "Weiterleitungs-URLs",
|
||||
"Redirect URLs - Tooltip": "Liste erlaubter Umleitungs-URLs mit Unterstützung von regulärer Ausdrucksprüfung; URLs, die nicht in der Liste enthalten sind, können nicht umgeleitet werden",
|
||||
"Refresh token expire": "Das Auffrischen des Tokens ist abgelaufen",
|
||||
"Refresh token expire - Tooltip": "Ablaufzeit des Aktualisierungstokens",
|
||||
"Refresh token expire": "Gültigkeitsdauer des Refresh-Tokens",
|
||||
"Refresh token expire - Tooltip": "Angabe der Gültigkeitsdauer des Refresh Tokens",
|
||||
"Right": "Rechts",
|
||||
"Rule": "Regel",
|
||||
"SAML metadata": "SAML-Metadaten",
|
||||
"SAML metadata - Tooltip": "Die Metadaten des SAML-Protokolls",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML-Metadaten URL erfolgreich in die Zwischenablage kopiert",
|
||||
"SAML reply URL": "SAML Antwort-URL",
|
||||
"Side panel HTML": "Seitenleisten-HTML",
|
||||
"Side panel HTML - Edit": "Seitenleisten HTML - Bearbeiten",
|
||||
"Side panel HTML - Tooltip": "Passen Sie den HTML-Code für das Seitenfeld der Login-Seite an",
|
||||
"SAML reply URL": "SAML Reply-URL",
|
||||
"Side panel HTML": "Sidepanel-HTML",
|
||||
"Side panel HTML - Edit": "Sidepanel HTML - Bearbeiten",
|
||||
"Side panel HTML - Tooltip": "Passen Sie den HTML-Code für das Sidepanel der Login-Seite an",
|
||||
"Sign Up Error": "Registrierungsfehler",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Anmeldeseite wurde erfolgreich in die Zwischenablage kopiert. Bitte fügen Sie sie in das Inkognito-Fenster oder einen anderen Browser ein",
|
||||
"Signin session": "Anmeldesitzung",
|
||||
"Signup items": "Anmeldungsartikel",
|
||||
"Signup items - Tooltip": "Gegenstände, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
|
||||
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Anmeldeseite-URL erfolgreich in die Zwischenablage kopiert. Bitte fügen Sie sie in das Inkognito-Fenster oder einen anderen Browser ein",
|
||||
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto anzumelden",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Anmeldeseite wurde in die Zwischenablage kopiert. Bitte fügen Sie sie in einen Inkognito-Tab oder einen anderen Browser ein",
|
||||
"Signin session": "Anmeldesession",
|
||||
"Signup items": "Registrierungs Items",
|
||||
"Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
|
||||
"Signup page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Die URL der Registrierungsseite wurde in die Zwischenablage kopiert. Bitte fügen Sie sie in einen Inkognito-Tab oder einen anderen Browser ein",
|
||||
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
|
||||
"Token expire": "Token läuft ab",
|
||||
"Token expire - Tooltip": "Ablaufzeit des Zugriffstokens",
|
||||
"Token expire - Tooltip": "Ablaufzeit des Access-Tokens",
|
||||
"Token format": "Token-Format",
|
||||
"Token format - Tooltip": "Das Format des Zugriffstokens",
|
||||
"You are unexpected to see this prompt page": "Du bist unerwartet diese Aufforderungsseite zu sehen"
|
||||
"Token format - Tooltip": "Das Format des Access-Tokens",
|
||||
"You are unexpected to see this prompt page": "Sie sind unerwartet auf diese Aufforderungsseite gelangt"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bitgröße",
|
||||
"Bit size - Tooltip": "Geheimschlüssellänge",
|
||||
"Bit size - Tooltip": "Länge des Secret-Keys",
|
||||
"Certificate": "Zertifikat",
|
||||
"Certificate - Tooltip": "Öffentliches Schlüsselzertifikat, das zum Entschlüsseln der JWT-Signatur des Access Tokens verwendet wird. Dieses Zertifikat muss normalerweise auf der Casdoor SDK-Seite (d. h. der Anwendung) bereitgestellt werden, um das JWT zu analysieren",
|
||||
"Certificate copied to clipboard successfully": "Zertifikat erfolgreich in die Zwischenablage kopiert",
|
||||
"Certificate - Tooltip": "Public-Key-Zertifikat, das zum Entschlüsseln der JWT-Signatur des Access Tokens verwendet wird. Dieses Zertifikat muss normalerweise auf der Casdoor SDK-Seite (d. h. der Anwendung) bereitgestellt werden, um das JWT zu parsen",
|
||||
"Certificate copied to clipboard successfully": "Zertifikat in die Zwischenablage kopiert",
|
||||
"Copy certificate": "Kopieren Sie das Zertifikat",
|
||||
"Copy private key": "Privaten Schlüssel kopieren",
|
||||
"Copy private key": "Private-Key kopieren",
|
||||
"Crypto algorithm": "Kryptoalgorithmus",
|
||||
"Crypto algorithm - Tooltip": "Verschlüsselungsalgorithmus, der vom Zertifikat verwendet wird",
|
||||
"Download certificate": "Zertifikat herunterladen",
|
||||
"Download private key": "Privater Schlüssel herunterladen",
|
||||
"Download private key": "Private-Key herunterladen",
|
||||
"Edit Cert": "Edit Cert - Zertifikat bearbeiten",
|
||||
"Expire in years": "Verfallen in Jahren",
|
||||
"Expire in years": "Ablaufzeit in Jahren",
|
||||
"Expire in years - Tooltip": "Gültigkeitsdauer des Zertifikats in Jahren",
|
||||
"New Cert": "Neues Zertifikat",
|
||||
"Private key": "Privater Schlüssel",
|
||||
"Private key": "Private-Key",
|
||||
"Private key - Tooltip": "Privater Schlüssel, der zum öffentlichen Schlüsselzertifikat gehört",
|
||||
"Private key copied to clipboard successfully": "Privater Schlüssel wurde erfolgreich in die Zwischenablage kopiert",
|
||||
"Private key copied to clipboard successfully": "Private-Key wurde erfolgreich in die Zwischenablage kopiert",
|
||||
"Scope - Tooltip": "Nutzungsszenarien des Zertifikats",
|
||||
"Type - Tooltip": "Art des Zertifikats"
|
||||
},
|
||||
@@ -117,7 +126,7 @@
|
||||
"Please input your phone verification code!": "Bitte geben Sie den Bestätigungscode Ihres Telefons ein!",
|
||||
"Please input your verification code!": "Bitte geben Sie Ihren Bestätigungscode ein!",
|
||||
"Send Code": "Code senden",
|
||||
"Sending": "Senden",
|
||||
"Sending": "Sendet",
|
||||
"Submit and complete": "Einreichen und abschließen"
|
||||
},
|
||||
"forget": {
|
||||
@@ -125,7 +134,7 @@
|
||||
"Change Password": "Passwort ändern",
|
||||
"Choose email or phone": "Wählen Sie E-Mail oder Telefon",
|
||||
"Next Step": "Nächster Schritt",
|
||||
"Please input your username!": "Bitte gib deinen Benutzernamen ein!",
|
||||
"Please input your username!": "Bitte geben Sie Ihren Benutzernamen ein!",
|
||||
"Reset": "Zurücksetzen",
|
||||
"Retrieve password": "Passwort abrufen",
|
||||
"Unknown forget type": "Unbekannter Vergesslichkeitstyp",
|
||||
@@ -137,24 +146,24 @@
|
||||
"Adapter - Tooltip": "Tabellenname des Policy Stores",
|
||||
"Adapters": "Adapter",
|
||||
"Add": "Hinzufügen",
|
||||
"Affiliation URL": "Zugehörigkeits-URL",
|
||||
"Affiliation URL": "Affiliation-URL",
|
||||
"Affiliation URL - Tooltip": "Die Homepage-URL für die Zugehörigkeit",
|
||||
"Application": "Bewerbung",
|
||||
"Application": "Applikation",
|
||||
"Applications": "Anwendungen",
|
||||
"Applications that require authentication": "Anwendungen, die eine Authentifizierung erfordern",
|
||||
"Avatar": "Avatar",
|
||||
"Avatar - Tooltip": "Öffentliches Avatarbild für den Benutzer",
|
||||
"Back Home": "Zurück zu Hause",
|
||||
"Back Home": "Zurück nach Hause",
|
||||
"Cancel": "Abbrechen",
|
||||
"Captcha": "Captcha",
|
||||
"Cert": "Zertifikat",
|
||||
"Cert - Tooltip": "Das öffentliche Schlüsselzertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss",
|
||||
"Cert - Tooltip": "Das Public-Key-Zertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss",
|
||||
"Certs": "Zertifikate",
|
||||
"Click to Upload": "Klicken Sie zum Hochladen",
|
||||
"Client IP": "Kunden-IP",
|
||||
"Client IP": "Client-IP",
|
||||
"Close": "Schließen",
|
||||
"Created time": "Erstellte Zeit",
|
||||
"Default application": "Standardanwendung",
|
||||
"Default application": "Standard Anwendung",
|
||||
"Default application - Tooltip": "Standard-Anwendung für Benutzer, die direkt von der Organisationsseite registriert wurden",
|
||||
"Default avatar": "Standard-Avatar",
|
||||
"Default avatar - Tooltip": "Standard-Avatar, der verwendet wird, wenn neu registrierte Benutzer kein Avatar-Bild festlegen",
|
||||
@@ -167,16 +176,16 @@
|
||||
"Edit": "Bearbeiten",
|
||||
"Email": "E-Mail",
|
||||
"Email - Tooltip": "Gültige E-Mail-Adresse",
|
||||
"Failed to add": "Konnte nicht hinzufügen",
|
||||
"Failed to add": "Fehler beim hinzufügen",
|
||||
"Failed to connect to server": "Die Verbindung zum Server konnte nicht hergestellt werden",
|
||||
"Failed to delete": "Konnte nicht gelöscht werden",
|
||||
"Failed to save": "Konnte nicht gespeichert werden",
|
||||
"Favicon": "Favicon",
|
||||
"Favicon - Tooltip": "Favicon Icon-URL, die auf allen Casdoor-Seiten der Organisation verwendet wird",
|
||||
"Favicon - Tooltip": "Favicon-URL, die auf allen Casdoor-Seiten der Organisation verwendet wird",
|
||||
"First name": "Vorname",
|
||||
"Forget URL": "Vergessen Sie die URL",
|
||||
"Forget URL": "Passwort vergessen URL",
|
||||
"Forget URL - Tooltip": "Benutzerdefinierte URL für die \"Passwort vergessen\" Seite. Wenn nicht festgelegt, wird die standardmäßige Casdoor \"Passwort vergessen\" Seite verwendet. Wenn sie festgelegt ist, wird der \"Passwort vergessen\" Link auf der Login-Seite zu dieser URL umgeleitet",
|
||||
"Found some texts still not translated? Please help us translate at": "Hast du noch einige Texte gefunden, die nicht übersetzt wurden? Bitte hilf uns beim Übersetzen",
|
||||
"Found some texts still not translated? Please help us translate at": "Haben Sie noch Texte gefunden, die nicht übersetzt wurden? Bitte helfen Sie uns beim Übersetzen",
|
||||
"Go to writable demo site?": "Gehe zur beschreibbaren Demo-Website?",
|
||||
"Home": "Zuhause",
|
||||
"Home - Tooltip": "Homepage der Anwendung",
|
||||
@@ -207,22 +216,22 @@
|
||||
"Organizations": "Organisationen",
|
||||
"Password": "Passwort",
|
||||
"Password - Tooltip": "Stellen Sie sicher, dass das Passwort korrekt ist",
|
||||
"Password salt": "Passworthash",
|
||||
"Password salt": "Passwort-Salt",
|
||||
"Password salt - Tooltip": "Zufälliger Parameter, der für die Verschlüsselung von Passwörtern verwendet wird",
|
||||
"Password type": "Passworttyp",
|
||||
"Password type - Tooltip": "Speicherformat von Passwörtern in der Datenbank",
|
||||
"Payments": "Zahlungen",
|
||||
"Permissions": "Erlaubnisse",
|
||||
"Permissions": "Rechte",
|
||||
"Permissions - Tooltip": "Berechtigungen, die diesem Benutzer gehören",
|
||||
"Phone": "Telefon",
|
||||
"Phone - Tooltip": "Telefonnummer",
|
||||
"Preview": "Vorschau",
|
||||
"Preview - Tooltip": "Vorschau der konfigurierten Effekte",
|
||||
"Products": "Produkte",
|
||||
"Provider": "Anbieter",
|
||||
"Provider - Tooltip": "Zahlungsanbieter müssen konfiguriert werden, einschließlich PayPal, Alipay, WeChat Pay usw.",
|
||||
"Providers": "Anbieter",
|
||||
"Providers - Tooltip": "Anbieter müssen konfiguriert werden, einschließlich Anmeldung durch Drittanbieter, Objektspeicherung, Verifizierungscode usw.",
|
||||
"Provider": "Provider",
|
||||
"Provider - Tooltip": "Zahlungsprovider, die konfiguriert werden müssen, inkl. PayPal, Alipay, WeChat Pay usw.",
|
||||
"Providers": "Provider",
|
||||
"Providers - Tooltip": "Provider, die konfiguriert werden müssen, einschließlich Drittanbieter-Logins, Objektspeicherung, Verifizierungscode usw.",
|
||||
"Real name": "Echter Name",
|
||||
"Records": "Datensätze",
|
||||
"Request URI": "Anforderungs-URI",
|
||||
@@ -230,9 +239,9 @@
|
||||
"Roles": "Rollen",
|
||||
"Roles - Tooltip": "Rollen, denen der Benutzer angehört",
|
||||
"Save": "Speichern",
|
||||
"Save & Exit": "Speichern und Beenden",
|
||||
"Session ID": "Sitzungs-ID",
|
||||
"Sessions": "Sitzungen",
|
||||
"Save & Exit": "Speichern und verlassen",
|
||||
"Session ID": "Session-ID",
|
||||
"Sessions": "Sessions",
|
||||
"Signin URL": "Anmeldungs-URL",
|
||||
"Signin URL - Tooltip": "Benutzerdefinierte URL für die Anmeldeseite. Wenn sie nicht festgelegt ist, wird die standardmäßige Casdoor-Anmeldeseite verwendet. Wenn sie festgelegt ist, leiten die Anmeldelinks auf verschiedenen Casdoor-Seiten zu dieser URL um",
|
||||
"Signup URL": "Anmelde-URL",
|
||||
@@ -241,7 +250,7 @@
|
||||
"Signup application - Tooltip": "Durch welche Anwendung hat sich der Benutzer angemeldet, als er sich registriert hat?",
|
||||
"Sorry, the page you visited does not exist.": "Entschuldigung, die von Ihnen besuchte Seite existiert nicht.",
|
||||
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Entschuldigung, der von Ihnen besuchte Benutzer existiert nicht oder Sie sind nicht autorisiert, auf diesen Benutzer zuzugreifen.",
|
||||
"Sorry, you do not have permission to access this page or logged in status invalid.": "Es tut uns leid, aber Sie haben keine Berechtigung, auf diese Seite zuzugreifen, oder Ihr angemeldeter Status ist ungültig.",
|
||||
"Sorry, you do not have permission to access this page or logged in status invalid.": "Es tut uns leid, aber Sie haben keine Berechtigung, auf diese Seite zuzugreifen, oder Sie sind nicht angemeldet.",
|
||||
"State": "Bundesland / Staat",
|
||||
"State - Tooltip": "Bundesland",
|
||||
"Successfully added": "Erfolgreich hinzugefügt",
|
||||
@@ -249,7 +258,7 @@
|
||||
"Successfully saved": "Erfolgreich gespeichert",
|
||||
"Supported country codes": "Unterstützte Ländercodes",
|
||||
"Supported country codes - Tooltip": "Ländercodes, die von der Organisation unterstützt werden. Diese Codes können als Präfix ausgewählt werden, wenn SMS-Verifizierungscodes gesendet werden",
|
||||
"Sure to delete": "Sicher löschen (or Gesichertes Löschen)",
|
||||
"Sure to delete": "Sicher zu löschen",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Synchronisieren",
|
||||
"Syncers": "Syncers",
|
||||
@@ -286,7 +295,7 @@
|
||||
"Enable SSL - Tooltip": "Ob SSL aktiviert werden soll",
|
||||
"Group ID": "Gruppen-ID",
|
||||
"Last Sync": "Letzte Synchronisation",
|
||||
"Server": "Serverh)",
|
||||
"Server": "Server",
|
||||
"Server host": "Server Host",
|
||||
"Server host - Tooltip": "LDAP-Server-Adresse",
|
||||
"Server name": "Servername",
|
||||
@@ -298,7 +307,7 @@
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Automatische Anmeldung",
|
||||
"Continue with": "Mach weiter mit",
|
||||
"Continue with": "Weitermachen mit",
|
||||
"Email or phone": "E-Mail oder Telefon",
|
||||
"Forgot password?": "Passwort vergessen?",
|
||||
"Loading": "Laden",
|
||||
@@ -316,30 +325,30 @@
|
||||
"Signing in...": "Anmelden...",
|
||||
"Successfully logged in with WebAuthn credentials": "Erfolgreich mit WebAuthn-Anmeldeinformationen angemeldet",
|
||||
"The input is not valid Email or phone number!": "Die Eingabe ist keine gültige E-Mail-Adresse oder Telefonnummer!",
|
||||
"To access": "Zu Zugriff",
|
||||
"To access": "Zum Zugriff",
|
||||
"Verification code": "Verifizierungscode",
|
||||
"WebAuthn": "WebAuthn",
|
||||
"sign up now": "Melde dich jetzt an",
|
||||
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Bearbeiten Modell",
|
||||
"Edit Model": "Modell bearbeiten",
|
||||
"Model text": "Modelltext",
|
||||
"Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website",
|
||||
"New Model": "Neues Modell"
|
||||
},
|
||||
"organization": {
|
||||
"Account items": "Kontenpositionen",
|
||||
"Account items": "Konto Items",
|
||||
"Account items - Tooltip": "Elemente auf der persönlichen Einstellungsseite",
|
||||
"Edit Organization": "Organisation bearbeiten",
|
||||
"Follow global theme": "Folge dem globalen Thema",
|
||||
"Init score": "Initiale Punktzahl",
|
||||
"Follow global theme": "Folge dem globalen Theme",
|
||||
"Init score": "Initialer Score",
|
||||
"Init score - Tooltip": "Anfangspunkte, die Benutzern bei der Registrierung vergeben werden",
|
||||
"Is profile public": "Ist das Profil öffentlich?",
|
||||
"Is profile public - Tooltip": "Nach der Schließung können nur globale Administratoren oder Benutzer in der gleichen Organisation auf die Profilseite des Benutzers zugreifen",
|
||||
"Modify rule": "Regel ändern",
|
||||
"New Organization": "Neue Organisation",
|
||||
"Soft deletion": "Weiche Löschung",
|
||||
"Soft deletion": "Softe Löschung",
|
||||
"Soft deletion - Tooltip": "Wenn aktiviert, werden gelöschte Benutzer nicht vollständig aus der Datenbank entfernt. Stattdessen werden sie als gelöscht markiert",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Sammlung von Tags, die für Benutzer zur Auswahl zur Verfügung stehen",
|
||||
@@ -353,8 +362,8 @@
|
||||
"Currency": "Währung",
|
||||
"Currency - Tooltip": "Wie USD, CNY usw.",
|
||||
"Download Invoice": "Rechnung herunterladen",
|
||||
"Edit Payment": "Bearbeiten Sie die Zahlung",
|
||||
"Individual": "Individuum",
|
||||
"Edit Payment": "Zahlung bearbeiten",
|
||||
"Individual": "individuell",
|
||||
"Invoice URL": "Rechnungs-URL",
|
||||
"Invoice URL - Tooltip": "URL für den Download der Rechnung",
|
||||
"Invoice actions": "Rechnungsaktionen",
|
||||
@@ -382,16 +391,16 @@
|
||||
"Please carefully check your invoice information. Once the invoice is issued, it cannot be withdrawn or modified.": "Bitte prüfen Sie sorgfältig Ihre Rechnungsinformationen. Sobald die Rechnung ausgestellt wurde, kann sie nicht zurückgenommen oder geändert werden.",
|
||||
"Please click the below button to return to the original website": "Bitte klicken Sie auf den unten stehenden Button, um zur ursprünglichen Website zurückzukehren",
|
||||
"Please pay the order first!": "Bitte zahlen Sie zuerst die Bestellung!",
|
||||
"Processing...": "Verarbeitung...",
|
||||
"Processing...": "In Bearbeitung...",
|
||||
"Product": "Produkt",
|
||||
"Product - Tooltip": "Produktname",
|
||||
"Result": "Ergebnis",
|
||||
"Return to Website": "Zurück zur Website",
|
||||
"The payment has failed": "Die Zahlung ist fehlgeschlagen",
|
||||
"The payment is still under processing": "Die Zahlung wird immer noch bearbeitet",
|
||||
"Type - Tooltip": "Zahlungsmethode, die beim Kauf des Produkts verwendet wird",
|
||||
"Type - Tooltip": "Zahlungsmethode, die beim Kauf des Produkts verwendet wurde",
|
||||
"You have successfully completed the payment": "Sie haben die Zahlung erfolgreich abgeschlossen",
|
||||
"please wait for a few seconds...": "Bitte warten Sie einige Sekunden...",
|
||||
"please wait for a few seconds...": "Bitte warten Sie ein paar Sekunden...",
|
||||
"the current state is": "der aktuelle Zustand ist"
|
||||
},
|
||||
"permission": {
|
||||
@@ -399,17 +408,17 @@
|
||||
"Actions - Tooltip": "Erlaubte Aktionen",
|
||||
"Admin": "Admin",
|
||||
"Allow": "erlauben",
|
||||
"Approve time": "Zeit genehmigen",
|
||||
"Approve time": "Zeit der Genehmigung",
|
||||
"Approve time - Tooltip": "Die Genehmigungszeit für diese Erlaubnis",
|
||||
"Approved": "Genehmigt",
|
||||
"Approver": "Genehmiger",
|
||||
"Approver - Tooltip": "Die Person, die die Genehmigung genehmigt hat",
|
||||
"Deny": "Leugnen",
|
||||
"Edit Permission": "Bearbeitungsberechtigung",
|
||||
"Effect": "Wirkung",
|
||||
"Effect - Tooltip": "erlauben oder ablehnen",
|
||||
"Deny": "Ablehnen",
|
||||
"Edit Permission": "Recht bearbeiten",
|
||||
"Effect": "Effekt",
|
||||
"Effect - Tooltip": "Erlauben oder ablehnen",
|
||||
"New Permission": "Neue Genehmigung",
|
||||
"Pending": "In Erwartung",
|
||||
"Pending": "Ausstehend",
|
||||
"Read": "Lesen",
|
||||
"Resource type": "Ressourcentyp",
|
||||
"Resource type - Tooltip": "Art der Ressource",
|
||||
@@ -433,8 +442,8 @@
|
||||
"New Product": "Neues Produkt",
|
||||
"Pay": "Zahlen",
|
||||
"PayPal": "PayPal",
|
||||
"Payment providers": "Zahlungsanbieter",
|
||||
"Payment providers - Tooltip": "Anbieter von Zahlungsdiensten",
|
||||
"Payment providers": "Zahlungsprovider",
|
||||
"Payment providers - Tooltip": "Provider von Zahlungsdiensten",
|
||||
"Placing order...": "Bestellung aufgeben...",
|
||||
"Please provide your username in the remark": "Bitte geben Sie Ihren Benutzernamen in der Anmerkung an",
|
||||
"Please scan the QR code to pay": "Bitte scannen Sie den QR-Code, um zu bezahlen",
|
||||
@@ -442,7 +451,7 @@
|
||||
"Price - Tooltip": "Preis des Produkts",
|
||||
"Quantity": "Menge",
|
||||
"Quantity - Tooltip": "Menge des Produkts",
|
||||
"Return URL": "Rückgabe-URL",
|
||||
"Return URL": "Rückkeht-URL",
|
||||
"Return URL - Tooltip": "URL für die Rückkehr nach einem erfolgreichen Kauf",
|
||||
"SKU": "SKU",
|
||||
"Sold": "Verkauft",
|
||||
@@ -450,79 +459,82 @@
|
||||
"Tag - Tooltip": "Tag des Produkts",
|
||||
"Test buy page..": "Testkaufseite.",
|
||||
"There is no payment channel for this product.": "Es gibt keinen Zahlungskanal für dieses Produkt.",
|
||||
"This product is currently not in sale.": "Dieses Produkt ist derzeit nicht im Verkauf.",
|
||||
"This product is currently not in sale.": "Dieses Produkt steht derzeit nicht zum Verkauf.",
|
||||
"USD": "USD",
|
||||
"WeChat Pay": "WeChat Pay"
|
||||
},
|
||||
"provider": {
|
||||
"Access key": "Zugriffsschlüssel",
|
||||
"Access key": "Access-Key",
|
||||
"Access key - Tooltip": "Zugriffsschlüssel",
|
||||
"Agent ID": "Agenten-ID",
|
||||
"Agent ID - Tooltip": "Agenten-ID",
|
||||
"App ID": "App ID",
|
||||
"App ID - Tooltip": "App-ID",
|
||||
"App key": "App-Schlüssel",
|
||||
"App key": "App-Key",
|
||||
"App key - Tooltip": "App-Schlüssel",
|
||||
"App secret": "App-Geheimnis",
|
||||
"App secret": "App-Secret",
|
||||
"AppSecret - Tooltip": "App-Geheimnis",
|
||||
"Auth URL": "Auth-URL",
|
||||
"Auth URL - Tooltip": "Auth-URL",
|
||||
"Bucket": "Eimer",
|
||||
"Bucket - Tooltip": "Name des Eimers",
|
||||
"Bucket": "Bucket",
|
||||
"Bucket - Tooltip": "Name des Buckets",
|
||||
"Can not parse metadata": "Kann Metadaten nicht durchsuchen / auswerten",
|
||||
"Can signin": "Einloggen möglich",
|
||||
"Can signup": "Kann registrieren",
|
||||
"Can unlink": "Entkoppeln möglich",
|
||||
"Can signin": "Kann sich einloggen",
|
||||
"Can signup": "Kann sich registrieren",
|
||||
"Can unlink": "Entlinken möglich",
|
||||
"Category": "Kategorie",
|
||||
"Category - Tooltip": "Wählen Sie eine Kategorie aus",
|
||||
"Channel No.": "Kanal Nr.",
|
||||
"Channel No. - Tooltip": "Kanalnummer.",
|
||||
"Client ID": "Kunden-ID",
|
||||
"Client ID - Tooltip": "Kundennummer",
|
||||
"Client ID 2": "Kunden-ID 2",
|
||||
"Client ID 2 - Tooltip": "Die zweite Kundennnummer (ID)",
|
||||
"Client secret": "Kunden-Geheimnis",
|
||||
"Client secret - Tooltip": "Kunden-Geheimnis",
|
||||
"Client secret 2": "Kunden-Geheimnis 2",
|
||||
"Client secret 2 - Tooltip": "Der zweite Geheimcode für den Kunden",
|
||||
"Client ID": "Client-ID",
|
||||
"Client ID - Tooltip": "Client-ID",
|
||||
"Client ID 2": "Client-ID 2",
|
||||
"Client ID 2 - Tooltip": "Die zweite Client-ID",
|
||||
"Client secret": "Client-Secret",
|
||||
"Client secret - Tooltip": "Client-Geheimnis",
|
||||
"Client secret 2": "Client-Secret 2",
|
||||
"Client secret 2 - Tooltip": "Der zweite Client-Secret-Key",
|
||||
"Copy": "Kopieren",
|
||||
"Disable SSL": "SSL deaktivieren",
|
||||
"Disable SSL - Tooltip": "Ob die Deaktivierung des SSL-Protokolls bei der Kommunikation mit dem STMP-Server erfolgen soll",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Benutzerdefinierte Domäne für Objektspeicher",
|
||||
"Edit Provider": "Anbieter bearbeiten",
|
||||
"Domain - Tooltip": "Benutzerdefinierte Domain für Objektspeicher",
|
||||
"Edit Provider": "Provider bearbeiten",
|
||||
"Email content": "Email-Inhalt",
|
||||
"Email content - Tooltip": "Inhalt der E-Mail",
|
||||
"Email sent successfully": "E-Mail erfolgreich gesendet",
|
||||
"Email title": "Email-Titel",
|
||||
"Email title - Tooltip": "Betreff der E-Mail",
|
||||
"Enable QR code": "QR-Code aktivieren",
|
||||
"Enable QR code - Tooltip": "Ob Scannen von QR-Code zum Einloggen erlaubt werden soll",
|
||||
"Endpoint": "Endpunkt",
|
||||
"Endpoint (Intranet)": "Endpunkt (Intranet)",
|
||||
"Enable QR code - Tooltip": "Ob das Scannen von QR-Codes zum Einloggen aktiviert werden soll",
|
||||
"Endpoint": "Endpoint",
|
||||
"Endpoint (Intranet)": "Endpoint (Intranet)",
|
||||
"Host": "Host",
|
||||
"Host - Tooltip": "Name des Gastgebers",
|
||||
"Host - Tooltip": "Name des Hosts",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "IdP-Zertifikat",
|
||||
"Issuer URL": "Emittenten-URL",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "Issuer-URL",
|
||||
"Issuer URL - Tooltip": "Emittenten-URL",
|
||||
"Link copied to clipboard successfully": "Link wurde erfolgreich in die Zwischenablage kopiert",
|
||||
"Metadata": "Metadaten",
|
||||
"Metadata - Tooltip": "SAML-Metadaten",
|
||||
"Method - Tooltip": "Anmeldeverfahren, QR-Code oder geräuschloser Login",
|
||||
"New Provider": "Neuer Anbieter",
|
||||
"Method - Tooltip": "Anmeldeverfahren, QR-Code oder Silent-Login",
|
||||
"New Provider": "Neuer Provider",
|
||||
"Normal": "Normal",
|
||||
"Parse": "parsen",
|
||||
"Parse metadata successfully": "Metadaten erfolgreich analysiert",
|
||||
"Path prefix": "Pfadpräfix",
|
||||
"Path prefix - Tooltip": "Bucket-Pfad-Präfix für Objektspeicher",
|
||||
"Please use WeChat and scan the QR code to sign in": "Bitte verwende WeChat und scanne den QR-Code ein, um dich anzumelden",
|
||||
"Port": "Hafen",
|
||||
"Please use WeChat and scan the QR code to sign in": "Bitte verwenden Sie WeChat und scanne den QR-Code ein, um dich anzumelden",
|
||||
"Port": "Port",
|
||||
"Port - Tooltip": "Stellen Sie sicher, dass der Port offen ist",
|
||||
"Prompted": "ausgelöst",
|
||||
"Provider URL": "Anbieter-URL",
|
||||
"Provider URL": "Provider-URL",
|
||||
"Provider URL - Tooltip": "URL zur Konfiguration des Dienstanbieters, dieses Feld dient nur als Referenz und wird in Casdoor nicht verwendet",
|
||||
"Region ID": "Regions-ID",
|
||||
"Region ID - Tooltip": "Regionale ID für den Dienstleister",
|
||||
"Region ID - Tooltip": "Regions-ID für den Dienstleister",
|
||||
"Region endpoint for Internet": "Regionsendpunkt für das Internet",
|
||||
"Region endpoint for Intranet": "Regionales Endpunkt für Intranet",
|
||||
"Required": "Benötigt",
|
||||
@@ -535,58 +547,61 @@
|
||||
"SP ACS URL": "SP ACS URL",
|
||||
"SP ACS URL - Tooltip": "SP ACS URL",
|
||||
"SP Entity ID": "SP-Entitäts-ID",
|
||||
"Scene": "Szene",
|
||||
"Scene": "Scene",
|
||||
"Scene - Tooltip": "Szene",
|
||||
"Scope": "Umfang",
|
||||
"Scope - Tooltip": "Umfang",
|
||||
"Secret access key": "Geheimer Zugriffsschlüssel",
|
||||
"Scope": "Scope",
|
||||
"Scope - Tooltip": "Scope",
|
||||
"Secret access key": "Secret-Access-Key",
|
||||
"Secret access key - Tooltip": "Geheimer Zugriffsschlüssel",
|
||||
"Secret key": "Geheimschlüssel",
|
||||
"Secret key - Tooltip": "Vom Server verwendet, um die API des Verifizierungscodes-Anbieters für die Verifizierung aufzurufen",
|
||||
"Secret key": "Secret-Key",
|
||||
"Secret key - Tooltip": "Vom Server verwendet, um die API des Verifizierungscodes-Providers für die Verifizierung aufzurufen",
|
||||
"Send Testing Email": "Senden Sie eine Test-E-Mail",
|
||||
"Send Testing SMS": "Sende Test-SMS",
|
||||
"Sign Name": "Unterschreiben Sie den Namen",
|
||||
"Sign Name": "Signatur Namen",
|
||||
"Sign Name - Tooltip": "Name der Signatur, die verwendet werden soll",
|
||||
"Sign request": "Unterschriftsanforderung",
|
||||
"Sign request - Tooltip": "Ob die Anfrage eine Unterschrift erfordert",
|
||||
"Signin HTML": "Anmeldung HTML",
|
||||
"Signin HTML - Edit": "Anmeldung HTML - Bearbeiten",
|
||||
"Sign request": "Signaturanfrage",
|
||||
"Sign request - Tooltip": "Ob die Anfrage eine Signatur erfordert",
|
||||
"Signin HTML": "Anmeldungs-HTML",
|
||||
"Signin HTML - Edit": "Anmeldungs-HTML - Bearbeiten",
|
||||
"Signin HTML - Tooltip": "Benutzerdefiniertes HTML zur Ersetzung des Standard-Anmelde-Seitenstils",
|
||||
"Signup HTML": "Anmeldung HTML",
|
||||
"Signup HTML": "Registrierungs-HTML",
|
||||
"Signup HTML - Edit": "Registrierung HTML - Bearbeiten",
|
||||
"Signup HTML - Tooltip": "Benutzerdefiniertes HTML zur Ersetzung des Standard-Anmelde-Seitenstils",
|
||||
"Site key": "Seitenschlüssel",
|
||||
"Site key - Tooltip": "Site-Schlüssel",
|
||||
"Signup HTML - Tooltip": "Benutzerdefiniertes HTML zur Ersetzung des Standard-Registrierungs-Seitenstils",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Site-Key",
|
||||
"Site key - Tooltip": "Seitenschlüssel",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Untertyp",
|
||||
"Sub type - Tooltip": "Unterart",
|
||||
"Template code": "Vorlagen-Code",
|
||||
"Template code - Tooltip": "Vorlagen-Code",
|
||||
"Template code": "Template-Code",
|
||||
"Template code - Tooltip": "Template-Code",
|
||||
"Test Email": "Test E-Mail",
|
||||
"Test Email - Tooltip": "E-Mail-Adresse zum Empfangen von Test-E-Mails",
|
||||
"Test SMTP Connection": "Testen Sie die SMTP-Verbindung",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Token-URL",
|
||||
"Token URL - Tooltip": "Token-URL",
|
||||
"Type": "Typ",
|
||||
"Type - Tooltip": "Wählen Sie einen Typ aus",
|
||||
"UserInfo URL": "UserInfo-URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo-URL",
|
||||
"admin (Shared)": "admin (Gemeinsam)"
|
||||
"admin (Shared)": "admin (Shared)"
|
||||
},
|
||||
"record": {
|
||||
"Is triggered": "Wird ausgelöst"
|
||||
"Is triggered": "Ist ausgelöst"
|
||||
},
|
||||
"resource": {
|
||||
"Copy Link": "Kopiere den Link",
|
||||
"Copy Link": "Link kopieren",
|
||||
"File name": "Dateiname",
|
||||
"File size": "Dateigröße",
|
||||
"Format": "Format",
|
||||
"Parent": "Elternteil",
|
||||
"Parent": "Parent",
|
||||
"Upload a file...": "Hochladen einer Datei..."
|
||||
},
|
||||
"role": {
|
||||
"Edit Role": "Rolle bearbeiten",
|
||||
"New Role": "Neue Rolle",
|
||||
"Sub domains": "Unterdomänen",
|
||||
"Sub domains": "Subdomains",
|
||||
"Sub domains - Tooltip": "In der aktuellen Rolle enthaltene Domains",
|
||||
"Sub roles": "Unterrollen",
|
||||
"Sub roles - Tooltip": "Rollen, die in der aktuellen Rolle enthalten sind",
|
||||
@@ -597,11 +612,11 @@
|
||||
"Accept": "Akzeptieren",
|
||||
"Agreement": "Vereinbarung",
|
||||
"Confirm": "Bestätigen",
|
||||
"Decline": "Abnahme",
|
||||
"Decline": "Ablehnen",
|
||||
"Have account?": "Haben Sie ein Konto?",
|
||||
"Please accept the agreement!": "Bitte akzeptieren Sie die Vereinbarung!",
|
||||
"Please click the below button to sign in": "Bitte klicken Sie auf die untenstehende Schaltfläche, um sich anzumelden",
|
||||
"Please confirm your password!": "Bitte bestätige dein Passwort!",
|
||||
"Please click the below button to sign in": "Bitte klicken Sie auf den untenstehenden Button, um sich anzumelden",
|
||||
"Please confirm your password!": "Bitte bestätigen Sie Ihr Passwort!",
|
||||
"Please input the correct ID card number!": "Bitte geben Sie die korrekte Ausweisnummer ein!",
|
||||
"Please input your Email!": "Bitte geben Sie Ihre E-Mail-Adresse ein!",
|
||||
"Please input your ID card number!": "Bitte geben Sie Ihre Personalausweisnummer ein!",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "Die Eingabe ist keine gültige E-Mail-Adresse!",
|
||||
"The input is not valid Phone!": "Die Eingabe ist kein gültiges Telefon!",
|
||||
"Username": "Benutzername",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Ihr Konto wurde erstellt!",
|
||||
"Your confirmed password is inconsistent with the password!": "Dein bestätigtes Passwort stimmt nicht mit dem Passwort überein!",
|
||||
"sign in now": "Jetzt anmelden"
|
||||
@@ -637,17 +653,17 @@
|
||||
"Database - Tooltip": "Der ursprüngliche Datenbankname",
|
||||
"Database type": "Datenbanktyp",
|
||||
"Database type - Tooltip": "Datenbanktyp, der alle Datenbanken unterstützt, die von XORM unterstützt werden, wie MySQL, PostgreSQL, SQL Server, Oracle, SQLite, usw.",
|
||||
"Edit Syncer": "Edit Syncer",
|
||||
"Edit Syncer": "Syncer bearbeiten",
|
||||
"Error text": "Fehlermeldung",
|
||||
"Error text - Tooltip": "Fehler Text",
|
||||
"Is hashed": "wurde gehasht",
|
||||
"Is hashed": "ist gehasht",
|
||||
"New Syncer": "Neuer Syncer",
|
||||
"Sync interval": "Synchronisierungsintervall",
|
||||
"Sync interval - Tooltip": "Einheit in Sekunden",
|
||||
"Table": "Tabelle",
|
||||
"Table - Tooltip": "Name der Datenbanktabelle",
|
||||
"Table columns": "Tabellenspalten",
|
||||
"Table columns - Tooltip": "zu Deutsch: Spalten in der Tabelle, die an der Datensynchronisierung beteiligt sind. Spalten, die nicht an der Synchronisierung beteiligt sind, müssen nicht hinzugefügt werden",
|
||||
"Table columns - Tooltip": "Spalten in der Tabelle, die an der Datensynchronisierung beteiligt sind. Spalten, die nicht an der Synchronisierung beteiligt sind, müssen nicht hinzugefügt werden",
|
||||
"Table primary key": "Primärschlüssel der Tabelle",
|
||||
"Table primary key - Tooltip": "Primärschlüssel in einer Tabelle, wie beispielsweise eine ID"
|
||||
},
|
||||
@@ -655,7 +671,7 @@
|
||||
"About Casdoor": "Über Casdoor",
|
||||
"An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS": "Eine Identitäts- und Zugriffsverwaltung (IAM) / Single-Sign-On (SSO) Plattform mit Web-UI, die OAuth 2.0, OIDC, SAML und CAS unterstützt",
|
||||
"CPU Usage": "CPU-Auslastung",
|
||||
"Community": "Gemeinschaft",
|
||||
"Community": "Community",
|
||||
"Failed to get CPU usage": "Konnte CPU-Auslastung nicht abrufen",
|
||||
"Failed to get memory usage": "Fehler beim Abrufen der Speichernutzung",
|
||||
"Memory Usage": "Speichernutzung",
|
||||
@@ -664,29 +680,29 @@
|
||||
"Version": "Version"
|
||||
},
|
||||
"theme": {
|
||||
"Blossom": "Blüte",
|
||||
"Border radius": "Eckradius",
|
||||
"Compact": "Kompakt",
|
||||
"Customize theme": "Anpassen des Themas",
|
||||
"Dark": "Dunkelheit",
|
||||
"Default": "Voreinstellung",
|
||||
"Blossom": "Blossom",
|
||||
"Border radius": "Border Radius",
|
||||
"Compact": "Compact",
|
||||
"Customize theme": "Anpassen des Themes",
|
||||
"Dark": "Dunkel",
|
||||
"Default": "Standardeinstellungen",
|
||||
"Document": "Dokument",
|
||||
"Is compact": "Ist kompakt",
|
||||
"Primary color": "Primärfarbe",
|
||||
"Theme": "Thema",
|
||||
"Theme - Tooltip": "Stilthema der Anwendung"
|
||||
"Theme": "Theme",
|
||||
"Theme - Tooltip": "Stiltheme der Anwendung"
|
||||
},
|
||||
"token": {
|
||||
"Access token": "Zugriffstoken",
|
||||
"Access token": "Access-Token",
|
||||
"Authorization code": "Authorisierungscode",
|
||||
"Edit Token": "Edit-Token bearbeiten",
|
||||
"Edit Token": "Token bearbeiten",
|
||||
"Expires in": "läuft ab in",
|
||||
"New Token": "Neues Token",
|
||||
"New Token": "Neuer Token",
|
||||
"Token type": "Token-Typ"
|
||||
},
|
||||
"user": {
|
||||
"3rd-party logins": "3rd-Party-Logins",
|
||||
"3rd-party logins - Tooltip": "Soziale Anmeldungen, die vom Benutzer verknüpft werden",
|
||||
"3rd-party logins": "Drittanbieter-Logins",
|
||||
"3rd-party logins - Tooltip": "Drittanbieter-Anmeldungen, die mit dem Benutzer verknüpft sind",
|
||||
"Address": "Adresse",
|
||||
"Address - Tooltip": "Wohnadresse",
|
||||
"Affiliation": "Zugehörigkeit",
|
||||
@@ -700,22 +716,22 @@
|
||||
"Country/Region - Tooltip": "Land oder Region",
|
||||
"Edit User": "Benutzer bearbeiten",
|
||||
"Email cannot be empty": "E-Mail darf nicht leer sein",
|
||||
"Email/phone reset successfully": "E-Mail-/Telefonrücksetzung erfolgreich durchgeführt",
|
||||
"Empty input!": "Leereingabe!",
|
||||
"Email/phone reset successfully": "E-Mail-/Telefon-Zurücksetzung erfolgreich durchgeführt",
|
||||
"Empty input!": "Leere Eingabe!",
|
||||
"Homepage": "Startseite des Benutzers",
|
||||
"Homepage - Tooltip": "Homepage-URL des Benutzers",
|
||||
"ID card": "Ausweis",
|
||||
"Input your email": "Gib deine E-Mail-Adresse ein",
|
||||
"Input your email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"Input your phone number": "Geben Sie Ihre Telefonnummer ein",
|
||||
"Is admin": "Ist Admin",
|
||||
"Is admin - Tooltip": "Ist ein Administrator der Organisation, zu der der Benutzer gehört",
|
||||
"Is deleted": "wurde gelöscht",
|
||||
"Is deleted": "ist gelöscht",
|
||||
"Is deleted - Tooltip": "\"Gelöschte Benutzer behalten nur Datenbankdatensätze und können keine Operationen ausführen.\"",
|
||||
"Is forbidden": "Ist verboten",
|
||||
"Is forbidden - Tooltip": "Verbotene Benutzer können sich nicht mehr einloggen",
|
||||
"Is global admin": "Ist weltweiter Administrator",
|
||||
"Is global admin": "Ist globaler Administrator",
|
||||
"Is global admin - Tooltip": "Ist ein Administrator von Casdoor",
|
||||
"Keys": "Schlüssel",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Ort",
|
||||
"Location - Tooltip": "Stadt des Wohnsitzes",
|
||||
@@ -724,22 +740,22 @@
|
||||
"New Email": "Neue E-Mail",
|
||||
"New Password": "Neues Passwort",
|
||||
"New User": "Neuer Benutzer",
|
||||
"New phone": "Neues Telefon",
|
||||
"New phone": "Neue Telefonnummer",
|
||||
"Old Password": "Altes Passwort",
|
||||
"Password set successfully": "Passwort erfolgreich festgelegt",
|
||||
"Phone cannot be empty": "Telefon kann nicht leer sein",
|
||||
"Phone cannot be empty": "Telefonnummer kann nicht leer sein",
|
||||
"Please select avatar from resources": "Bitte wählen Sie einen Avatar aus den Ressourcen aus",
|
||||
"Properties": "Eigenschaften",
|
||||
"Properties - Tooltip": "Eigenschaften des Benutzers",
|
||||
"Re-enter New": "Neueingabe betreten",
|
||||
"Reset Email...": "Reset E-Mail...",
|
||||
"Re-enter New": "Neueingabe wiederholen",
|
||||
"Reset Email...": "E-Mail zurücksetzen...",
|
||||
"Reset Phone...": "Telefon zurücksetzen...",
|
||||
"Select a photo...": "Wählen Sie ein Foto aus...",
|
||||
"Set Password": "Passwort festlegen",
|
||||
"Set new profile picture": "Neues Profilbild festlegen",
|
||||
"Set password...": "Passwort festlegen...",
|
||||
"Tag": "Tag",
|
||||
"Tag - Tooltip": "Tag des Benutzers",
|
||||
"Tag - Tooltip": "Tags des Benutzers",
|
||||
"Title": "Titel",
|
||||
"Title - Tooltip": "Position in der Zugehörigkeit",
|
||||
"Two passwords you typed do not match.": "Zwei von Ihnen eingegebene Passwörter stimmen nicht überein.",
|
||||
@@ -749,10 +765,10 @@
|
||||
"Values": "Werte",
|
||||
"Verification code sent": "Bestätigungscode gesendet",
|
||||
"WebAuthn credentials": "WebAuthn-Anmeldeinformationen",
|
||||
"input password": "Eingabe des Passworts"
|
||||
"input password": "Passwort eingeben"
|
||||
},
|
||||
"webhook": {
|
||||
"Content type": "Inhaltstyp",
|
||||
"Content type": "Content-Type",
|
||||
"Content type - Tooltip": "Inhaltstyp",
|
||||
"Edit Webhook": "Webhook bearbeiten",
|
||||
"Events": "Ereignisse",
|
||||
@@ -760,9 +776,9 @@
|
||||
"Headers": "Überschriften",
|
||||
"Headers - Tooltip": "HTTP-Header (Schlüssel-Wert-Paare)",
|
||||
"Is user extended": "Wurde der Benutzer erweitert?",
|
||||
"Is user extended - Tooltip": "Sollten die erweiterten Felder des Benutzers in das JSON eingeschlossen werden?",
|
||||
"Is user extended - Tooltip": "Sollten die erweiterten Felder des Benutzers in das JSON inkludiert werden?",
|
||||
"Method - Tooltip": "HTTP Methode",
|
||||
"New Webhook": "Neuer Webhook",
|
||||
"New Webhook": "Neue Webhook",
|
||||
"Value": "Wert"
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Whether to allow users to register a new account",
|
||||
"Failed to sign in": "Failed to sign in",
|
||||
"File uploaded successfully": "File uploaded successfully",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Follow organization theme",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Edit",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Left",
|
||||
"Logged in successfully": "Logged in successfully",
|
||||
"Logged out successfully": "Logged out successfully",
|
||||
"New Application": "New Application",
|
||||
"No verification": "No verification",
|
||||
"None": "None",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"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",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Redirect URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Redirect URL (Assertion Consumer Service POST Binding URL)",
|
||||
"Redirect URLs": "Redirect URLs",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Side panel HTML - Edit",
|
||||
"Side panel HTML - Tooltip": "Customize the HTML code for the side panel of the login page",
|
||||
"Sign Up Error": "Sign Up Error",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"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",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Name of host",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "IdP certificate",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "Issuer URL",
|
||||
"Issuer URL - Tooltip": "Issuer URL",
|
||||
"Link copied to clipboard successfully": "Link copied to clipboard successfully",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "SAML metadata",
|
||||
"Method - Tooltip": "Login method, QR code or silent login",
|
||||
"New Provider": "New Provider",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Parse",
|
||||
"Parse metadata successfully": "Parse metadata successfully",
|
||||
"Path prefix": "Path prefix",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Signup HTML",
|
||||
"Signup HTML - Edit": "Signup HTML - Edit",
|
||||
"Signup HTML - Tooltip": "Custom HTML for replacing the default signup page style",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Site key",
|
||||
"Site key - Tooltip": "Site key",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Sub type",
|
||||
"Sub type - Tooltip": "Sub type",
|
||||
"Template code": "Template code",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Test Email",
|
||||
"Test Email - Tooltip": "Email address to receive test mails",
|
||||
"Test SMTP Connection": "Test SMTP Connection",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL",
|
||||
"Type": "Type",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "The input is not valid Email!",
|
||||
"The input is not valid Phone!": "The input is not valid Phone!",
|
||||
"Username": "Username",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Your account has been created!",
|
||||
"Your confirmed password is inconsistent with the password!": "Your confirmed password is inconsistent with the password!",
|
||||
"sign in now": "sign in now"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Ya sea permitir que los usuarios registren una nueva cuenta",
|
||||
"Failed to sign in": "Error al iniciar sesión",
|
||||
"File uploaded successfully": "Archivo subido exitosamente",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Seguir el tema de la organización",
|
||||
"Form CSS": "Formulario CSS",
|
||||
"Form CSS - Edit": "Formulario CSS - Editar",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Ubicación de los formularios de registro, inicio de sesión y olvido de contraseña",
|
||||
"Grant types": "Tipos de subvenciones",
|
||||
"Grant types - Tooltip": "Selecciona cuáles tipos de subvenciones están permitidas en el protocolo OAuth",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Izquierda",
|
||||
"Logged in successfully": "Acceso satisfactorio",
|
||||
"Logged out successfully": "Cerró sesión exitosamente",
|
||||
"New Application": "Nueva aplicación",
|
||||
"No verification": "No verification",
|
||||
"None": "Ninguno",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "¡Por favor, ingrese su solicitud!",
|
||||
"Please input your organization!": "¡Por favor, ingrese su organización!",
|
||||
"Please select a HTML file": "Por favor, seleccione un archivo HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL de la página de acceso exitosamente copiada al portapapeles, por favor péguela en la ventana de incógnito o en otro navegador",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Redireccionar URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "URL de redireccionamiento (URL de enlace de publicación del servicio consumidor de afirmaciones)",
|
||||
"Redirect URLs": "Redireccionar URLs",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Panel lateral HTML - Editar",
|
||||
"Side panel HTML - Tooltip": "Personaliza el código HTML del panel lateral de la página de inicio de sesión",
|
||||
"Sign Up Error": "Error de registro",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "La URL de la página de inicio de sesión fue copiada al portapapeles exitosamente, por favor péguela en una ventana de incógnito o en otro navegador",
|
||||
"Signin session": "Sesión de inicio de sesión",
|
||||
"Signup items": "Artículos de registro",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Nombre del anfitrión",
|
||||
"IdP": "IdP = Proveedor de Identidad",
|
||||
"IdP certificate": "Certificado de proveedor de identidad (IdP)",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "URL del emisor",
|
||||
"Issuer URL - Tooltip": "URL del emisor",
|
||||
"Link copied to clipboard successfully": "Enlace copiado al portapapeles satisfactoriamente",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "Metadatos SAML",
|
||||
"Method - Tooltip": "Método de inicio de sesión, código QR o inicio de sesión silencioso",
|
||||
"New Provider": "Nuevo proveedor",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Analizar",
|
||||
"Parse metadata successfully": "Analizar los metadatos con éxito",
|
||||
"Path prefix": "Prefijo de ruta",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Registro HTML",
|
||||
"Signup HTML - Edit": "Registro HTML - Editar",
|
||||
"Signup HTML - Tooltip": "HTML personalizado para reemplazar el estilo predeterminado de la página de registro",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Clave del sitio",
|
||||
"Site key - Tooltip": "Clave del sitio",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Subtipo",
|
||||
"Sub type - Tooltip": "Subtipo",
|
||||
"Template code": "Código de plantilla",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Correo de prueba",
|
||||
"Test Email - Tooltip": "Dirección de correo electrónico para recibir mensajes de prueba",
|
||||
"Test SMTP Connection": "Prueba de conexión SMTP",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "URL de token",
|
||||
"Type": "Tipo",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "¡La entrada no es un correo electrónico válido!",
|
||||
"The input is not valid Phone!": "¡La entrada no es un número de teléfono válido!",
|
||||
"Username": "Nombre de usuario",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "¡Su cuenta ha sido creada!",
|
||||
"Your confirmed password is inconsistent with the password!": "¡Su contraseña confirmada no es coherente con la contraseña!",
|
||||
"sign in now": "Inicie sesión ahora"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Doit-on autoriser les utilisateurs à créer un nouveau compte ?",
|
||||
"Failed to sign in": "Échec de la connexion",
|
||||
"File uploaded successfully": "Fichier téléchargé avec succès",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Suivre le thème de l'organisation",
|
||||
"Form CSS": "Formulaire CSS",
|
||||
"Form CSS - Edit": "Form CSS - Modifier",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Emplacement des formulaires d'inscription, de connexion et de récupération de mot de passe",
|
||||
"Grant types": "Types de subventions",
|
||||
"Grant types - Tooltip": "Sélectionnez les types d'autorisations autorisés dans le protocole OAuth",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "gauche",
|
||||
"Logged in successfully": "Connecté avec succès",
|
||||
"Logged out successfully": "Déconnecté avec succès",
|
||||
"New Application": "Nouvelle application",
|
||||
"No verification": "No verification",
|
||||
"None": "Aucun",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "Veuillez saisir votre demande d'application !",
|
||||
"Please input your organization!": "S'il vous plaît saisir votre organisation !",
|
||||
"Please select a HTML file": "S'il vous plaît sélectionnez un fichier HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL de la page rapide copiée avec succès dans le presse-papiers, veuillez la coller dans la fenêtre de navigation privée ou dans un autre navigateur",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Rediriger l'URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "URL de redirection (URL de liaison POST du service consommateur d'assertions)",
|
||||
"Redirect URLs": "Rediriger les URL",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Panneau latéral HTML - Modifier",
|
||||
"Side panel HTML - Tooltip": "Personnalisez le code HTML du panneau latéral de la page de connexion",
|
||||
"Sign Up Error": "Erreur d'inscription",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "L'URL de la page de connexion a été copiée avec succès dans le presse-papiers, veuillez la coller dans la fenêtre de navigation privée ou dans un autre navigateur",
|
||||
"Signin session": "Session de connexion",
|
||||
"Signup items": "Les éléments d'inscription",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Nom d'hôte",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "Certificat IdP",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "URL de l'émetteur",
|
||||
"Issuer URL - Tooltip": "URL de l'émetteur",
|
||||
"Link copied to clipboard successfully": "Lien copié avec succès dans le presse-papiers",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "Métadonnées SAML",
|
||||
"Method - Tooltip": "Méthode de connexion, code QR ou connexion silencieuse",
|
||||
"New Provider": "Nouveau fournisseur",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Parser",
|
||||
"Parse metadata successfully": "Parcourir les métadonnées avec succès",
|
||||
"Path prefix": "Préfixe de chemin",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Inscription HTML",
|
||||
"Signup HTML - Edit": "Inscription HTML - éditer",
|
||||
"Signup HTML - Tooltip": "HTML personnalisé pour remplacer le style par défaut de la page d'inscription",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Clé de site",
|
||||
"Site key - Tooltip": "Clé de site",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Sous-type",
|
||||
"Sub type - Tooltip": "Sous-type",
|
||||
"Template code": "Code modèle",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Courriel de test",
|
||||
"Test Email - Tooltip": "Adresse e-mail pour recevoir des courriels de test",
|
||||
"Test SMTP Connection": "Test de connexion SMTP",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "URL de jeton",
|
||||
"Token URL - Tooltip": "URL de jeton",
|
||||
"Type": "Type",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "L'entrée n'est pas un e-mail valide !",
|
||||
"The input is not valid Phone!": "L'entrée n'est pas un numéro de téléphone valide !",
|
||||
"Username": "Nom d'utilisateur",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Votre compte a été créé !",
|
||||
"Your confirmed password is inconsistent with the password!": "Votre mot de passe confirmé est incompatible avec le mot de passe !",
|
||||
"sign in now": "Connectez-vous maintenant"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Apakah akan mengizinkan pengguna untuk mendaftar akun baru",
|
||||
"Failed to sign in": "Gagal masuk",
|
||||
"File uploaded successfully": "Berkas telah diunggah dengan sukses",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Ikuti tema organisasi",
|
||||
"Form CSS": "Formulir CSS",
|
||||
"Form CSS - Edit": "Formulir CSS - Edit",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Tempat pendaftaran, masuk, dan lupa kata sandi",
|
||||
"Grant types": "Jenis-jenis hibah",
|
||||
"Grant types - Tooltip": "Pilih jenis hibah apa yang diperbolehkan dalam protokol OAuth",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Kiri",
|
||||
"Logged in successfully": "Berhasil masuk",
|
||||
"Logged out successfully": "Berhasil keluar dari sistem",
|
||||
"New Application": "Aplikasi Baru",
|
||||
"No verification": "No verification",
|
||||
"None": "Tidak ada",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "Silakan masukkan aplikasi Anda!",
|
||||
"Please input your organization!": "Silakan masukkan organisasi Anda!",
|
||||
"Please select a HTML file": "Silahkan pilih file HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Tautan halaman Prompt berhasil disalin ke papan klip, silakan tempelkan ke jendela penyamaran atau browser lainnya",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Mengalihkan URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "URL pengalihan (Penyanggah Konsumen Layanan Ikatan POST URL)",
|
||||
"Redirect URLs": "Mengarahkan URL",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Panel sisi HTML - Sunting",
|
||||
"Side panel HTML - Tooltip": "Menyesuaikan kode HTML untuk panel samping halaman login",
|
||||
"Sign Up Error": "Kesalahan Pendaftaran",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL halaman masuk berhasil disalin ke clipboard, silakan tempelkan di jendela penyamaran atau browser lainnya",
|
||||
"Signin session": "Sesi masuk",
|
||||
"Signup items": "Item pendaftaran",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Nama tuan rumah",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "Sertifikat IdP",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "URL penerbit",
|
||||
"Issuer URL - Tooltip": "URL Penerbit",
|
||||
"Link copied to clipboard successfully": "Tautan berhasil disalin ke papan klip",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "Metadata SAML",
|
||||
"Method - Tooltip": "Metode login, kode QR atau login tanpa suara",
|
||||
"New Provider": "Penyedia Baru",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Parse: Memecah atau mengurai data atau teks menjadi bagian-bagian yang lebih kecil dan lebih mudah dipahami atau dimanipulasi",
|
||||
"Parse metadata successfully": "Berhasil mem-parse metadata",
|
||||
"Path prefix": "Awalan jalur",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Pendaftaran HTML",
|
||||
"Signup HTML - Edit": "Pendaftaran HTML - Sunting",
|
||||
"Signup HTML - Tooltip": "HTML khusus untuk mengganti gaya halaman pendaftaran bawaan",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Kunci situs",
|
||||
"Site key - Tooltip": "Kunci situs atau kunci halaman web",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Sub jenis",
|
||||
"Sub type - Tooltip": "Sub jenis",
|
||||
"Template code": "Kode template",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Email Uji Coba",
|
||||
"Test Email - Tooltip": "Alamat email untuk menerima email percobaan",
|
||||
"Test SMTP Connection": "Tes Koneksi SMTP",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Token URL: Tautan Token",
|
||||
"Token URL - Tooltip": "Token URL: URL Token",
|
||||
"Type": "Jenis",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "Input yang dimasukkan bukan sesuai dengan format Email yang valid!",
|
||||
"The input is not valid Phone!": "Masukan ponsel tidak valid!",
|
||||
"Username": "Nama pengguna",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Akun Anda telah dibuat!",
|
||||
"Your confirmed password is inconsistent with the password!": "Kata sandi yang dikonfirmasi tidak konsisten dengan kata sandi!",
|
||||
"sign in now": "Masuk sekarang"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "新しいアカウントの登録をユーザーに許可するかどうか",
|
||||
"Failed to sign in": "ログインに失敗しました",
|
||||
"File uploaded successfully": "ファイルが正常にアップロードされました",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "組織のテーマに従ってください",
|
||||
"Form CSS": "フォームCSS",
|
||||
"Form CSS - Edit": "フォームのCSS - 編集",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "登録、ログイン、パスワード忘れフォームの位置",
|
||||
"Grant types": "グラント種類",
|
||||
"Grant types - Tooltip": "OAuthプロトコルで許可されているグラントタイプを選択してください",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "左",
|
||||
"Logged in successfully": "正常にログインしました",
|
||||
"Logged out successfully": "正常にログアウトしました",
|
||||
"New Application": "新しいアプリケーション",
|
||||
"No verification": "No verification",
|
||||
"None": "なし",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "あなたの申請を入力してください!",
|
||||
"Please input your organization!": "あなたの組織を入力してください!",
|
||||
"Please select a HTML file": "HTMLファイルを選択してください",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "プロンプトページのURLが正常にクリップボードにコピーされました。インコグニートウィンドウまたは別のブラウザに貼り付けてください",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "リダイレクトURL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "リダイレクトURL(アサーションコンシューマサービスPOSTバインディングURL)",
|
||||
"Redirect URLs": "リダイレクトURL",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "サイドパネルのHTML - 編集",
|
||||
"Side panel HTML - Tooltip": "ログインページのサイドパネルに対するHTMLコードをカスタマイズしてください",
|
||||
"Sign Up Error": "サインアップエラー",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "サインインページのURLがクリップボードに正常にコピーされました。インコグニートウィンドウまたは別のブラウザに貼り付けてください",
|
||||
"Signin session": "サインインセッション",
|
||||
"Signup items": "サインアップアイテム",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "ホストの名前",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "IdP証明書",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "発行者のURL",
|
||||
"Issuer URL - Tooltip": "発行者URL",
|
||||
"Link copied to clipboard successfully": "リンクがクリップボードに正常にコピーされました",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "SAMLのメタデータ",
|
||||
"Method - Tooltip": "ログイン方法、QRコードまたはサイレントログイン",
|
||||
"New Provider": "新しい提供者",
|
||||
"Normal": "Normal",
|
||||
"Parse": "パースする",
|
||||
"Parse metadata successfully": "メタデータを正常に解析しました",
|
||||
"Path prefix": "パスプレフィックス",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "サインアップ HTML",
|
||||
"Signup HTML - Edit": "サインアップ HTML - 編集",
|
||||
"Signup HTML - Tooltip": "デフォルトのサインアップページスタイルを置き換えるためのカスタムHTML",
|
||||
"Silent": "Silent",
|
||||
"Site key": "サイトキー",
|
||||
"Site key - Tooltip": "サイトキー",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "サブタイプ",
|
||||
"Sub type - Tooltip": "サブタイプ",
|
||||
"Template code": "テンプレートコード",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "テストメール",
|
||||
"Test Email - Tooltip": "テストメールを受け取るためのメールアドレス",
|
||||
"Test SMTP Connection": "SMTP接続をテストする",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "トークンのURL",
|
||||
"Token URL - Tooltip": "トークンURL",
|
||||
"Type": "タイプ",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "入力されたものは有効なメールではありません",
|
||||
"The input is not valid Phone!": "この入力は有効な電話番号ではありません!",
|
||||
"Username": "ユーザー名",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "あなたのアカウントが作成されました!",
|
||||
"Your confirmed password is inconsistent with the password!": "確認されたパスワードは、パスワードと矛盾しています!",
|
||||
"sign in now": "今すぐサインインしてください"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "사용자가 새로운 계정을 등록할지 여부",
|
||||
"Failed to sign in": "로그인 실패했습니다",
|
||||
"File uploaded successfully": "파일이 성공적으로 업로드되었습니다",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "조직의 주제를 따르세요",
|
||||
"Form CSS": "CSS 양식",
|
||||
"Form CSS - Edit": "폼 CSS - 편집",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "가입, 로그인 및 비밀번호 재설정 양식의 위치",
|
||||
"Grant types": "Grant types: 부여 유형",
|
||||
"Grant types - Tooltip": "OAuth 프로토콜에서 허용되는 그란트 유형을 선택하십시오",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "왼쪽",
|
||||
"Logged in successfully": "성공적으로 로그인했습니다",
|
||||
"Logged out successfully": "로그아웃이 성공적으로 되었습니다",
|
||||
"New Application": "새로운 응용 프로그램",
|
||||
"No verification": "No verification",
|
||||
"None": "없음",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "당신의 신청서를 입력해주세요!",
|
||||
"Please input your organization!": "귀하의 조직을 입력해 주세요!",
|
||||
"Please select a HTML file": "HTML 파일을 선택해 주세요",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "프롬프트 페이지 URL이 클립 보드에 성공적으로 복사되었습니다. 시크릿 모드 창이나 다른 브라우저에 붙여 넣으세요",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "리디렉트 URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "리디렉션 URL (단언 서비스 소비자 POST 바인딩 URL)",
|
||||
"Redirect URLs": "URL 리디렉트",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "사이드 패널 HTML - 편집",
|
||||
"Side panel HTML - Tooltip": "로그인 페이지의 측면 패널용 HTML 코드를 맞춤 설정하십시오",
|
||||
"Sign Up Error": "가입 오류",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "로그인 페이지 URL이 클립보드에 성공적으로 복사되었습니다. 익명 창이나 다른 브라우저에 붙여넣어주세요",
|
||||
"Signin session": "로그인 세션",
|
||||
"Signup items": "가입 항목",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "호스트의 이름",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "IdP 인증서",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "발행자 URL",
|
||||
"Issuer URL - Tooltip": "발급자 URL",
|
||||
"Link copied to clipboard successfully": "링크가 클립보드에 성공적으로 복사되었습니다",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "SAML 메타데이터",
|
||||
"Method - Tooltip": "로그인 방법, QR 코드 또는 음성 로그인",
|
||||
"New Provider": "새로운 공급 업체",
|
||||
"Normal": "Normal",
|
||||
"Parse": "파싱",
|
||||
"Parse metadata successfully": "메타데이터를 성공적으로 분석했습니다",
|
||||
"Path prefix": "경로 접두어",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "가입 양식 HTML",
|
||||
"Signup HTML - Edit": "가입 HTML - 수정",
|
||||
"Signup HTML - Tooltip": "기본 가입 페이지 스타일을 바꾸기 위한 사용자 지정 HTML",
|
||||
"Silent": "Silent",
|
||||
"Site key": "사이트 키",
|
||||
"Site key - Tooltip": "사이트 키",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "하위 유형",
|
||||
"Sub type - Tooltip": "서브 타입",
|
||||
"Template code": "템플릿 코드",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "테스트 이메일",
|
||||
"Test Email - Tooltip": "테스트 메일을 받을 이메일 주소",
|
||||
"Test SMTP Connection": "테스트 SMTP 연결",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "토큰 URL",
|
||||
"Token URL - Tooltip": "토큰 URL",
|
||||
"Type": "타입",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "입력 값은 유효한 이메일이 아닙니다!",
|
||||
"The input is not valid Phone!": "입력이 유효한 전화번호가 아닙니다!",
|
||||
"Username": "사용자 이름",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "계정이 생성되었습니다!",
|
||||
"Your confirmed password is inconsistent with the password!": "확인된 비밀번호가 비밀번호와 일치하지 않습니다!",
|
||||
"sign in now": "지금 로그인하십시오"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Разрешить ли пользователям зарегистрировать новый аккаунт",
|
||||
"Failed to sign in": "Не удалось войти в систему",
|
||||
"File uploaded successfully": "Файл успешно загружен",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Cледуйте теме организации",
|
||||
"Form CSS": "Форма CSS",
|
||||
"Form CSS - Edit": "Форма CSS - Редактирование",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Местоположение форм регистрации, входа и восстановления пароля",
|
||||
"Grant types": "Типы грантов",
|
||||
"Grant types - Tooltip": "Выберите, какие типы грантов разрешены в протоколе OAuth",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Левый",
|
||||
"Logged in successfully": "Успешный вход в систему",
|
||||
"Logged out successfully": "Успешный выход из системы",
|
||||
"New Application": "Новое приложение",
|
||||
"No verification": "No verification",
|
||||
"None": "Никакой",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "Пожалуйста, введите свою заявку!",
|
||||
"Please input your organization!": "Пожалуйста, введите название вашей организации!",
|
||||
"Please select a HTML file": "Пожалуйста, выберите файл HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL страницы успешно скопирован в буфер обмена, пожалуйста, вставьте его в режиме инкогнито или в другом браузере",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Перенаправление URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Перенаправление URL (адрес сервиса потребителя утверждения POST-связывание)",
|
||||
"Redirect URLs": "Перенаправление URL-адресов",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Боковая панель HTML - Редактировать",
|
||||
"Side panel HTML - Tooltip": "Настроить HTML-код для боковой панели страницы входа в систему",
|
||||
"Sign Up Error": "Ошибка при регистрации",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "URL страницы входа скопирован успешно, пожалуйста, вставьте ее в инкогнито-окно или другой браузер",
|
||||
"Signin session": "Сессия входа в систему",
|
||||
"Signup items": "Элементы регистрации",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Имя хоста",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "Сертификат IdP",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "URL выпускающего органа",
|
||||
"Issuer URL - Tooltip": "URL эмитента",
|
||||
"Link copied to clipboard successfully": "Ссылка успешно скопирована в буфер обмена",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "Метаданные SAML",
|
||||
"Method - Tooltip": "Метод входа, QR-код или беззвучный вход",
|
||||
"New Provider": "Новый провайдер",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Спарсить",
|
||||
"Parse metadata successfully": "Успешно обработана метаданные",
|
||||
"Path prefix": "Префикс пути",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Регистрационная форма HTML",
|
||||
"Signup HTML - Edit": "Регистрационная форма HTML - Редактировать",
|
||||
"Signup HTML - Tooltip": "Пользовательский HTML для замены стиля стандартной страницы регистрации",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Ключ сайта",
|
||||
"Site key - Tooltip": "Ключ сайта",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Подтип",
|
||||
"Sub type - Tooltip": "Подтип",
|
||||
"Template code": "Шаблонный код",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Тестовое письмо",
|
||||
"Test Email - Tooltip": "Адрес электронной почты для получения тестовых писем",
|
||||
"Test SMTP Connection": "Тестирование соединения SMTP",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Токен URL (URL-адрес маркера)",
|
||||
"Token URL - Tooltip": "Токен URL",
|
||||
"Type": "Тип",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "Ввод не является действительным адресом электронной почты!",
|
||||
"The input is not valid Phone!": "Ввод не является действительным телефоном!",
|
||||
"Username": "Имя пользователя",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Ваш аккаунт был создан!",
|
||||
"Your confirmed password is inconsistent with the password!": "Ваш подтвержденный пароль не соответствует паролю!",
|
||||
"sign in now": "войти сейчас"
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"Enable signup - Tooltip": "Có cho phép người dùng đăng ký tài khoản mới không?",
|
||||
"Failed to sign in": "Không đăng nhập được",
|
||||
"File uploaded successfully": "Tệp được tải lên thành công",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Theo chủ đề tổ chức",
|
||||
"Form CSS": "Mẫu CSS",
|
||||
"Form CSS - Edit": "Biểu mẫu CSS - Chỉnh sửa",
|
||||
@@ -49,15 +50,21 @@
|
||||
"Form position - Tooltip": "Vị trí của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu",
|
||||
"Grant types": "Loại hỗ trợ",
|
||||
"Grant types - Tooltip": "Chọn loại hỗ trợ được cho phép trong giao thức OAuth",
|
||||
"Incremental": "Incremental",
|
||||
"Left": "Trái",
|
||||
"Logged in successfully": "Đăng nhập thành công",
|
||||
"Logged out successfully": "Đã đăng xuất thành công",
|
||||
"New Application": "Ứng dụng mới",
|
||||
"No verification": "No verification",
|
||||
"None": "Không có gì",
|
||||
"Normal": "Normal",
|
||||
"Only signup": "Only signup",
|
||||
"Please input your application!": "Vui lòng nhập đơn của bạn!",
|
||||
"Please input your organization!": "Vui lòng nhập tên tổ chức của bạn!",
|
||||
"Please select a HTML file": "Vui lòng chọn tệp HTML",
|
||||
"Prompt page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Đã sao chép đường dẫn trang một cách thành công, hãy dán nó vào cửa sổ ẩn danh hoặc trình duyệt khác",
|
||||
"Random": "Random",
|
||||
"Real name": "Real name",
|
||||
"Redirect URL": "Chuyển hướng đường dẫn URL",
|
||||
"Redirect URL (Assertion Consumer Service POST Binding URL) - Tooltip": "Điều hướng URL (URL khung POST Dịch vụ Tiêu thụ Khẳng định)",
|
||||
"Redirect URLs": "Chuyển hướng URL",
|
||||
@@ -74,6 +81,8 @@
|
||||
"Side panel HTML - Edit": "Bảng Panel Bên - Chỉnh sửa HTML",
|
||||
"Side panel HTML - Tooltip": "Tùy chỉnh mã HTML cho bảng điều khiển bên của trang đăng nhập",
|
||||
"Sign Up Error": "Lỗi đăng ký",
|
||||
"Signin": "Signin",
|
||||
"Signin (Default True)": "Signin (Default True)",
|
||||
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Đã sao chép thành công địa chỉ URL trang Đăng nhập vào clipboard, vui lòng dán nó vào cửa sổ ẩn danh hoặc trình duyệt khác",
|
||||
"Signin session": "Phiên đăng nhập",
|
||||
"Signup items": "Các mục đăng ký",
|
||||
@@ -504,6 +513,8 @@
|
||||
"Host - Tooltip": "Tên của người chủ chỗ ở",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "Chứng chỉ IdP",
|
||||
"Intelligent Validation": "Intelligent Validation",
|
||||
"Internal": "Internal",
|
||||
"Issuer URL": "Địa chỉ URL của người phát hành",
|
||||
"Issuer URL - Tooltip": "Địa chỉ URL của nhà phát hành",
|
||||
"Link copied to clipboard successfully": "Đã sao chép liên kết vào bộ nhớ tạm thành công",
|
||||
@@ -511,6 +522,7 @@
|
||||
"Metadata - Tooltip": "SAML metadata: siêu dữ liệu SAML",
|
||||
"Method - Tooltip": "Phương thức đăng nhập, mã QR hoặc đăng nhập im lặng",
|
||||
"New Provider": "Nhà cung cấp mới",
|
||||
"Normal": "Normal",
|
||||
"Parse": "Phân tích cú pháp",
|
||||
"Parse metadata successfully": "Phân tích siêu dữ liệu thành công",
|
||||
"Path prefix": "Tiền tố đường dẫn",
|
||||
@@ -555,8 +567,10 @@
|
||||
"Signup HTML": "Đăng ký HTML",
|
||||
"Signup HTML - Edit": "Đăng ký HTML - Chỉnh sửa",
|
||||
"Signup HTML - Tooltip": "Trang HTML tùy chỉnh để thay thế phong cách trang đăng ký mặc định",
|
||||
"Silent": "Silent",
|
||||
"Site key": "Khóa trang web",
|
||||
"Site key - Tooltip": "Khóa trang web",
|
||||
"Sliding Validation": "Sliding Validation",
|
||||
"Sub type": "Loại phụ",
|
||||
"Sub type - Tooltip": "Loại phụ",
|
||||
"Template code": "Mã mẫu của template",
|
||||
@@ -564,6 +578,7 @@
|
||||
"Test Email": "Thư Email kiểm tra",
|
||||
"Test Email - Tooltip": "Địa chỉ email để nhận thư kiểm tra",
|
||||
"Test SMTP Connection": "Kiểm tra kết nối SMTP",
|
||||
"Third-party": "Third-party",
|
||||
"Token URL": "Đường dẫn Token",
|
||||
"Token URL - Tooltip": "Địa chỉ URL của Token",
|
||||
"Type": "Kiểu",
|
||||
@@ -621,6 +636,7 @@
|
||||
"The input is not valid Email!": "Đầu vào không phải là địa chỉ Email hợp lệ!",
|
||||
"The input is not valid Phone!": "Đầu vào không hợp lệ! Số điện thoại không hợp lệ!",
|
||||
"Username": "Tên đăng nhập",
|
||||
"Username - Tooltip": "Username - Tooltip",
|
||||
"Your account has been created!": "Tài khoản của bạn đã được tạo!",
|
||||
"Your confirmed password is inconsistent with the password!": "Mật khẩu xác nhận của bạn không khớp với mật khẩu đã nhập!",
|
||||
"sign in now": "Đăng nhập ngay bây giờ"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user