mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-28 09:10:31 +08:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
19209718ea | ||
![]() |
e75d26260a | ||
![]() |
6572ab69ce | ||
![]() |
8db87a7559 | ||
![]() |
0dcccfc19c | ||
![]() |
96219442f5 | ||
![]() |
903745c540 | ||
![]() |
df741805cd | ||
![]() |
ee5c3f3f39 | ||
![]() |
714f69be7b | ||
![]() |
0d12972e92 | ||
![]() |
78b62c28ab | ||
![]() |
5c26335fd6 | ||
![]() |
7edaeafea5 | ||
![]() |
336f3f7a7b | ||
![]() |
47dc3715f9 | ||
![]() |
7503e05a4a | ||
![]() |
b89cf1de07 | ||
![]() |
be87078c25 | ||
![]() |
faf352acc5 | ||
![]() |
0db61dd658 | ||
![]() |
ebe8ad8669 | ||
![]() |
2e01f0d10e | ||
![]() |
754fa1e745 | ||
![]() |
8b9e0ba96b | ||
![]() |
b0656aca36 | ||
![]() |
623b4fee17 | ||
![]() |
1b1de1dd01 | ||
![]() |
968d8646b2 | ||
![]() |
94eef7dceb | ||
![]() |
fe647939ce | ||
![]() |
984a69cb4b | ||
![]() |
098a1ece68 | ||
![]() |
ad6f2ad2e1 | ||
![]() |
2d55252261 | ||
![]() |
30ea3a1335 | ||
![]() |
b7d78d1e27 | ||
![]() |
3d5a645a3b | ||
![]() |
4ad21e7781 |
@@ -64,6 +64,7 @@ COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||
COPY --from=FRONT /web/build ./web/build
|
||||
RUN mkdir tempFiles
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
@@ -137,7 +137,7 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
||||
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && form.Phone != "" {
|
||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
||||
if checkResult.Code != object.VerificationSuccess {
|
||||
|
@@ -339,7 +339,7 @@ func (c *ApiController) Login() {
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "SAML" {
|
||||
// SAML
|
||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
|
||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -546,7 +546,7 @@ func (c *ApiController) Login() {
|
||||
func (c *ApiController) GetSamlLogin() {
|
||||
providerId := c.Input().Get("id")
|
||||
relayState := c.Input().Get("relayState")
|
||||
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState, c.GetAcceptLanguage())
|
||||
authURL, method, err := object.GenerateSamlRequest(providerId, relayState, c.Ctx.Request.Host, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
@@ -72,6 +72,11 @@ func (c *RootController) CasProxyValidate() {
|
||||
c.CasP3ServiceAndProxyValidate()
|
||||
}
|
||||
|
||||
func queryUnescape(service string) string {
|
||||
s, _ := url.QueryUnescape(service)
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
ticket := c.Input().Get("ticket")
|
||||
format := c.Input().Get("format")
|
||||
@@ -91,7 +96,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
||||
// find the token
|
||||
if ok {
|
||||
// check whether service is the one for which we previously issued token
|
||||
if strings.HasPrefix(service, issuedService) {
|
||||
if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
|
||||
serviceResponse.Success = response
|
||||
} else {
|
||||
// service not match
|
||||
|
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()
|
||||
}
|
@@ -65,7 +65,7 @@ func (c *ApiController) GetLdapUsers() {
|
||||
// })
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
users, err := conn.GetLdapUsers(ldapServer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -80,15 +80,16 @@ func (c *ApiController) GetLdapUsers() {
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
// GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
Uuid: user.Uuid,
|
||||
DisplayName: user.DisplayName,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
||||
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||
|
||||
c.ResponseOk(resp, existUuids)
|
||||
}
|
||||
@@ -131,7 +132,7 @@ func (c *ApiController) AddLdap() {
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
@@ -160,7 +161,7 @@ func (c *ApiController) AddLdap() {
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
132
controllers/message.go
Normal file
132
controllers/message.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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")
|
||||
chat := c.Input().Get("chat")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var messages []*object.Message
|
||||
if chat == "" {
|
||||
messages = object.GetMessages(owner)
|
||||
} else {
|
||||
messages = object.GetChatMessages(chat)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedMessages(messages)
|
||||
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()
|
||||
}
|
@@ -128,7 +128,7 @@ func (c *ApiController) DeleteToken() {
|
||||
// @Title GetOAuthCode
|
||||
// @Tag Token API
|
||||
// @Description get OAuth code
|
||||
// @Param id query string true "The id ( owner/name ) of user"
|
||||
// @Param user_id query string true "The id ( owner/name ) of user"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param response_type query string true "OAuth response type"
|
||||
// @Param redirect_uri query string true "OAuth redirect URI"
|
||||
|
@@ -138,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 {
|
||||
@@ -149,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 != "" {
|
||||
@@ -161,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)
|
||||
|
@@ -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
|
||||
|
||||
|
4
go.mod
4
go.mod
@@ -17,14 +17,16 @@ require (
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
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.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
|
19
go.sum
19
go.sum
@@ -173,6 +173,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/forestmgy/ldapserver v1.1.0 h1:gvil4nuLhqPEL8SugCkFhRyA0/lIvRdwZSqlrw63ll4=
|
||||
github.com/forestmgy/ldapserver v1.1.0/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||
@@ -222,10 +224,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.6.0 h1:uLInMApSvBfP+vEFasNE0rnVPG++fjp7lmAIvNhe+UU=
|
||||
github.com/go-webauthn/webauthn v0.6.0/go.mod h1:7edMRZXwuM6JIVjN68G24Bzt+bPCvTmjiL0j+cAmXtY=
|
||||
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,10 +236,13 @@ 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.3/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=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -658,8 +663,10 @@ 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.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -674,6 +681,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -741,6 +749,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -833,6 +842,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -843,6 +853,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "E-Mail ist ungültig",
|
||||
"Empty username.": "Leerer Benutzername.",
|
||||
"FirstName cannot be blank": "Vorname darf nicht leer sein",
|
||||
"LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"LastName cannot be blank": "Nachname darf nicht leer sein",
|
||||
"Ldap user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Mehrere Konten mit derselben uid, bitte überprüfen Sie Ihren LDAP-Server",
|
||||
"Organization does not exist": "Organisation existiert nicht",
|
||||
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
||||
"Session outdated, please login again": "Sitzung abgelaufen, bitte erneut anmelden",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Dem Benutzer ist der Zugang verboten, bitte kontaktieren Sie den Administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche oder Bindestriche enthalten, keine aufeinanderfolgenden Bindestriche oder Unterstriche haben und darf nicht mit einem Bindestrich oder Unterstrich beginnen oder enden.",
|
||||
"Username already exists": "Benutzername existiert bereits",
|
||||
"Username cannot be an email address": "Benutzername kann keine E-Mail-Adresse sein",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Ldap user name or password incorrect": "Ldap user name or password incorrect",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
"Session outdated, please login again": "Session outdated, please login again",
|
||||
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
|
||||
"Username already exists": "Username already exists",
|
||||
"Username cannot be an email address": "Username cannot be an email address",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "El correo electrónico no es válido",
|
||||
"Empty username.": "Nombre de usuario vacío.",
|
||||
"FirstName cannot be blank": "El nombre no puede estar en blanco",
|
||||
"LDAP user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
|
||||
"LastName cannot be blank": "El apellido no puede estar en blanco",
|
||||
"Ldap user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Cuentas múltiples con el mismo uid, por favor revise su servidor ldap",
|
||||
"Organization does not exist": "La organización no existe",
|
||||
"Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "El número de teléfono no es válido",
|
||||
"Session outdated, please login again": "Sesión expirada, por favor vuelva a iniciar sesión",
|
||||
"The user is forbidden to sign in, please contact the administrator": "El usuario no está autorizado a iniciar sesión, por favor contacte al administrador",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "El nombre de usuario solo puede contener caracteres alfanuméricos, guiones bajos o guiones, no puede tener guiones o subrayados consecutivos, y no puede comenzar ni terminar con un guión o subrayado.",
|
||||
"Username already exists": "El nombre de usuario ya existe",
|
||||
"Username cannot be an email address": "Nombre de usuario no puede ser una dirección de correo electrónico",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "L'adresse e-mail est invalide",
|
||||
"Empty username.": "Nom d'utilisateur vide.",
|
||||
"FirstName cannot be blank": "Le prénom ne peut pas être laissé vide",
|
||||
"LDAP user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
|
||||
"LastName cannot be blank": "Le nom de famille ne peut pas être vide",
|
||||
"Ldap user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Plusieurs comptes avec le même identifiant d'utilisateur, veuillez vérifier votre serveur LDAP",
|
||||
"Organization does not exist": "L'organisation n'existe pas",
|
||||
"Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
||||
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
||||
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
||||
"Username already exists": "Nom d'utilisateur existe déjà",
|
||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "Email tidak valid",
|
||||
"Empty username.": "Nama pengguna kosong.",
|
||||
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
||||
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"LastName cannot be blank": "Nama belakang tidak boleh kosong",
|
||||
"Ldap user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda",
|
||||
"Organization does not exist": "Organisasi tidak ada",
|
||||
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||
"Session outdated, please login again": "Sesi kedaluwarsa, silakan masuk lagi",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Pengguna dilarang masuk, silakan hubungi administrator",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Nama pengguna hanya bisa menggunakan karakter alfanumerik, garis bawah atau tanda hubung, tidak boleh memiliki dua tanda hubung atau garis bawah berurutan, dan tidak boleh diawali atau diakhiri dengan tanda hubung atau garis bawah.",
|
||||
"Username already exists": "Nama pengguna sudah ada",
|
||||
"Username cannot be an email address": "Username tidak bisa menjadi alamat email",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "電子メールは無効です",
|
||||
"Empty username.": "空のユーザー名。",
|
||||
"FirstName cannot be blank": "ファーストネームは空白にできません",
|
||||
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"LastName cannot be blank": "姓は空白にできません",
|
||||
"Ldap user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||
"Organization does not exist": "組織は存在しません",
|
||||
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "電話番号が無効です",
|
||||
"Session outdated, please login again": "セッションが期限切れになりました。再度ログインしてください",
|
||||
"The user is forbidden to sign in, please contact the administrator": "ユーザーはサインインできません。管理者に連絡してください",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "ユーザー名には英数字、アンダースコア、ハイフンしか含めることができません。連続したハイフンまたはアンダースコアは不可であり、ハイフンまたはアンダースコアで始まるまたは終わることもできません。",
|
||||
"Username already exists": "ユーザー名はすでに存在しています",
|
||||
"Username cannot be an email address": "ユーザー名には電子メールアドレスを使用できません",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "이메일이 유효하지 않습니다",
|
||||
"Empty username.": "빈 사용자 이름.",
|
||||
"FirstName cannot be blank": "이름은 공백일 수 없습니다",
|
||||
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||
"Ldap user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||
"Organization does not exist": "조직은 존재하지 않습니다",
|
||||
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "전화번호가 유효하지 않습니다",
|
||||
"Session outdated, please login again": "세션이 만료되었습니다. 다시 로그인해주세요",
|
||||
"The user is forbidden to sign in, please contact the administrator": "사용자는 로그인이 금지되어 있습니다. 관리자에게 문의하십시오",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "사용자 이름은 알파벳, 숫자, 밑줄 또는 하이픈만 포함할 수 있으며, 연속된 하이픈 또는 밑줄을 가질 수 없으며, 하이픈 또는 밑줄로 시작하거나 끝날 수 없습니다.",
|
||||
"Username already exists": "사용자 이름이 이미 존재합니다",
|
||||
"Username cannot be an email address": "사용자 이름은 이메일 주소가 될 수 없습니다",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "Адрес электронной почты недействительный",
|
||||
"Empty username.": "Пустое имя пользователя.",
|
||||
"FirstName cannot be blank": "Имя не может быть пустым",
|
||||
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Ldap user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
"Organization does not exist": "Организация не существует",
|
||||
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||
"Username already exists": "Имя пользователя уже существует",
|
||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "Địa chỉ email không hợp lệ",
|
||||
"Empty username.": "Tên đăng nhập trống.",
|
||||
"FirstName cannot be blank": "Tên không được để trống",
|
||||
"LDAP user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
|
||||
"LastName cannot be blank": "Họ không thể để trống",
|
||||
"Ldap user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Nhiều tài khoản với cùng một uid, vui lòng kiểm tra máy chủ ldap của bạn",
|
||||
"Organization does not exist": "Tổ chức không tồn tại",
|
||||
"Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "Số điện thoại không hợp lệ",
|
||||
"Session outdated, please login again": "Phiên làm việc hết hạn, vui lòng đăng nhập lại",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Người dùng bị cấm đăng nhập, vui lòng liên hệ với quản trị viên",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Tên người dùng chỉ có thể chứa các ký tự chữ và số, gạch dưới hoặc gạch ngang, không được có hai ký tự gạch dưới hoặc gạch ngang liền kề và không được bắt đầu hoặc kết thúc bằng dấu gạch dưới hoặc gạch ngang.",
|
||||
"Username already exists": "Tên đăng nhập đã tồn tại",
|
||||
"Username cannot be an email address": "Tên người dùng không thể là địa chỉ email",
|
||||
|
@@ -32,8 +32,8 @@
|
||||
"Email is invalid": "无效邮箱",
|
||||
"Empty username.": "用户名不可为空",
|
||||
"FirstName cannot be blank": "名不可以为空",
|
||||
"LDAP user name or password incorrect": "LDAP密码错误",
|
||||
"LastName cannot be blank": "姓不可以为空",
|
||||
"Ldap user name or password incorrect": "LDAP密码错误",
|
||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||
"Organization does not exist": "组织不存在",
|
||||
"Password must have at least 6 characters": "新密码至少为6位",
|
||||
@@ -42,6 +42,7 @@
|
||||
"Phone number is invalid": "无效手机号",
|
||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||
"Username already exists": "用户名已存在",
|
||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||
|
@@ -159,8 +159,8 @@
|
||||
"serverName": "",
|
||||
"host": "",
|
||||
"port": 389,
|
||||
"admin": "",
|
||||
"passwd": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"baseDn": "",
|
||||
"autoSync": 0,
|
||||
"lastSync": ""
|
||||
|
@@ -110,12 +110,11 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
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))
|
||||
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
||||
e.AddAttribute("userPassword", message.AttributeValue(getUserPasswordWithType(user)))
|
||||
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
||||
|
||||
for _, attr := range r.Attributes() {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
|
19
ldap/util.go
19
ldap/util.go
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/lor00x/goldap/message"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
)
|
||||
@@ -68,6 +69,7 @@ func getUsername(filter string) string {
|
||||
|
||||
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
|
||||
@@ -114,3 +116,20 @@ func getUserPasswordWithType(user *object.User) string {
|
||||
}
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
||||
|
||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||
switch attributeName {
|
||||
case "cn":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "uid":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "email":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mobile":
|
||||
return message.AttributeValue(user.Phone)
|
||||
case "userPassword":
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -80,3 +80,21 @@ func DownloadAndUpload(url string, fullFilePath string, lang string) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrlFromBuffer(organization string, username string, fileBuffer *bytes.Buffer, ext string, upload bool) string {
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s%s", organization, username, ext)
|
||||
uploadedFileUrl, _ := GetUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||
|
||||
if upload {
|
||||
_, _, err := UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer, "en")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedFileUrl
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
@@ -37,3 +38,22 @@ func TestSyncPermanentAvatars(t *testing.T) {
|
||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAvatars(t *testing.T) {
|
||||
InitConfig()
|
||||
InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
users := GetUsers("casdoor")
|
||||
for _, user := range users {
|
||||
if strings.HasPrefix(user.Avatar, "http") {
|
||||
continue
|
||||
}
|
||||
|
||||
updated := user.refreshAvatar()
|
||||
if updated {
|
||||
user.PermanentAvatar = "*"
|
||||
UpdateUser(user.GetId(), user, []string{"avatar"}, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
167
object/avatar_util.go
Normal file
167
object/avatar_util.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
func hasGravatar(client *http.Client, email string) (bool, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL with d=404 parameter
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s?d=404", hashedEmail)
|
||||
|
||||
// Send a request to Gravatar
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check if the user has a custom Gravatar image
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return true, nil
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("failed to fetch gravatar image: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func getGravatarFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
|
||||
// Clean and lowercase the email
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
// Generate MD5 hash of the email
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, email)
|
||||
hashedEmail := fmt.Sprintf("%x", hash.Sum(nil))
|
||||
|
||||
// Create Gravatar URL
|
||||
gravatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s", hashedEmail)
|
||||
|
||||
// Download the image
|
||||
req, err := http.NewRequest("GET", gravatarURL, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, "", fmt.Errorf("failed to download gravatar image: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Get the content type and determine the file extension
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
fileExtension := ""
|
||||
switch contentType {
|
||||
case "image/jpeg":
|
||||
fileExtension = ".jpg"
|
||||
case "image/png":
|
||||
fileExtension = ".png"
|
||||
case "image/gif":
|
||||
fileExtension = ".gif"
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
|
||||
}
|
||||
|
||||
// Save the image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = io.Copy(buffer, resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return buffer, fileExtension, nil
|
||||
}
|
||||
|
||||
func getColor(data []byte) color.RGBA {
|
||||
r := int(data[0]) % 256
|
||||
g := int(data[1]) % 256
|
||||
b := int(data[2]) % 256
|
||||
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
|
||||
}
|
||||
|
||||
func getIdenticonFileBuffer(username string) (*bytes.Buffer, string, error) {
|
||||
username = strings.TrimSpace(strings.ToLower(username))
|
||||
|
||||
hash := md5.New()
|
||||
io.WriteString(hash, username)
|
||||
hashedUsername := hash.Sum(nil)
|
||||
|
||||
// Define the size of the image
|
||||
const imageSize = 420
|
||||
const cellSize = imageSize / 7
|
||||
|
||||
// Create a new image
|
||||
img := image.NewRGBA(image.Rect(0, 0, imageSize, imageSize))
|
||||
|
||||
// Create a context
|
||||
dc := gg.NewContextForRGBA(img)
|
||||
|
||||
// Set a background color
|
||||
dc.SetColor(color.RGBA{240, 240, 240, 255})
|
||||
dc.Clear()
|
||||
|
||||
// Get avatar color
|
||||
avatarColor := getColor(hashedUsername)
|
||||
|
||||
// Draw cells
|
||||
for i := 0; i < 7; i++ {
|
||||
for j := 0; j < 7; j++ {
|
||||
if (hashedUsername[i] >> uint(j) & 1) == 1 {
|
||||
dc.SetColor(avatarColor)
|
||||
dc.DrawRectangle(float64(j*cellSize), float64(i*cellSize), float64(cellSize), float64(cellSize))
|
||||
dc.Fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save image to a bytes.Buffer
|
||||
buffer := &bytes.Buffer{}
|
||||
err := png.Encode(buffer, img)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to save image: %w", err)
|
||||
}
|
||||
|
||||
return buffer, ".png", nil
|
||||
}
|
143
object/chat.go
Normal file
143
object/chat.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
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)
|
||||
}
|
@@ -188,29 +188,33 @@ func CheckPassword(user *User, password string, lang string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) {
|
||||
func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
ldaps := GetLdaps(user.Owner)
|
||||
ldapLoginSuccess := false
|
||||
hit := false
|
||||
|
||||
for _, ldapServer := range ldaps {
|
||||
conn, err := ldapServer.GetLdapConn()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, []string{}, nil)
|
||||
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||
0, 0, false, ldapServer.buildFilterString(user), []string{}, nil)
|
||||
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
continue
|
||||
} else if len(searchResult.Entries) > 1 {
|
||||
return nil, i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
||||
}
|
||||
if len(searchResult.Entries) > 1 {
|
||||
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
||||
}
|
||||
|
||||
hit = true
|
||||
dn := searchResult.Entries[0].DN
|
||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||
ldapLoginSuccess = true
|
||||
@@ -219,9 +223,12 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
return nil, i18n.Translate(lang, "check:Ldap user name or password incorrect")
|
||||
if !hit {
|
||||
return "user not exist"
|
||||
}
|
||||
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
|
||||
}
|
||||
return user, ""
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
|
||||
@@ -236,10 +243,14 @@ func CheckUserPassword(organization string, username string, password string, la
|
||||
|
||||
if user.Ldap != "" {
|
||||
// ONLY for ldap users
|
||||
return checkLdapUserPassword(user, password, lang)
|
||||
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
|
||||
if msg == "user not exist" {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
}
|
||||
return nil, msg
|
||||
}
|
||||
} else {
|
||||
msg := CheckPassword(user, password, lang)
|
||||
if msg != "" {
|
||||
if msg := CheckPassword(user, password, lang); msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
}
|
||||
@@ -342,7 +353,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")
|
||||
}
|
||||
|
@@ -219,8 +219,8 @@ func initBuiltInLdap() {
|
||||
ServerName: "BuildIn LDAP Server",
|
||||
Host: "example.com",
|
||||
Port: 389,
|
||||
Admin: "cn=buildin,dc=example,dc=com",
|
||||
Passwd: "123",
|
||||
Username: "cn=buildin,dc=example,dc=com",
|
||||
Password: "123",
|
||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||
AutoSync: 0,
|
||||
LastSync: "",
|
||||
|
402
object/ldap.go
402
object/ldap.go
@@ -15,14 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type Ldap struct {
|
||||
@@ -30,263 +23,20 @@ type Ldap struct {
|
||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `xorm:"int" json:"port"`
|
||||
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||
Username string `xorm:"varchar(100)" json:"username"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
Filter string `xorm:"varchar(200)" json:"filter"`
|
||||
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
||||
|
||||
AutoSync int `json:"autoSync"`
|
||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||
}
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
IsAD bool
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
// Gcn string
|
||||
Uuid string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
// GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type ldapServerType struct {
|
||||
Vendorname string
|
||||
Vendorversion string
|
||||
IsGlobalCatalogReady string
|
||||
ForestFunctionality string
|
||||
}
|
||||
|
||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
returnAnyNotEmpty := func(strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
res := make([]LdapRespUser, 0)
|
||||
for _, user := range users {
|
||||
res = append(res, LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
Uuid: user.Uuid,
|
||||
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest("",
|
||||
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
isMicrosoft := false
|
||||
var ldapServerType ldapServerType
|
||||
for _, entry := range searchResult.Entries {
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "vendorname":
|
||||
ldapServerType.Vendorname = attribute.Values[0]
|
||||
case "vendorversion":
|
||||
ldapServerType.Vendorversion = attribute.Values[0]
|
||||
case "isGlobalCatalogReady":
|
||||
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
|
||||
case "forestFunctionality":
|
||||
ldapServerType.ForestFunctionality = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
if ldapServerType.Vendorname == "" &&
|
||||
ldapServerType.Vendorversion == "" &&
|
||||
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
|
||||
ldapServerType.ForestFunctionality != "" {
|
||||
isMicrosoft = true
|
||||
}
|
||||
return isMicrosoft, err
|
||||
}
|
||||
|
||||
func (ldap *Ldap) GetLdapConn() (c *ldapConn, err error) {
|
||||
var conn *goldap.Conn
|
||||
if ldap.EnableSsl {
|
||||
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||
} else {
|
||||
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ldap.Admin, ldap.Passwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAD, err := isMicrosoftAD(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
//FIXME: The Base DN does not necessarily contain the Group
|
||||
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
//}
|
||||
|
||||
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{
|
||||
"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
SearchFilterMsAD := "(objectClass=user)"
|
||||
SearchAttributesMsAD := []string{
|
||||
"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
var searchReq *goldap.SearchRequest
|
||||
if l.IsAD {
|
||||
searchReq = goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilterMsAD, SearchAttributesMsAD, nil)
|
||||
} else {
|
||||
searchReq = goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
}
|
||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
var ldapUserItem ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
ldapUserItem.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "sAMAccountName":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
ldapUserItem.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
ldapUserItem.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "objectGUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "mail":
|
||||
ldapUserItem.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
ldapUserItem.Email = attribute.Values[0]
|
||||
case "emailAddress":
|
||||
ldapUserItem.EmailAddress = attribute.Values[0]
|
||||
case "telephoneNumber":
|
||||
ldapUserItem.TelephoneNumber = attribute.Values[0]
|
||||
case "mobile":
|
||||
ldapUserItem.Mobile = attribute.Values[0]
|
||||
case "mobileTelephoneNumber":
|
||||
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
|
||||
case "registeredAddress":
|
||||
ldapUserItem.RegisteredAddress = attribute.Values[0]
|
||||
case "postalAddress":
|
||||
ldapUserItem.PostalAddress = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, ldapUserItem)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
func AddLdap(ldap *Ldap) bool {
|
||||
if len(ldap.Id) == 0 {
|
||||
ldap.Id = util.GenerateId()
|
||||
@@ -307,12 +57,12 @@ func AddLdap(ldap *Ldap) bool {
|
||||
func CheckLdapExist(ldap *Ldap) bool {
|
||||
var result []*Ldap
|
||||
err := adapter.Engine.Find(&result, &Ldap{
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Admin: ldap.Admin,
|
||||
Passwd: ldap.Passwd,
|
||||
BaseDn: ldap.BaseDn,
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Username: ldap.Username,
|
||||
Password: ldap.Password,
|
||||
BaseDn: ldap.BaseDn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -359,7 +109,7 @@ func UpdateLdap(ldap *Ldap) bool {
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "enable_ssl", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -375,123 +125,3 @@ func DeleteLdap(ldap *Ldap) bool {
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range users {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
|
||||
organization := getOrganization("admin", owner)
|
||||
ldap := GetLdap(ldapId)
|
||||
|
||||
var dc []string
|
||||
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||
if strings.Contains(basedn, "dc=") {
|
||||
dc = append(dc, basedn[3:])
|
||||
}
|
||||
}
|
||||
affiliation := strings.Join(dc, ".")
|
||||
|
||||
var ou []string
|
||||
for _, admin := range strings.Split(ldap.Admin, ",") {
|
||||
if strings.Contains(admin, "ou=") {
|
||||
ou = append(ou, admin[3:])
|
||||
}
|
||||
}
|
||||
tag := strings.Join(ou, ".")
|
||||
|
||||
for _, user := range users {
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
for _, existUuid := range existUuids {
|
||||
if user.Uuid == existUuid {
|
||||
existUsers = append(existUsers, user)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found && !AddUser(&User{
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: user.Cn,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: affiliation,
|
||||
Tag: tag,
|
||||
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
var existUuids []string
|
||||
existUuidSet := make(map[string]struct{})
|
||||
|
||||
//whereStr := ""
|
||||
//for i, uuid := range uuids {
|
||||
// if i == 0 {
|
||||
// whereStr = fmt.Sprintf("'%s'", uuid)
|
||||
// } else {
|
||||
// whereStr = fmt.Sprintf(",'%s'", uuid)
|
||||
// }
|
||||
//}
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
for _, result := range results {
|
||||
existUuidSet[result.Ldap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for uuid := range existUuidSet {
|
||||
existUuids = append(existUuids, uuid)
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func buildLdapUserName(uid, uidNum string) string {
|
||||
var result User
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
|
||||
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if result.Name == uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
|
@@ -82,7 +82,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
||||
continue
|
||||
}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap.BaseDn)
|
||||
users, err := conn.GetLdapUsers(ldap)
|
||||
if err != nil {
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
@@ -112,3 +112,10 @@ func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
403
object/ldap_conn.go
Normal file
403
object/ldap_conn.go
Normal file
@@ -0,0 +1,403 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type LdapConn struct {
|
||||
Conn *goldap.Conn
|
||||
IsAD bool
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
// Gcn string
|
||||
Uuid string
|
||||
DisplayName string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
// GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||
var conn *goldap.Conn
|
||||
if ldap.EnableSsl {
|
||||
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||
} else {
|
||||
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(ldap.Username, ldap.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isAD, err := isMicrosoftAD(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest("",
|
||||
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
isMicrosoft := false
|
||||
|
||||
type ldapServerType struct {
|
||||
Vendorname string
|
||||
Vendorversion string
|
||||
IsGlobalCatalogReady string
|
||||
ForestFunctionality string
|
||||
}
|
||||
var ldapServerTypes ldapServerType
|
||||
for _, entry := range searchResult.Entries {
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "vendorname":
|
||||
ldapServerTypes.Vendorname = attribute.Values[0]
|
||||
case "vendorversion":
|
||||
ldapServerTypes.Vendorversion = attribute.Values[0]
|
||||
case "isGlobalCatalogReady":
|
||||
ldapServerTypes.IsGlobalCatalogReady = attribute.Values[0]
|
||||
case "forestFunctionality":
|
||||
ldapServerTypes.ForestFunctionality = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
if ldapServerTypes.Vendorname == "" &&
|
||||
ldapServerTypes.Vendorversion == "" &&
|
||||
ldapServerTypes.IsGlobalCatalogReady == "TRUE" &&
|
||||
ldapServerTypes.ForestFunctionality != "" {
|
||||
isMicrosoft = true
|
||||
}
|
||||
return isMicrosoft, err
|
||||
}
|
||||
|
||||
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
|
||||
SearchAttributes := []string{
|
||||
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
|
||||
}
|
||||
if l.IsAD {
|
||||
SearchAttributes = append(SearchAttributes, "sAMAccountName")
|
||||
} else {
|
||||
SearchAttributes = append(SearchAttributes, "uid")
|
||||
}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn, goldap.ScopeWholeSubtree, goldap.NeverDerefAliases,
|
||||
0, 0, false,
|
||||
ldapServer.Filter, SearchAttributes, nil)
|
||||
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
for _, entry := range searchResult.Entries {
|
||||
var user ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
user.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
user.Uid = attribute.Values[0]
|
||||
case "sAMAccountName":
|
||||
user.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
user.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
user.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
user.Uuid = attribute.Values[0]
|
||||
case "objectGUID":
|
||||
user.Uuid = attribute.Values[0]
|
||||
case "displayName":
|
||||
user.DisplayName = attribute.Values[0]
|
||||
case "mail":
|
||||
user.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
user.Email = attribute.Values[0]
|
||||
case "emailAddress":
|
||||
user.EmailAddress = attribute.Values[0]
|
||||
case "telephoneNumber":
|
||||
user.TelephoneNumber = attribute.Values[0]
|
||||
case "mobile":
|
||||
user.Mobile = attribute.Values[0]
|
||||
case "mobileTelephoneNumber":
|
||||
user.MobileTelephoneNumber = attribute.Values[0]
|
||||
case "registeredAddress":
|
||||
user.RegisteredAddress = attribute.Values[0]
|
||||
case "postalAddress":
|
||||
user.PostalAddress = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, user)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
// FIXME: The Base DN does not necessarily contain the Group
|
||||
//
|
||||
// func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
// }
|
||||
|
||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
res := make([]LdapRespUser, 0)
|
||||
for _, user := range users {
|
||||
res = append(res, LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
Uuid: user.Uuid,
|
||||
DisplayName: user.DisplayName,
|
||||
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||
Phone: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||
Address: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range respUsers {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := GetExistUuids(owner, uuids)
|
||||
|
||||
organization := getOrganization("admin", owner)
|
||||
ldap := GetLdap(ldapId)
|
||||
|
||||
var dc []string
|
||||
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
|
||||
if strings.Contains(basedn, "dc=") {
|
||||
dc = append(dc, basedn[3:])
|
||||
}
|
||||
}
|
||||
affiliation := strings.Join(dc, ".")
|
||||
|
||||
var ou []string
|
||||
for _, admin := range strings.Split(ldap.Username, ",") {
|
||||
if strings.Contains(admin, "ou=") {
|
||||
ou = append(ou, admin[3:])
|
||||
}
|
||||
}
|
||||
tag := strings.Join(ou, ".")
|
||||
|
||||
for _, respUser := range respUsers {
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
for _, existUuid := range existUuids {
|
||||
if respUser.Uuid == existUuid {
|
||||
existUsers = append(existUsers, respUser)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
newUser := &User{
|
||||
Owner: owner,
|
||||
Name: respUser.buildLdapUserName(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: respUser.buildLdapDisplayName(),
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: respUser.Email,
|
||||
Phone: respUser.Phone,
|
||||
Address: []string{respUser.Address},
|
||||
Affiliation: affiliation,
|
||||
Tag: tag,
|
||||
Score: beego.AppConfig.DefaultInt("initScore", 2000),
|
||||
Ldap: respUser.Uuid,
|
||||
}
|
||||
|
||||
affected := AddUser(newUser)
|
||||
if !affected {
|
||||
failedUsers = append(failedUsers, respUser)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func GetExistUuids(owner string, uuids []string) []string {
|
||||
var users []User
|
||||
var existUuids []string
|
||||
existUuidSet := make(map[string]struct{})
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(users) > 0 {
|
||||
for _, result := range users {
|
||||
existUuidSet[result.Ldap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for uuid := range existUuidSet {
|
||||
existUuids = append(existUuids, uuid)
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func (ldapUser *LdapRespUser) buildLdapUserName() string {
|
||||
user := User{}
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if user.Name == ldapUser.Uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return ldapUser.Uid
|
||||
}
|
||||
|
||||
func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
|
||||
if ldapUser.DisplayName != "" {
|
||||
return ldapUser.DisplayName
|
||||
}
|
||||
|
||||
return ldapUser.Cn
|
||||
}
|
||||
|
||||
func (ldap *Ldap) buildFilterString(user *User) string {
|
||||
if len(ldap.FilterFields) == 0 {
|
||||
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("(&%s(|", ldap.Filter)
|
||||
for _, field := range ldap.FilterFields {
|
||||
filter = fmt.Sprintf("%s(%s=%s)", filter, field, user.getFieldFromLdapAttribute(field))
|
||||
}
|
||||
filter = fmt.Sprintf("%s))", filter)
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
func (user *User) getFieldFromLdapAttribute(attribute string) string {
|
||||
switch attribute {
|
||||
case "uid":
|
||||
return user.Name
|
||||
case "mail":
|
||||
return user.Email
|
||||
case "mobile":
|
||||
return user.Phone
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
148
object/message.go
Normal file
148
object/message.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// 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) index" 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 GetChatMessages(chat string) []*Message {
|
||||
messages := []*Message{}
|
||||
err := adapter.Engine.Asc("created_time").Find(&messages, &Message{Chat: chat})
|
||||
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)
|
||||
}
|
@@ -18,6 +18,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@@ -43,6 +44,26 @@ type OidcDiscovery struct {
|
||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
func isIpAddress(host string) bool {
|
||||
// Attempt to split the host and port, ignoring the error
|
||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
// If an error occurs, it might be because there's no port
|
||||
// In that case, use the original host string
|
||||
hostWithoutPort = host
|
||||
}
|
||||
|
||||
// Attempt to parse the host as an IP address (both IPv4 and IPv6)
|
||||
ip := net.ParseIP(hostWithoutPort)
|
||||
if ip != nil {
|
||||
// The host is an IP address
|
||||
return true
|
||||
}
|
||||
|
||||
// The host is not an IP address
|
||||
return false
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
@@ -52,6 +73,8 @@ func getOriginFromHost(host string) (string, string) {
|
||||
protocol := "https://"
|
||||
if strings.HasPrefix(host, "localhost") {
|
||||
protocol = "http://"
|
||||
} else if isIpAddress(host) {
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
if host == "localhost:8000" {
|
||||
|
@@ -29,7 +29,10 @@ import (
|
||||
func getEnforcer(permission *Permission) *casbin.Enforcer {
|
||||
tableName := "permission_rule"
|
||||
if len(permission.Adapter) != 0 {
|
||||
tableName = permission.Adapter
|
||||
adapterObj := getCasbinAdapter(permission.Owner, permission.Adapter)
|
||||
if adapterObj != nil && adapterObj.Table != "" {
|
||||
tableName = adapterObj.Table
|
||||
}
|
||||
}
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
driverName := conf.GetConfigString("driverName")
|
||||
@@ -130,7 +133,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
for _, subUser := range roleObj.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, domain, role, "", "", permissionId})
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, role, "", "", "", permissionId})
|
||||
@@ -140,7 +143,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
for _, subRole := range roleObj.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, domain, role, "", "", permissionId})
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, role, "", "", "", permissionId})
|
||||
|
@@ -30,7 +30,10 @@ 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, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
|
@@ -221,6 +221,10 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
if provider.ClientSecret2 == "***" {
|
||||
session = session.Omit("client_secret2")
|
||||
}
|
||||
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := session.Update(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -230,6 +234,9 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) bool {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -256,7 +263,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)
|
||||
}
|
||||
|
@@ -23,29 +23,32 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
|
||||
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
|
||||
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
|
||||
sp, err := buildSp(provider, samlResponse, host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return assertionInfo.NameID, err
|
||||
}
|
||||
|
||||
func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method string, err error) {
|
||||
func GenerateSamlRequest(id, relayState, host, 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)
|
||||
}
|
||||
sp, err := buildSp(provider, "")
|
||||
|
||||
sp, err := buildSp(provider, "", host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -67,35 +70,22 @@ func GenerateSamlLoginUrl(id, relayState, lang string) (auth string, method stri
|
||||
return auth, method, nil
|
||||
}
|
||||
|
||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
func buildSp(provider *Provider, samlResponse string, host string) (*saml2.SAMLServiceProvider, error) {
|
||||
_, origin := getOriginFromHost(host)
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
certStore, err := buildSpCertificateStore(provider, samlResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certStore.Roots = append(certStore.Roots, idpCert)
|
||||
|
||||
sp := &saml2.SAMLServiceProvider{
|
||||
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
||||
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
||||
IDPCertificateStore: &certStore,
|
||||
SignAuthnRequests: false,
|
||||
IDPCertificateStore: &certStore,
|
||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||
}
|
||||
|
||||
if provider.Endpoint != "" {
|
||||
sp.IdentityProviderSSOURL = provider.Endpoint
|
||||
sp.IdentityProviderIssuer = provider.IssuerUrl
|
||||
@@ -104,10 +94,45 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
|
||||
sp.SignAuthnRequests = true
|
||||
sp.SPKeyStore = buildSpKeyStore()
|
||||
}
|
||||
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
||||
func buildSpCertificateStore(provider *Provider, samlResponse string) (dsig.MemoryX509CertificateStore, error) {
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = getCertificateFromSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return dsig.MemoryX509CertificateStore{}, err
|
||||
}
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{idpCert},
|
||||
}
|
||||
return certStore, nil
|
||||
}
|
||||
|
||||
func getCertificateFromSamlResponse(samlResponse string, providerType string) string {
|
||||
de, err := base64.StdEncoding.DecodeString(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -122,14 +147,3 @@ func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||
return res[1]
|
||||
}
|
||||
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore{
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
|
@@ -15,10 +15,12 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/xorm-io/core"
|
||||
@@ -48,6 +50,7 @@ type User struct {
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
@@ -57,7 +60,6 @@ type User struct {
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
@@ -449,7 +451,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
if len(columns) == 0 {
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
}
|
||||
@@ -513,7 +515,10 @@ func AddUser(user *User) bool {
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
updated := user.refreshAvatar()
|
||||
if updated && user.PermanentAvatar != "*" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||
}
|
||||
|
||||
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||
|
||||
@@ -693,3 +698,40 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func (user *User) refreshAvatar() bool {
|
||||
var err error
|
||||
var fileBuffer *bytes.Buffer
|
||||
var ext string
|
||||
|
||||
// Gravatar + Identicon
|
||||
if strings.Contains(user.Avatar, "Gravatar") && user.Email != "" {
|
||||
client := proxy.ProxyHttpClient
|
||||
has, err := hasGravatar(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
fileBuffer, ext, err = getGravatarFileBuffer(client, user.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer == nil && strings.Contains(user.Avatar, "Identicon") {
|
||||
fileBuffer, ext, err = getIdenticonFileBuffer(user.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if fileBuffer != nil {
|
||||
avatarUrl := getPermanentAvatarUrlFromBuffer(user.Owner, user.Name, fileBuffer, ext, true)
|
||||
user.Avatar = avatarUrl
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@@ -131,6 +131,12 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = userInfo.DisplayName
|
||||
}
|
||||
} else if user.DisplayName == "" {
|
||||
if userInfo.Username != "" {
|
||||
user.DisplayName = userInfo.Username
|
||||
} else {
|
||||
user.DisplayName = userInfo.Id
|
||||
}
|
||||
}
|
||||
if userInfo.Email != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
||||
|
@@ -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, appCertificate, 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
|
||||
}
|
@@ -189,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")
|
||||
|
@@ -30,3 +30,12 @@ func ContainsString(values []string, val string) bool {
|
||||
sort.Strings(values)
|
||||
return sort.SearchStrings(values, val) != len(values)
|
||||
}
|
||||
|
||||
func ReturnAnyNotEmpty(strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@@ -259,3 +259,11 @@ func maskString(str string) string {
|
||||
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// GetEndPoint remove scheme from url
|
||||
func GetEndPoint(endpoint string) string {
|
||||
for _, prefix := range []string{"https://", "http://"} {
|
||||
endpoint = strings.TrimPrefix(endpoint, prefix)
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^1.5.6",
|
||||
"@ant-design/cssinjs": "^1.8.1",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@crowdin/cli": "^3.7.10",
|
||||
@@ -12,7 +12,7 @@
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"antd": "5.1.6",
|
||||
"antd": "5.2.3",
|
||||
"antd-token-previewer": "^1.1.0-22",
|
||||
"codemirror": "^5.61.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
|
@@ -112,6 +112,7 @@ class AdapterEditPage extends React.Component {
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
|
||||
this.getModels(value);
|
||||
this.updateAdapterField("organization", value);
|
||||
this.updateAdapterField("owner", value);
|
||||
})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
@@ -266,7 +267,7 @@ class AdapterEditPage extends React.Component {
|
||||
|
||||
submitAdapterEdit(willExist) {
|
||||
const adapter = Setting.deepCopy(this.state.adapter);
|
||||
AdapterBackend.updateAdapter(this.state.adapter.owner, this.state.adapterName, adapter)
|
||||
AdapterBackend.updateAdapter(this.state.owner, this.state.adapterName, adapter)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
|
@@ -17,7 +17,7 @@ import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
@@ -44,6 +44,11 @@ import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import ChatListPage from "./ChatListPage";
|
||||
import ChatEditPage from "./ChatEditPage";
|
||||
import ChatPage from "./ChatPage";
|
||||
import MessageEditPage from "./MessageEditPage";
|
||||
import MessageListPage from "./MessageListPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
@@ -147,6 +152,10 @@ class App extends Component {
|
||||
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")) {
|
||||
@@ -317,12 +326,17 @@ class App extends Component {
|
||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||
"/account"
|
||||
));
|
||||
items.push(Setting.getItem(<><CommentOutlined /> {i18next.t("account:Chats & Messages")}</>,
|
||||
"/chat"
|
||||
));
|
||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||
"/logout"));
|
||||
|
||||
const onClick = (e) => {
|
||||
if (e.key === "/account") {
|
||||
this.props.history.push("/account");
|
||||
} else if (e.key === "/chat") {
|
||||
this.props.history.push("/chat");
|
||||
} else if (e.key === "/logout") {
|
||||
this.logout();
|
||||
}
|
||||
@@ -415,6 +429,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"
|
||||
));
|
||||
@@ -529,6 +551,11 @@ 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="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage 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} />)} />
|
||||
@@ -602,7 +629,7 @@ class App extends Component {
|
||||
}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{Setting.isMobile() ?
|
||||
{(Setting.isMobile() || window.location.pathname === "/chat") ?
|
||||
this.renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{this.renderRouter()}
|
||||
|
190
web/src/ChatBox.js
Normal file
190
web/src/ChatBox.js
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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 {Avatar, Input, List} from "antd";
|
||||
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
class ChatBox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputValue: "",
|
||||
};
|
||||
|
||||
this.listContainerRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.messages !== this.props.messages) {
|
||||
this.scrollToListItem(this.props.messages.length);
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.state.inputValue !== "") {
|
||||
this.send(this.state.inputValue);
|
||||
this.setState({inputValue: ""});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scrollToListItem(index) {
|
||||
const listContainerElement = this.listContainerRef.current;
|
||||
|
||||
if (!listContainerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetItem = listContainerElement.querySelector(
|
||||
`#chatbox-list-item-${index}`
|
||||
);
|
||||
|
||||
if (!targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollDistance = targetItem.offsetTop - listContainerElement.offsetTop;
|
||||
|
||||
listContainerElement.scrollTo({
|
||||
top: scrollDistance,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
send = (text) => {
|
||||
this.props.sendMessage(text);
|
||||
this.setState({inputValue: ""});
|
||||
};
|
||||
|
||||
renderList() {
|
||||
return (
|
||||
<div ref={this.listContainerRef} style={{position: "relative", maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={this.props.messages === undefined ? undefined : [...this.props.messages, {}]}
|
||||
renderItem={(item, index) => {
|
||||
if (Object.keys(item).length === 0 && item.constructor === Object) {
|
||||
return <List.Item id={`chatbox-list-item-${index}`} style={{
|
||||
height: "160px",
|
||||
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||
position: "relative",
|
||||
}} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Item id={`chatbox-list-item-${index}`} style={{
|
||||
backgroundColor: index % 2 === 0 ? "white" : "rgb(247,247,248)",
|
||||
borderBottom: "1px solid rgb(229, 229, 229)",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div style={{width: "800px", margin: "0 auto", position: "relative"}}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar style={{width: "30px", height: "30px", borderRadius: "3px"}} src={item.author === `${this.props.account.owner}/${this.props.account.name}` ? this.props.account.avatar : "https://cdn.casbin.com/casdoor/resource/built-in/admin/gpt.png"} />}
|
||||
title={<div style={{fontSize: "16px", fontWeight: "normal", lineHeight: "24px", marginTop: "-15px", marginLeft: "5px", marginRight: "80px"}}>{item.text}</div>}
|
||||
/>
|
||||
<div style={{position: "absolute", top: "0px", right: "0px"}}
|
||||
>
|
||||
<CopyOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
<LikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
<DislikeOutlined style={{color: "rgb(172,172,190)", margin: "5px"}} />
|
||||
</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: "120px",
|
||||
background: "linear-gradient(transparent 0%, rgba(255, 255, 255, 0.8) 50%, white 100%)",
|
||||
pointerEvents: "none",
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInput() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
bottom: "90px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{position: "relative", width: "760px", marginLeft: "-280px"}}>
|
||||
<TextArea
|
||||
placeholder={"Send a message..."}
|
||||
autoSize={{maxRows: 8}}
|
||||
value={this.state.inputValue}
|
||||
onChange={(e) => this.setState({inputValue: e.target.value})}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "normal",
|
||||
lineHeight: "24px",
|
||||
width: "770px",
|
||||
height: "48px",
|
||||
borderRadius: "6px",
|
||||
borderColor: "rgb(229,229,229)",
|
||||
boxShadow: "0 0 15px rgba(0, 0, 0, 0.1)",
|
||||
paddingLeft: "17px",
|
||||
paddingRight: "17px",
|
||||
paddingTop: "12px",
|
||||
paddingBottom: "12px",
|
||||
}}
|
||||
suffix={<SendOutlined style={{color: "rgb(210,210,217"}} onClick={() => this.send(this.state.inputValue)} />}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<SendOutlined
|
||||
style={{
|
||||
color: this.state.inputValue === "" ? "rgb(210,210,217)" : "rgb(142,142,160)",
|
||||
position: "absolute",
|
||||
bottom: "17px",
|
||||
right: "17px",
|
||||
}}
|
||||
onClick={() => this.send(this.state.inputValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderList()
|
||||
}
|
||||
{
|
||||
this.renderInput()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatBox;
|
243
web/src/ChatEditPage.js
Normal file
243
web/src/ChatEditPage.js
Normal file
@@ -0,0 +1,243 @@
|
||||
// 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("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.type} onChange={(value => {
|
||||
this.updateChatField("type", value);
|
||||
})}
|
||||
options={[
|
||||
{value: "Single", name: i18next.t("chat:Single")},
|
||||
{value: "Group", name: i18next.t("chat:Group")},
|
||||
{value: "AI", name: i18next.t("chat:AI")},
|
||||
].map((item) => Setting.getOption(item.name, item.value))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.chat.category} onChange={e => {
|
||||
this.updateChatField("category", 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("chat: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("chat: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("general:Users"), i18next.t("chat: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;
|
294
web/src/ChatListPage.js
Normal file
294
web/src/ChatListPage.js
Normal file
@@ -0,0 +1,294 @@
|
||||
// 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}`,
|
||||
type: "Single",
|
||||
category: "Chat Category - 1",
|
||||
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("provider:Type"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: "Single", value: "Single"},
|
||||
{text: "Group", value: "Group"},
|
||||
{text: "AI", value: "AI"},
|
||||
],
|
||||
render: (text, record, index) => {
|
||||
return i18next.t(`chat:${text}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Category"),
|
||||
dataIndex: "category",
|
||||
key: "category",
|
||||
// width: '100px',
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("category"),
|
||||
},
|
||||
{
|
||||
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;
|
100
web/src/ChatMenu.js
Normal file
100
web/src/ChatMenu.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 {Menu} from "antd";
|
||||
import {LayoutOutlined} from "@ant-design/icons";
|
||||
|
||||
class ChatMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
const openKeys = items.map((item) => item.key);
|
||||
|
||||
this.state = {
|
||||
openKeys: openKeys,
|
||||
selectedKeys: ["0-0"],
|
||||
};
|
||||
}
|
||||
|
||||
chatsToItems(chats) {
|
||||
const categories = {};
|
||||
chats.forEach((chat) => {
|
||||
if (!categories[chat.category]) {
|
||||
categories[chat.category] = [];
|
||||
}
|
||||
categories[chat.category].push(chat);
|
||||
});
|
||||
|
||||
return Object.keys(categories).map((category, index) => {
|
||||
return {
|
||||
key: `${index}`,
|
||||
icon: <LayoutOutlined />,
|
||||
label: category,
|
||||
children: categories[category].map((chat, chatIndex) => {
|
||||
const globalChatIndex = chats.indexOf(chat);
|
||||
return {
|
||||
key: `${index}-${chatIndex}`,
|
||||
index: globalChatIndex,
|
||||
label: chat.displayName,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onSelect = (info) => {
|
||||
const [categoryIndex, chatIndex] = info.selectedKeys[0].split("-").map(Number);
|
||||
const selectedItem = this.chatsToItems(this.props.chats)[categoryIndex].children[chatIndex];
|
||||
this.setState({selectedKeys: [`${categoryIndex}-${chatIndex}`]});
|
||||
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(selectedItem.index);
|
||||
}
|
||||
};
|
||||
|
||||
getRootSubmenuKeys(items) {
|
||||
return items.map((item, index) => `${index}`);
|
||||
}
|
||||
|
||||
onOpenChange = (keys) => {
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
const rootSubmenuKeys = this.getRootSubmenuKeys(items);
|
||||
const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
|
||||
|
||||
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
|
||||
this.setState({openKeys: keys});
|
||||
} else {
|
||||
this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const items = this.chatsToItems(this.props.chats);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
mode="inline"
|
||||
openKeys={this.state.openKeys}
|
||||
selectedKeys={this.state.selectedKeys}
|
||||
onOpenChange={this.onOpenChange}
|
||||
onSelect={this.onSelect}
|
||||
items={items}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatMenu;
|
199
web/src/ChatPage.js
Normal file
199
web/src/ChatPage.js
Normal file
@@ -0,0 +1,199 @@
|
||||
// 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 {Spin} from "antd";
|
||||
import moment from "moment";
|
||||
import ChatMenu from "./ChatMenu";
|
||||
import ChatBox from "./ChatBox";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ChatBackend from "./backend/ChatBackend";
|
||||
import * as MessageBackend from "./backend/MessageBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ChatPage 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}`,
|
||||
type: "Single",
|
||||
category: "Chat Category - 1",
|
||||
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}`);
|
||||
// });
|
||||
// }
|
||||
|
||||
newMessage(text) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.messagename,
|
||||
name: `message_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
organization: this.props.account.owner,
|
||||
chat: this.state.chatName,
|
||||
author: `${this.props.account.owner}/${this.props.account.name}`,
|
||||
text: text,
|
||||
};
|
||||
}
|
||||
|
||||
sendMessage(text) {
|
||||
const newMessage = this.newMessage(text);
|
||||
MessageBackend.addMessage(newMessage)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.getMessages(this.state.chatName);
|
||||
} 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}`);
|
||||
});
|
||||
}
|
||||
|
||||
getMessages(chatName) {
|
||||
MessageBackend.getChatMessages(chatName)
|
||||
.then((messages) => {
|
||||
this.setState({
|
||||
messages: messages,
|
||||
});
|
||||
|
||||
Setting.scrollToDiv(`chatbox-list-item-${messages.length}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(chats) {
|
||||
return (this.state.loading) ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
|
||||
(
|
||||
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
|
||||
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)"}}>
|
||||
<ChatMenu chats={chats} onSelect={(i) => {
|
||||
const chat = chats[i];
|
||||
this.getMessages(chat.name);
|
||||
this.setState({
|
||||
chatName: chat.name,
|
||||
});
|
||||
}} />
|
||||
</div>
|
||||
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundImage: "url(https://cdn.casbin.org/img/casdoor-logo_1185x256.png)",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundSize: "200px auto",
|
||||
backgroundBlendMode: "luminosity",
|
||||
filter: "grayscale(80%) brightness(140%) contrast(90%)",
|
||||
opacity: 0.5,
|
||||
}}>
|
||||
</div>
|
||||
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
|
||||
</div>
|
||||
</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,
|
||||
});
|
||||
|
||||
const chats = res.data;
|
||||
if (this.state.chatName === undefined && chats.length > 0) {
|
||||
const chat = chats[0];
|
||||
this.getMessages(chat.name);
|
||||
this.setState({
|
||||
chatName: chat.name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isAuthorized: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default ChatPage;
|
@@ -69,7 +69,7 @@ class EntryPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||
<Spin size="large" spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
@@ -84,7 +84,7 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
@@ -166,13 +166,37 @@ class LdapEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Search Filter"), i18next.t("ldap:Search Filter - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.filter} onChange={e => {
|
||||
this.updateLdapField("filter", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Filter fields"), i18next.t("ldap:Filter fields - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Select value={this.state.ldap.filterFields ?? []} style={{width: "100%"}} mode={"multiple"} options={[
|
||||
{value: "uid", label: "uid"},
|
||||
{value: "mail", label: "Email"},
|
||||
{value: "mobile", label: "mobile"},
|
||||
].map((item) => Setting.getOption(item.label, item.value))} onChange={value => {
|
||||
this.updateLdapField("filterFields", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||
{Setting.getLabel(i18next.t("ldap:Admin"), i18next.t("ldap:Admin - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input value={this.state.ldap.admin} onChange={e => {
|
||||
this.updateLdapField("admin", e.target.value);
|
||||
<Input value={this.state.ldap.username} onChange={e => {
|
||||
this.updateLdapField("username", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -182,9 +206,9 @@ class LdapEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={21}>
|
||||
<Input.Password
|
||||
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.passwd}
|
||||
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} value={this.state.ldap.password}
|
||||
onChange={e => {
|
||||
this.updateLdapField("passwd", e.target.value);
|
||||
this.updateLdapField("password", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
|
@@ -1,192 +0,0 @@
|
||||
// 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 {Link} from "react-router-dom";
|
||||
import {Button, Col, Row, Table} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import i18next from "i18next";
|
||||
import PopconfirmModal from "./PopconfirmModal";
|
||||
|
||||
class LdapListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
ldaps: null,
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getLdaps();
|
||||
}
|
||||
|
||||
getLdaps() {
|
||||
LdapBackend.getLdaps("")
|
||||
.then((res) => {
|
||||
let ldapsData = [];
|
||||
if (res.status === "ok") {
|
||||
ldapsData = res.data;
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
this.setState((prevState) => {
|
||||
prevState.ldaps = ldapsData;
|
||||
return prevState;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteLdap(index) {
|
||||
|
||||
}
|
||||
|
||||
renderTable(ldaps) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("ldap:Server name"),
|
||||
dataIndex: "serverName",
|
||||
key: "serverName",
|
||||
width: "200px",
|
||||
sorter: (a, b) => a.serverName.localeCompare(b.serverName),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/ldaps/${record.id}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.owner.localeCompare(b.owner),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Server"),
|
||||
dataIndex: "host",
|
||||
key: "host",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.host.localeCompare(b.host),
|
||||
render: (text, record, index) => {
|
||||
return `${text}:${record.port}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Base DN"),
|
||||
dataIndex: "baseDn",
|
||||
key: "baseDn",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.baseDn.localeCompare(b.baseDn),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Admin"),
|
||||
dataIndex: "admin",
|
||||
key: "admin",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.admin.localeCompare(b.admin),
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Auto Sync"),
|
||||
dataIndex: "autoSync",
|
||||
key: "autoSync",
|
||||
width: "100px",
|
||||
sorter: (a, b) => a.autoSync.localeCompare(b.autoSync),
|
||||
render: (text, record, index) => {
|
||||
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
|
||||
<span style={{color: "#52c41a"}}>{text + " mins"}</span>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Last Sync"),
|
||||
dataIndex: "lastSync",
|
||||
key: "lastSync",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
|
||||
render: (text, record, index) => {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "240px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
type="primary"
|
||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("general:Sync")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
||||
onConfirm={() => this.deleteLdap(index)}
|
||||
>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table columns={columns} dataSource={ldaps} rowKey="id" size="middle" bordered
|
||||
pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
<span>{i18next.t("general:LDAPs")}</span>
|
||||
<Button type="primary" size="small" style={{marginLeft: "10px"}}
|
||||
onClick={() => {
|
||||
this.addLdap();
|
||||
}}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={ldaps === null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%"}}>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.ldaps)
|
||||
}
|
||||
</Col>
|
||||
<Col span={1}>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LdapListPage;
|
@@ -13,10 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
||||
import {Button, Popconfirm, Table} from "antd";
|
||||
import * as Setting from "./Setting";
|
||||
import * as LdapBackend from "./backend/LdapBackend";
|
||||
import i18next from "i18next";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
class LdapSyncPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -77,9 +78,8 @@ class LdapSyncPage extends React.Component {
|
||||
LdapBackend.getLdap(this.state.organizationName, this.state.ldapId)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState((prevState) => {
|
||||
prevState.ldap = res.data;
|
||||
return prevState;
|
||||
this.setState({
|
||||
ldap: res.data,
|
||||
});
|
||||
this.getLdapUser();
|
||||
} else {
|
||||
@@ -139,22 +139,46 @@ class LdapSyncPage extends React.Component {
|
||||
dataIndex: "cn",
|
||||
key: "cn",
|
||||
sorter: (a, b) => a.cn.localeCompare(b.cn),
|
||||
render: (text, record, index) => {
|
||||
return (<div style={{display: "flex", justifyContent: "space-between"}}>
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
{this.state.existUuids.includes(record.uuid) ?
|
||||
Setting.getTag("green", i18next.t("ldap:synced")) :
|
||||
Setting.getTag("red", i18next.t("ldap:unsynced"))
|
||||
}
|
||||
</div>);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:UidNumber / Uid"),
|
||||
title: "Uid",
|
||||
dataIndex: "uid",
|
||||
key: "uid",
|
||||
sorter: (a, b) => a.uid.localeCompare(b.uid),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
this.state.existUuids.includes(record.uuid) ?
|
||||
<Link to={`/users/${this.state.organizationName}/${text}`}>
|
||||
{text}
|
||||
</Link> :
|
||||
text
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "UidNumber",
|
||||
dataIndex: "uidNumber",
|
||||
key: "uidNumber",
|
||||
width: "200px",
|
||||
sorter: (a, b) => a.uidNumber.localeCompare(b.uidNumber),
|
||||
render: (text, record, index) => {
|
||||
return `${text} / ${record.uid}`;
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("ldap:Group ID"),
|
||||
dataIndex: "groupId",
|
||||
key: "groupId",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.groupId.localeCompare(b.groupId),
|
||||
filters: this.buildFilter(this.state.users, "groupId"),
|
||||
onFilter: (value, record) => record.groupId.indexOf(value) === 0,
|
||||
@@ -163,14 +187,12 @@ class LdapSyncPage extends React.Component {
|
||||
title: i18next.t("general:Email"),
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
width: "240px",
|
||||
sorter: (a, b) => a.email.localeCompare(b.email),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Phone"),
|
||||
dataIndex: "phone",
|
||||
key: "phone",
|
||||
width: "160px",
|
||||
sorter: (a, b) => a.phone.localeCompare(b.phone),
|
||||
},
|
||||
{
|
||||
@@ -183,9 +205,8 @@ class LdapSyncPage extends React.Component {
|
||||
|
||||
const rowSelection = {
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
this.setState(prevState => {
|
||||
prevState.selectedUsers = selectedRows;
|
||||
return prevState;
|
||||
this.setState({
|
||||
selectedUsers: selectedRows,
|
||||
});
|
||||
},
|
||||
getCheckboxProps: record => ({
|
||||
@@ -194,42 +215,36 @@ class LdapSyncPage extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered
|
||||
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
||||
title={() => (
|
||||
<div>
|
||||
<span>{this.state.ldap?.serverName}</span>
|
||||
<Popconfirm placement={"right"}
|
||||
title={"Please confirm to sync selected users"}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" style={{marginLeft: "10px"}}>
|
||||
{i18next.t("general:Sync")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("general:Edit")} LDAP
|
||||
<Table rowSelection={rowSelection} columns={columns} dataSource={users} rowKey="uuid" bordered size="small"
|
||||
pagination={{defaultPageSize: 10, showQuickJumper: true, showSizeChanger: true}}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.state.ldap?.serverName}
|
||||
<Popconfirm placement={"right"} disabled={this.state.selectedUsers.length === 0}
|
||||
title={"Please confirm to sync selected users"}
|
||||
onConfirm={() => this.syncUsers()}
|
||||
>
|
||||
<Button type="primary" style={{marginLeft: "10px"}} disabled={this.state.selectedUsers.length === 0}>
|
||||
{i18next.t("general:Sync")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
/>
|
||||
</div>
|
||||
</Popconfirm>
|
||||
<Button style={{marginLeft: "20px"}}
|
||||
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||
{i18next.t("general:Edit")} LDAP
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={users === null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{width: "100%", justifyContent: "center"}}>
|
||||
<Col span={22}>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.renderTable(this.state.users)
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||
|
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("message: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;
|
@@ -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");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -352,7 +352,7 @@ class ProviderEditPage extends React.Component {
|
||||
[
|
||||
{id: "Normal", name: i18next.t("provider:Normal")},
|
||||
{id: "Silent", name: i18next.t("provider:Silent")},
|
||||
].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
@@ -461,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 => {
|
||||
@@ -475,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>
|
||||
)
|
||||
}
|
||||
@@ -815,6 +821,20 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
this.state.provider.type === "WeChat Pay" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel("cert", "cert")} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.cert} onChange={e => {
|
||||
this.updateProviderField("cert", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{this.getAppIdRow(this.state.provider)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
|
@@ -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");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -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: "vi", country: "VN", alt: "TiếngViệt"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
@@ -1070,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;
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Result, Row, Select, Spin, Switch} from "antd";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@@ -110,9 +110,9 @@ class UserEditPage extends React.Component {
|
||||
}
|
||||
|
||||
parseUserField(key, value) {
|
||||
// if ([].includes(key)) {
|
||||
// value = Setting.myParseInt(value);
|
||||
// }
|
||||
if (["score", "karma", "ranking"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -360,6 +360,19 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Address") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Address"), i18next.t("user:Address - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.address} onChange={e => {
|
||||
this.updateUserField("address", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Affiliation") {
|
||||
return (
|
||||
(this.state.application === null || this.state.user === null) ? null : (
|
||||
@@ -379,6 +392,32 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "ID card type") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:ID card type"), i18next.t("user:ID card type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.idCardType} onChange={e => {
|
||||
this.updateUserField("idCardType", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "ID card") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:ID card"), i18next.t("user:ID card - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.idCard} onChange={e => {
|
||||
this.updateUserField("idCard", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Homepage") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@@ -431,6 +470,97 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Language") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Language"), i18next.t("user:Language - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.language} onChange={e => {
|
||||
this.updateUserField("language", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Gender") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Gender"), i18next.t("user:Gender - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.gender} onChange={e => {
|
||||
this.updateUserField("gender", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Birthday") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Birthday"), i18next.t("user:Birthday - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.birthday} onChange={e => {
|
||||
this.updateUserField("birthday", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Education") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Education"), i18next.t("user:Education - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.education} onChange={e => {
|
||||
this.updateUserField("education", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Score") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Score"), i18next.t("user:Score - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.score} onChange={value => {
|
||||
this.updateUserField("score", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Karma") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Karma"), i18next.t("user:Karma - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.karma} onChange={value => {
|
||||
this.updateUserField("karma", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Ranking") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Ranking"), i18next.t("user:Ranking - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.user.ranking} onChange={value => {
|
||||
this.updateUserField("ranking", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Signup application") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@@ -787,11 +787,11 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
|
||||
const visibleOAuthProviderItems = application.providers.filter(providerItem => this.isProviderVisible(providerItem));
|
||||
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
if (this.props.preview !== "auto" && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
|
||||
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Signing in...")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ class ResultPage extends React.Component {
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(application);
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
}}>
|
||||
{i18next.t("login:Sign In")}
|
||||
|
@@ -408,24 +408,27 @@ class SignupPage extends React.Component {
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("code:Please input your phone verification code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={!this.state.validPhone}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
countryCode={this.form.current?.getFieldValue("countryCode")}
|
||||
/>
|
||||
</Form.Item>
|
||||
{
|
||||
signupItem.rule !== "No verification" &&
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
label={i18next.t("code:Phone code")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("code:Please input your phone verification code!"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<SendCodeInput
|
||||
disabled={!this.state.validPhone}
|
||||
method={"signup"}
|
||||
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
|
||||
application={application}
|
||||
countryCode={this.form.current?.getFieldValue("countryCode")}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else if (signupItem.name === "Password") {
|
||||
|
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());
|
||||
}
|
81
web/src/backend/MessageBackend.js
Normal file
81
web/src/backend/MessageBackend.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 getChatMessages(chat) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-messages?chat=${chat}`, {
|
||||
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());
|
||||
}
|
@@ -71,8 +71,12 @@ export function deleteUserWebAuthnCredential(credentialID) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
// Base64 to ArrayBuffer
|
||||
// Base64URL to ArrayBuffer
|
||||
export function webAuthnBufferDecode(value) {
|
||||
value = value.replace(/-/g, "+").replace(/_/g, "/");
|
||||
while (value.length % 4) {
|
||||
value += "=";
|
||||
}
|
||||
return Uint8Array.from(atob(value), c => c.charCodeAt(0));
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ export const AgreementModal = (props) => {
|
||||
const [doc, setDoc] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getTermsOfUseContent(application.termsOfUseUrl).then((data) => {
|
||||
getTermsOfUseContent(application.termsOfUse).then((data) => {
|
||||
setDoc(data);
|
||||
});
|
||||
}, []);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Abmelden",
|
||||
"My Account": "Mein Konto",
|
||||
"Sign Up": "Anmelden"
|
||||
@@ -41,6 +42,7 @@
|
||||
"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",
|
||||
"First, last": "First, last",
|
||||
"Follow organization theme": "Folge dem Theme der Organisation",
|
||||
"Form CSS": "Form CSS",
|
||||
"Form CSS - Edit": "Form CSS - Bearbeiten",
|
||||
@@ -49,15 +51,21 @@
|
||||
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
|
||||
"Grant types": "Grant-Typen",
|
||||
"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",
|
||||
"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 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",
|
||||
@@ -74,6 +82,8 @@
|
||||
"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": "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",
|
||||
@@ -108,6 +118,19 @@
|
||||
"Scope - Tooltip": "Nutzungsszenarien des Zertifikats",
|
||||
"Type - Tooltip": "Art des Zertifikats"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Der Code, den Sie erhalten haben",
|
||||
"Email code": "E-Mail-Code",
|
||||
@@ -150,6 +173,7 @@
|
||||
"Cert": "Zertifikat",
|
||||
"Cert - Tooltip": "Das Public-Key-Zertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss",
|
||||
"Certs": "Zertifikate",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Klicken Sie zum Hochladen",
|
||||
"Client IP": "Client-IP",
|
||||
"Close": "Schließen",
|
||||
@@ -194,6 +218,7 @@
|
||||
"Master password": "Hauptpasswort",
|
||||
"Master password - Tooltip": "Kann zum Einloggen aller Benutzer unter dieser Organisation verwendet werden, was es Administratoren bequem macht, sich als dieser Benutzer einzuloggen, um technische Probleme zu lösen",
|
||||
"Menu": "Menü",
|
||||
"Messages": "Messages",
|
||||
"Method": "Methode",
|
||||
"Model": "Modell",
|
||||
"Model - Tooltip": "Casbin-Zugriffskontrollmodell",
|
||||
@@ -260,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL-Link",
|
||||
"Up": "Oben",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Nutzer",
|
||||
"User - Tooltip": "Stellen Sie sicher, dass der Benutzername korrekt ist",
|
||||
"User containers": "Nutzerpools",
|
||||
@@ -284,8 +310,12 @@
|
||||
"Edit LDAP": "LDAP bearbeiten",
|
||||
"Enable SSL": "Aktivieren Sie SSL",
|
||||
"Enable SSL - Tooltip": "Ob SSL aktiviert werden soll",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Gruppen-ID",
|
||||
"Last Sync": "Letzte Synchronisation",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Server",
|
||||
"Server host": "Server Host",
|
||||
"Server host - Tooltip": "LDAP-Server-Adresse",
|
||||
@@ -294,7 +324,8 @@
|
||||
"Server port": "Server-Port",
|
||||
"Server port - Tooltip": "LDAP-Server-Port",
|
||||
"The Auto Sync option will sync all users to specify organization": "Die Option \"Auto Sync\" synchronisiert alle Benutzer mit der angegebenen Organisation",
|
||||
"UidNumber / Uid": "UidNumber / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Automatische Anmeldung",
|
||||
@@ -322,6 +353,16 @@
|
||||
"sign up now": "Melde dich jetzt an",
|
||||
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Modell bearbeiten",
|
||||
"Model text": "Modelltext",
|
||||
@@ -504,6 +545,8 @@
|
||||
"Host - Tooltip": "Name des Hosts",
|
||||
"IdP": "IdP",
|
||||
"IdP certificate": "IdP-Zertifikat",
|
||||
"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",
|
||||
@@ -511,6 +554,7 @@
|
||||
"Metadata - Tooltip": "SAML-Metadaten",
|
||||
"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",
|
||||
@@ -555,8 +599,10 @@
|
||||
"Signup HTML": "Registrierungs-HTML",
|
||||
"Signup HTML - Edit": "Registrierung HTML - Bearbeiten",
|
||||
"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": "Template-Code",
|
||||
@@ -564,6 +610,7 @@
|
||||
"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",
|
||||
@@ -621,6 +668,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"
|
||||
@@ -693,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Arbeitgeber, wie Firmenname oder Organisationsname",
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Selbstvorstellung des Nutzers",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Captcha-Überprüfung fehlgeschlagen",
|
||||
"Captcha Verify Success": "Captcha-Verifizierung Erfolgreich",
|
||||
"Country code": "Ländercode",
|
||||
"Country/Region": "Land/Region",
|
||||
"Country/Region - Tooltip": "Land oder Region",
|
||||
"Edit User": "Benutzer bearbeiten",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "E-Mail darf nicht leer sein",
|
||||
"Email/phone reset successfully": "E-Mail-/Telefon-Zurücksetzung erfolgreich durchgeführt",
|
||||
"Empty input!": "Leere Eingabe!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Startseite des Benutzers",
|
||||
"Homepage - Tooltip": "Homepage-URL des Benutzers",
|
||||
"ID card": "Ausweis",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"Input your phone number": "Geben Sie Ihre Telefonnummer ein",
|
||||
"Is admin": "Ist Admin",
|
||||
@@ -715,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Verbotene Benutzer können sich nicht mehr einloggen",
|
||||
"Is global admin": "Ist globaler Administrator",
|
||||
"Is global admin - Tooltip": "Ist ein Administrator von Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Keys",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Link",
|
||||
"Location": "Ort",
|
||||
"Location - Tooltip": "Stadt des Wohnsitzes",
|
||||
@@ -731,9 +793,13 @@
|
||||
"Please select avatar from resources": "Bitte wählen Sie einen Avatar aus den Ressourcen aus",
|
||||
"Properties": "Eigenschaften",
|
||||
"Properties - Tooltip": "Eigenschaften des Benutzers",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Neueingabe wiederholen",
|
||||
"Reset Email...": "E-Mail zurücksetzen...",
|
||||
"Reset Phone...": "Telefon zurücksetzen...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Wählen Sie ein Foto aus...",
|
||||
"Set Password": "Passwort festlegen",
|
||||
"Set new profile picture": "Neues Profilbild festlegen",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Logout",
|
||||
"My Account": "My Account",
|
||||
"Sign Up": "Sign Up"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Usage scenarios of the certificate",
|
||||
"Type - Tooltip": "Type of certificate"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Code you received",
|
||||
"Email code": "Email code",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "Cert",
|
||||
"Cert - Tooltip": "The public key certificate that needs to be verified by the client SDK corresponding to this application",
|
||||
"Certs": "Certs",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Click to Upload",
|
||||
"Client IP": "Client IP",
|
||||
"Close": "Close",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Master password",
|
||||
"Master password - Tooltip": "Can be used to log in to all users under this organization, making it convenient for administrators to log in as this user to solve technical issues",
|
||||
"Menu": "Menu",
|
||||
"Messages": "Messages",
|
||||
"Method": "Method",
|
||||
"Model": "Model",
|
||||
"Model - Tooltip": "Casbin access control model",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL link",
|
||||
"Up": "Up",
|
||||
"Updated time": "Updated time",
|
||||
"User": "User",
|
||||
"User - Tooltip": "Make sure the username is correct",
|
||||
"User containers": "User pools",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Edit LDAP",
|
||||
"Enable SSL": "Enable SSL",
|
||||
"Enable SSL - Tooltip": "Whether to enable SSL",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Group ID",
|
||||
"Last Sync": "Last Sync",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Server",
|
||||
"Server host": "Server host",
|
||||
"Server host - Tooltip": "LDAP server address",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Server port",
|
||||
"Server port - Tooltip": "LDAP server port",
|
||||
"The Auto Sync option will sync all users to specify organization": "The Auto Sync option will sync all users to specify organization",
|
||||
"UidNumber / Uid": "UidNumber / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Auto sign in",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "sign up now",
|
||||
"username, Email or phone": "username, Email or phone"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Edit Model",
|
||||
"Model text": "Model text",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Employer, such as company name or organization name",
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Self introduction of the user",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Country/Region",
|
||||
"Country/Region - Tooltip": "Country or region",
|
||||
"Edit User": "Edit User",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "Email cannot be empty",
|
||||
"Email/phone reset successfully": "Email/phone reset successfully",
|
||||
"Empty input!": "Empty input!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "Homepage URL of the user",
|
||||
"ID card": "ID card",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Input your email",
|
||||
"Input your phone number": "Input your phone number",
|
||||
"Is admin": "Is admin",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Forbidden users cannot log in any more",
|
||||
"Is global admin": "Is global admin",
|
||||
"Is global admin - Tooltip": "Is an administrator of Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Keys",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Link",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "City of residence",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Properties",
|
||||
"Properties - Tooltip": "Properties of the user",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Re-enter New",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Reset Phone...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Select a photo...",
|
||||
"Set Password": "Set Password",
|
||||
"Set new profile picture": "Set new profile picture",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Cierre de sesión",
|
||||
"My Account": "Mi cuenta",
|
||||
"Sign Up": "Registrarse"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Escenarios de uso del certificado",
|
||||
"Type - Tooltip": "Tipo de certificado"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Código que recibió",
|
||||
"Email code": "Código de correo electrónico",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "ificado",
|
||||
"Cert - Tooltip": "El certificado de clave pública que necesita ser verificado por el SDK del cliente correspondiente a esta aplicación",
|
||||
"Certs": "Certificaciones",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Haz clic para cargar",
|
||||
"Client IP": "Dirección IP del cliente",
|
||||
"Close": "Cerca",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Contraseña maestra",
|
||||
"Master password - Tooltip": "Se puede usar para iniciar sesión en todos los usuarios de esta organización, lo que hace conveniente que los administradores inicien sesión como este usuario para resolver problemas técnicos",
|
||||
"Menu": "Menú",
|
||||
"Messages": "Messages",
|
||||
"Method": "Método",
|
||||
"Model": "Modelo",
|
||||
"Model - Tooltip": "Modelo de control de acceso Casbin",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "Dirección URL",
|
||||
"URL - Tooltip": "Enlace de URL",
|
||||
"Up": "Arriba",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Usuario",
|
||||
"User - Tooltip": "Asegúrate de que el nombre de usuario sea correcto",
|
||||
"User containers": "Piscinas de usuarios",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Editar LDAP",
|
||||
"Enable SSL": "Habilitar SSL",
|
||||
"Enable SSL - Tooltip": "Si se habilita SSL",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Identificador de grupo",
|
||||
"Last Sync": "Última sincronización",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Servidor",
|
||||
"Server host": "Anfitrión del servidor",
|
||||
"Server host - Tooltip": "Dirección del servidor LDAP",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Puerto del servidor",
|
||||
"Server port - Tooltip": "Puerto del servidor LDAP",
|
||||
"The Auto Sync option will sync all users to specify organization": "La opción Auto Sync sincronizará a todos los usuarios con la organización especificada",
|
||||
"UidNumber / Uid": "UidNumber / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Inicio de sesión automático",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "Regístrate ahora",
|
||||
"username, Email or phone": "Nombre de usuario, correo electrónico o teléfono"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Editar modelo",
|
||||
"Model text": "Texto modelo",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Empleador, como el nombre de una empresa u organización",
|
||||
"Bio": "Bio - Biografía",
|
||||
"Bio - Tooltip": "Introducción personal del usuario",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Validación de Captcha fallida",
|
||||
"Captcha Verify Success": "Verificación de Captcha Exitosa",
|
||||
"Country code": "Código de país",
|
||||
"Country/Region": "País/Región",
|
||||
"Country/Region - Tooltip": "País o región",
|
||||
"Edit User": "Editar usuario",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "El correo electrónico no puede estar vacío",
|
||||
"Email/phone reset successfully": "Restablecimiento de correo electrónico/teléfono exitoso",
|
||||
"Empty input!": "¡Entrada vacía!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Página de inicio del usuario",
|
||||
"Homepage - Tooltip": "URL de la página de inicio del usuario",
|
||||
"ID card": "Tarjeta de identificación",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Introduce tu correo electrónico",
|
||||
"Input your phone number": "Ingrese su número de teléfono",
|
||||
"Is admin": "Es el administrador",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Los usuarios bloqueados ya no pueden iniciar sesión",
|
||||
"Is global admin": "¿Es administrador global?",
|
||||
"Is global admin - Tooltip": "Es un administrador de Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Claves",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Enlace",
|
||||
"Location": "Ubicación",
|
||||
"Location - Tooltip": "Ciudad de residencia",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Por favor, selecciona un avatar de los recursos disponibles",
|
||||
"Properties": "Propiedades",
|
||||
"Properties - Tooltip": "Propiedades del usuario",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Volver a ingresar Nueva",
|
||||
"Reset Email...": "Restablecer Correo Electrónico...",
|
||||
"Reset Phone...": "Reiniciar teléfono...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Selecciona una foto...",
|
||||
"Set Password": "Establecer contraseña",
|
||||
"Set new profile picture": "Establecer nueva foto de perfil",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Déconnexion",
|
||||
"My Account": "Mon Compte",
|
||||
"Sign Up": "S'inscrire"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Scénarios d'utilisation du certificat",
|
||||
"Type - Tooltip": "Type de certificat"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Le code que vous avez reçu",
|
||||
"Email code": "Code email",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "ainement",
|
||||
"Cert - Tooltip": "Le certificat de clé publique qui doit être vérifié par le kit de développement client correspondant à cette application",
|
||||
"Certs": "Certains",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Cliquez pour télécharger",
|
||||
"Client IP": "Adresse IP du client",
|
||||
"Close": "Fermer",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Mot de passe principal",
|
||||
"Master password - Tooltip": "Peut être utilisé pour se connecter à tous les utilisateurs sous cette organisation, ce qui facilite la connexion des administrateurs en tant que cet utilisateur pour résoudre les problèmes techniques",
|
||||
"Menu": "Menu",
|
||||
"Messages": "Messages",
|
||||
"Method": "Méthode",
|
||||
"Model": "Modèle",
|
||||
"Model - Tooltip": "Modèle de contrôle d'accès Casbin",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Lien d'URL",
|
||||
"Up": "Haut",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Utilisateur",
|
||||
"User - Tooltip": "Assurez-vous que le nom d'utilisateur est correct",
|
||||
"User containers": "Piscines d'utilisateurs",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Modifier LDAP",
|
||||
"Enable SSL": "Activer SSL",
|
||||
"Enable SSL - Tooltip": "Que ce soit pour activer SSL",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Identifiant de groupe",
|
||||
"Last Sync": "Dernière synchronisation",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Serveur",
|
||||
"Server host": "Hébergeur de serveur",
|
||||
"Server host - Tooltip": "Adresse du serveur LDAP",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Port du serveur",
|
||||
"Server port - Tooltip": "Port du serveur LDAP",
|
||||
"The Auto Sync option will sync all users to specify organization": "L'option de synchronisation automatique synchronisera tous les utilisateurs vers l'organisation spécifiée",
|
||||
"UidNumber / Uid": "NuméroUID / UID"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Connexion automatique",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "Inscrivez-vous maintenant",
|
||||
"username, Email or phone": "Nom d'utilisateur, e-mail ou téléphone"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Modifier le modèle",
|
||||
"Model text": "Texte modèle",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Employeur, tel que le nom de l'entreprise ou de l'organisation",
|
||||
"Bio": "Bio",
|
||||
"Bio - Tooltip": "Présentation de l'utilisateur",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "La vérification Captcha a échoué",
|
||||
"Captcha Verify Success": "Succès de vérification de Captcha",
|
||||
"Country code": "Code pays",
|
||||
"Country/Region": "Pays/Région",
|
||||
"Country/Region - Tooltip": "Pays ou région",
|
||||
"Edit User": "Modifier l'utilisateur",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "L'e-mail ne peut pas être vide",
|
||||
"Email/phone reset successfully": "Réinitialisation de l'email/du téléphone réussie",
|
||||
"Empty input!": "Entrée vide !",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Page d'accueil de l'utilisateur",
|
||||
"Homepage - Tooltip": "Adresse URL de la page d'accueil de l'utilisateur",
|
||||
"ID card": "carte d'identité",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Entrez votre adresse e-mail",
|
||||
"Input your phone number": "Saisissez votre numéro de téléphone",
|
||||
"Is admin": "Est l'administrateur",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Les utilisateurs interdits ne peuvent plus se connecter",
|
||||
"Is global admin": "Est l'administrateur global",
|
||||
"Is global admin - Tooltip": "Est un administrateur de Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Clés",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Lien",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "Ville de résidence",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Veuillez sélectionner un avatar à partir des ressources",
|
||||
"Properties": "Propriétés",
|
||||
"Properties - Tooltip": "Propriétés de l'utilisateur",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Entrer de nouveau dans le nouveau",
|
||||
"Reset Email...": "Réinitialisation de l'e-mail...",
|
||||
"Reset Phone...": "Réinitialiser le téléphone...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Sélectionnez une photo...",
|
||||
"Set Password": "Définir un mot de passe",
|
||||
"Set new profile picture": "Changer la photo de profil",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Keluar",
|
||||
"My Account": "Akun Saya",
|
||||
"Sign Up": "Mendaftar"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Skema penggunaan sertifikat:",
|
||||
"Type - Tooltip": "Jenis sertifikat"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Kode yang kamu terima",
|
||||
"Email code": "Kode email",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "Sertifikat",
|
||||
"Cert - Tooltip": "Sertifikat kunci publik yang perlu diverifikasi oleh SDK klien yang sesuai dengan aplikasi ini",
|
||||
"Certs": "Sertifikat",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Klik untuk Mengunggah",
|
||||
"Client IP": "IP klien",
|
||||
"Close": "Tutup",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Kata sandi utama",
|
||||
"Master password - Tooltip": "Dapat digunakan untuk masuk ke semua pengguna di bawah organisasi ini, sehingga memudahkan administrator untuk masuk sebagai pengguna ini untuk menyelesaikan masalah teknis",
|
||||
"Menu": "Daftar makanan",
|
||||
"Messages": "Messages",
|
||||
"Method": "Metode",
|
||||
"Model": "Model",
|
||||
"Model - Tooltip": "Model kontrol akses Casbin",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Tautan URL",
|
||||
"Up": "Ke atas",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Pengguna",
|
||||
"User - Tooltip": "Pastikan username-nya benar",
|
||||
"User containers": "User pools",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Mengedit LDAP",
|
||||
"Enable SSL": "Aktifkan SSL",
|
||||
"Enable SSL - Tooltip": "Apakah untuk mengaktifkan SSL?",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "ID grup",
|
||||
"Last Sync": "Terakhir Sinkronisasi",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Server",
|
||||
"Server host": "Hewan Server",
|
||||
"Server host - Tooltip": "Alamat server LDAP",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Port server",
|
||||
"Server port - Tooltip": "Port server LDAP",
|
||||
"The Auto Sync option will sync all users to specify organization": "Opsi Auto Sync akan menyinkronkan semua pengguna ke organisasi tertentu",
|
||||
"UidNumber / Uid": "NomorUID / UID"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Masuk otomatis",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "Daftar sekarang",
|
||||
"username, Email or phone": "nama pengguna, Email atau nomor telepon"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Mengedit Model",
|
||||
"Model text": "Teks Model",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Pemberi Kerja, seperti nama perusahaan atau nama organisasi",
|
||||
"Bio": "Bio: Biografi",
|
||||
"Bio - Tooltip": "Pengenalan diri dari pengguna",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Gagal memverifikasi Captcha",
|
||||
"Captcha Verify Success": "Captcha Verifikasi Berhasil",
|
||||
"Country code": "Kode negara",
|
||||
"Country/Region": "Negara/daerah",
|
||||
"Country/Region - Tooltip": "Negara atau wilayah",
|
||||
"Edit User": "Edit Pengguna",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "Email tidak boleh kosong",
|
||||
"Email/phone reset successfully": "Email/telepon berhasil diatur ulang",
|
||||
"Empty input!": "Masukan kosong!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Homepage",
|
||||
"Homepage - Tooltip": "URL halaman depan pengguna",
|
||||
"ID card": "Kartu identitas",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Masukkan alamat email Anda",
|
||||
"Input your phone number": "Masukkan nomor telepon Anda",
|
||||
"Is admin": "Apakah admin?",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "User yang dilarang tidak dapat masuk lagi",
|
||||
"Is global admin": "Apakah global admin",
|
||||
"Is global admin - Tooltip": "Adalah seorang administrator Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Kunci",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Tautan",
|
||||
"Location": "Lokasi",
|
||||
"Location - Tooltip": "Kota tempat tinggal",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Silakan pilih avatar dari sumber daya",
|
||||
"Properties": "Properti",
|
||||
"Properties - Tooltip": "Properti dari pengguna",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Masukkan kembali baru",
|
||||
"Reset Email...": "Atur Ulang Email...",
|
||||
"Reset Phone...": "Atur Ulang Telepon...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Pilih foto...",
|
||||
"Set Password": "Atur Kata Sandi",
|
||||
"Set new profile picture": "Mengatur gambar profil baru",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "ログアウト",
|
||||
"My Account": "マイアカウント",
|
||||
"Sign Up": "新規登録"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "証明書の使用シナリオ",
|
||||
"Type - Tooltip": "証明書の種類"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "受け取ったコード",
|
||||
"Email code": "メールコード",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "証明書",
|
||||
"Cert - Tooltip": "このアプリケーションに対応するクライアントSDKによって検証する必要がある公開鍵証明書",
|
||||
"Certs": "証明書",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "アップロードするにはクリックしてください",
|
||||
"Client IP": "クライアントIP",
|
||||
"Close": "閉じる",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "マスターパスワード",
|
||||
"Master password - Tooltip": "この組織のすべてのユーザーにログインするために使用でき、管理者が技術的な問題を解決するためにこのユーザーとしてログインするのに便利です",
|
||||
"Menu": "メニュー",
|
||||
"Messages": "Messages",
|
||||
"Method": "方法",
|
||||
"Model": "モデル",
|
||||
"Model - Tooltip": "カスビンアクセスコントロールモデル",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URLリンク",
|
||||
"Up": "アップ",
|
||||
"Updated time": "Updated time",
|
||||
"User": "ユーザー",
|
||||
"User - Tooltip": "ユーザー名が正しいことを確認してください",
|
||||
"User containers": "ユーザープール",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "LDAPを編集",
|
||||
"Enable SSL": "SSL を有効にする",
|
||||
"Enable SSL - Tooltip": "SSLを有効にするかどうか",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "グループID",
|
||||
"Last Sync": "最後の同期",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "サーバー",
|
||||
"Server host": "サーバーホスト",
|
||||
"Server host - Tooltip": "LDAPサーバーのアドレス",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "サーバーポート",
|
||||
"Server port - Tooltip": "LDAPサーバーポート",
|
||||
"The Auto Sync option will sync all users to specify organization": "オート同期オプションは、特定の組織に全ユーザーを同期します",
|
||||
"UidNumber / Uid": "Uid番号 / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "自動サインイン",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "今すぐサインアップ",
|
||||
"username, Email or phone": "ユーザー名、メールアドレス、または電話番号"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "編集モデル",
|
||||
"Model text": "モデルテキスト",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "企業名や団体名などの雇用主",
|
||||
"Bio": "バイオ技術",
|
||||
"Bio - Tooltip": "ユーザーの自己紹介\n\n私は○○です。私は○○(国、都市、職業など)出身で、現在は○○(国、都市、職業など)に住んでいます。私は○○(趣味、特技、興味など)が好きで、空き時間にはよくそれをしています。よろしくお願いします",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "キャプチャ検証に失敗しました",
|
||||
"Captcha Verify Success": "キャプチャを確認しました。成功しました",
|
||||
"Country code": "国番号",
|
||||
"Country/Region": "国/地域",
|
||||
"Country/Region - Tooltip": "国または地域",
|
||||
"Edit User": "ユーザーの編集",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "電子メールは空にできません",
|
||||
"Email/phone reset successfully": "メール/電話のリセットが成功しました",
|
||||
"Empty input!": "空の入力!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "ユーザーのホームページ",
|
||||
"Homepage - Tooltip": "ユーザーのホームページのURL",
|
||||
"ID card": "IDカード",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "あなたのメールアドレスを入力してください",
|
||||
"Input your phone number": "電話番号を入力してください",
|
||||
"Is admin": "管理者ですか?",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "禁止されたユーザーはこれ以上ログインできません",
|
||||
"Is global admin": "グローバル管理者です",
|
||||
"Is global admin - Tooltip": "Casdoorの管理者です",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "鍵",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "リンク",
|
||||
"Location": "場所",
|
||||
"Location - Tooltip": "居住都市",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "リソースからアバターを選択してください",
|
||||
"Properties": "特性",
|
||||
"Properties - Tooltip": "ユーザーのプロパティー",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "新しく入り直す",
|
||||
"Reset Email...": "リセットメール...",
|
||||
"Reset Phone...": "リセットします...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "写真を選択してください...",
|
||||
"Set Password": "パスワードを設定する",
|
||||
"Set new profile picture": "新しいプロフィール写真を設定する",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "로그아웃",
|
||||
"My Account": "내 계정",
|
||||
"Sign Up": "가입하기"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "인증서의 사용 시나리오",
|
||||
"Type - Tooltip": "증명서 유형"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "받은 코드",
|
||||
"Email code": "이메일 코드",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "인증서",
|
||||
"Cert - Tooltip": "이 응용 프로그램에 해당하는 클라이언트 SDK에서 확인해야 하는 공개 키 인증서",
|
||||
"Certs": "증명서",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "클릭하여 업로드하세요",
|
||||
"Client IP": "고객 IP",
|
||||
"Close": "닫다",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "마스터 비밀번호",
|
||||
"Master password - Tooltip": "이 조직의 모든 사용자에게 로그인하는 데 사용될 수 있으며, 이 사용자로 로그인하여 기술 문제를 해결하는 관리자에게 편리합니다",
|
||||
"Menu": "메뉴",
|
||||
"Messages": "Messages",
|
||||
"Method": "방법",
|
||||
"Model": "모델",
|
||||
"Model - Tooltip": "Casbin 접근 제어 모델",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL 링크",
|
||||
"Up": "위로",
|
||||
"Updated time": "Updated time",
|
||||
"User": "사용자",
|
||||
"User - Tooltip": "사용자 이름이 정확한지 확인하세요",
|
||||
"User containers": "사용자 풀",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "LDAP 수정",
|
||||
"Enable SSL": "SSL 활성화",
|
||||
"Enable SSL - Tooltip": "SSL을 활성화할지 여부를 결정하십시오",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "그룹 ID",
|
||||
"Last Sync": "마지막 동기화",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "서버",
|
||||
"Server host": "서버 호스트",
|
||||
"Server host - Tooltip": "LDAP 서버 주소",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "서버 포트",
|
||||
"Server port - Tooltip": "LDAP 서버 포트",
|
||||
"The Auto Sync option will sync all users to specify organization": "오토 동기화 옵션은 모든 사용자를 지정된 조직에 동기화합니다",
|
||||
"UidNumber / Uid": "식별자 번호 / 식별자"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "자동 로그인",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "지금 가입하세요",
|
||||
"username, Email or phone": "유저명, 이메일 또는 전화번호"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "편집 형태 모델",
|
||||
"Model text": "모델 텍스트",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "고용주, 회사명 또는 조직명",
|
||||
"Bio": "바이오",
|
||||
"Bio - Tooltip": "사용자의 자기소개\n\n안녕하세요, 저는 [이름]입니다. 한국을 포함한 여러 나라에서 살아본 적이 있습니다. 저는 [직업/전공]을 공부하고 있으며 [취미/관심사]에 대해 깊게 알고 있습니다. 이 채팅 서비스를 사용하여 새로운 사람들과 함께 대화를 나누기를 원합니다. 감사합니다",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "캡차 검증 실패",
|
||||
"Captcha Verify Success": "캡차 검증 성공",
|
||||
"Country code": "국가 코드",
|
||||
"Country/Region": "국가 / 지역",
|
||||
"Country/Region - Tooltip": "국가 또는 지역",
|
||||
"Edit User": "사용자 편집",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "이메일은 비어 있을 수 없습니다",
|
||||
"Email/phone reset successfully": "이메일/전화 초기화가 성공적으로 완료되었습니다",
|
||||
"Empty input!": "빈 입력!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "사용자의 홈페이지",
|
||||
"Homepage - Tooltip": "사용자의 홈페이지 URL",
|
||||
"ID card": "ID 카드",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "이메일을 입력하세요",
|
||||
"Input your phone number": "전화번호를 입력하세요",
|
||||
"Is admin": "어드민인가요?",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "금지된 사용자는 더 이상 로그인할 수 없습니다",
|
||||
"Is global admin": "전세계 관리자입니까?",
|
||||
"Is global admin - Tooltip": "캐스도어의 관리자입니다",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "열쇠",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "링크",
|
||||
"Location": "장소",
|
||||
"Location - Tooltip": "거주 도시",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "자원에서 아바타를 선택해주세요",
|
||||
"Properties": "특성",
|
||||
"Properties - Tooltip": "사용자의 속성",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "재진입 새로운",
|
||||
"Reset Email...": "이메일 리셋...",
|
||||
"Reset Phone...": "폰 초기화...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "사진을 선택하세요.",
|
||||
"Set Password": "비밀번호 설정",
|
||||
"Set new profile picture": "새로운 프로필 사진을 설정하세요",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Выход",
|
||||
"My Account": "Мой аккаунт",
|
||||
"Sign Up": "Зарегистрироваться"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Сценарии использования сертификата",
|
||||
"Type - Tooltip": "Тип сертификата"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Код, который вы получили",
|
||||
"Email code": "Электронный код письма",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "Сертификат",
|
||||
"Cert - Tooltip": "Сертификат открытого ключа, который требуется проверить клиентским SDK, соответствующим этому приложению",
|
||||
"Certs": "сертификаты",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Нажмите, чтобы загрузить",
|
||||
"Client IP": "Клиентский IP",
|
||||
"Close": "Близко",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Главный пароль",
|
||||
"Master password - Tooltip": "Можно использовать для входа в учетные записи всех пользователей этой организации, что удобно для администраторов, чтобы войти в качестве этого пользователя и решить технические проблемы",
|
||||
"Menu": "Меню",
|
||||
"Messages": "Messages",
|
||||
"Method": "Метод",
|
||||
"Model": "Модель",
|
||||
"Model - Tooltip": "Модель контроля доступа Casbin",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Ссылка URL",
|
||||
"Up": "Вверх",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Пользователь",
|
||||
"User - Tooltip": "Убедитесь, что имя пользователя правильное",
|
||||
"User containers": "Пользовательские пулы",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Изменить LDAP",
|
||||
"Enable SSL": "Включить SSL",
|
||||
"Enable SSL - Tooltip": "Перевод: Следует ли включать SSL",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Идентификатор группы",
|
||||
"Last Sync": "Последняя синхронизация",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Сервер",
|
||||
"Server host": "Хост сервера",
|
||||
"Server host - Tooltip": "Адрес сервера LDAP",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Порт сервера",
|
||||
"Server port - Tooltip": "Port сервера LDAP",
|
||||
"The Auto Sync option will sync all users to specify organization": "Опция \"Авто-синхронизация\" синхронизирует всех пользователей с указанной организацией",
|
||||
"UidNumber / Uid": "НомерUid / Uid"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Автоматическая авторизация",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "Зарегистрируйтесь сейчас",
|
||||
"username, Email or phone": "имя пользователя, электронная почта или телефон"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Редактировать модель",
|
||||
"Model text": "Модельный текст",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Работодатель, такой как название компании или организации",
|
||||
"Bio": "Био",
|
||||
"Bio - Tooltip": "Само представление пользователя",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Ошибка верификации Captcha",
|
||||
"Captcha Verify Success": "Успешно прошли проверку Captcha",
|
||||
"Country code": "Код страны",
|
||||
"Country/Region": "Страна/регион",
|
||||
"Country/Region - Tooltip": "Страна или регион",
|
||||
"Edit User": "Редактировать пользователь",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "Email не может быть пустым",
|
||||
"Email/phone reset successfully": "Электронная почта / номер телефона успешно сброшены",
|
||||
"Empty input!": "Пустой ввод!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Главная страница пользователя",
|
||||
"Homepage - Tooltip": "URL домашней страницы пользователя",
|
||||
"ID card": "ID-карта",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Введите свой адрес электронной почты",
|
||||
"Input your phone number": "Введите ваш номер телефона",
|
||||
"Is admin": "Это администратор",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Запрещенные пользователи больше не могут выполнять вход в систему",
|
||||
"Is global admin": "Является глобальным администратором",
|
||||
"Is global admin - Tooltip": "Является администратором Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Ключи",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Ссылка",
|
||||
"Location": "Местоположение",
|
||||
"Location - Tooltip": "Город проживания",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Пожалуйста, выберите аватар из ресурсов",
|
||||
"Properties": "Свойства",
|
||||
"Properties - Tooltip": "Свойства пользователя",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Войдите снова Новый",
|
||||
"Reset Email...": "Сбросить электронное письмо...",
|
||||
"Reset Phone...": "Сбросить телефон...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Выберите фотографию...",
|
||||
"Set Password": "Установить пароль",
|
||||
"Set new profile picture": "Установить новое фото профиля",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "Chats & Messages",
|
||||
"Logout": "Đăng xuất",
|
||||
"My Account": "Tài khoản của tôi",
|
||||
"Sign Up": "Đăng ký"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "Các kịch bản sử dụng của giấy chứng nhận",
|
||||
"Type - Tooltip": "Loại chứng chỉ"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "Edit Chat",
|
||||
"Group": "Group",
|
||||
"Message count": "Message count",
|
||||
"New Chat": "New Chat",
|
||||
"Single": "Single",
|
||||
"User1": "User1",
|
||||
"User1 - Tooltip": "User1 - Tooltip",
|
||||
"User2": "User2",
|
||||
"User2 - Tooltip": "User2 - Tooltip",
|
||||
"Users - Tooltip": "Users - Tooltip"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "Mã bạn nhận được",
|
||||
"Email code": "Mã email",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "Chứng chỉ",
|
||||
"Cert - Tooltip": "Chứng chỉ khóa công khai cần được xác minh bởi SDK khách hàng tương ứng với ứng dụng này",
|
||||
"Certs": "Chứng chỉ",
|
||||
"Chats": "Chats",
|
||||
"Click to Upload": "Nhấp để tải lên",
|
||||
"Client IP": "Địa chỉ IP của khách hàng",
|
||||
"Close": "Đóng lại",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "Mật khẩu chính",
|
||||
"Master password - Tooltip": "Có thể được sử dụng để đăng nhập vào tất cả các người dùng trong tổ chức này, giúp cho quản trị viên dễ dàng đăng nhập với tư cách người dùng này để giải quyết các vấn đề kỹ thuật",
|
||||
"Menu": "Thực đơn",
|
||||
"Messages": "Messages",
|
||||
"Method": "Phương pháp",
|
||||
"Model": "Mô hình",
|
||||
"Model - Tooltip": "Mô hình kiểm soát truy cập Casbin",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Đường dẫn URL",
|
||||
"Up": "Lên",
|
||||
"Updated time": "Updated time",
|
||||
"User": "Người dùng",
|
||||
"User - Tooltip": "Hãy đảm bảo tên đăng nhập chính xác",
|
||||
"User containers": "Nhóm người dùng",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "Chỉnh sửa LDAP",
|
||||
"Enable SSL": "Kích hoạt SSL",
|
||||
"Enable SSL - Tooltip": "Có nên kích hoạt SSL hay không?",
|
||||
"Filter fields": "Filter fields",
|
||||
"Filter fields - Tooltip": "Filter fields - Tooltip",
|
||||
"Group ID": "Nhóm ID",
|
||||
"Last Sync": "Đồng bộ lần cuối",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "Máy chủ",
|
||||
"Server host": "Máy chủ chủ động",
|
||||
"Server host - Tooltip": "Địa chỉ máy chủ LDAP",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "Cổng máy chủ",
|
||||
"Server port - Tooltip": "Cổng máy chủ LDAP",
|
||||
"The Auto Sync option will sync all users to specify organization": "Tùy chọn Auto Sync sẽ đồng bộ tất cả người dùng vào tổ chức cụ thể",
|
||||
"UidNumber / Uid": "Số UID / UID"
|
||||
"synced": "synced",
|
||||
"unsynced": "unsynced"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "Tự động đăng nhập",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "Đăng ký ngay bây giờ",
|
||||
"username, Email or phone": "Tên đăng nhập, Email hoặc điện thoại"
|
||||
},
|
||||
"message": {
|
||||
"Author": "Author",
|
||||
"Author - Tooltip": "Author - Tooltip",
|
||||
"Chat": "Chat",
|
||||
"Chat - Tooltip": "Chat - Tooltip",
|
||||
"Edit Message": "Edit Message",
|
||||
"New Message": "New Message",
|
||||
"Text": "Text",
|
||||
"Text - Tooltip": "Text - Tooltip"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "Chỉnh sửa mô hình",
|
||||
"Model text": "Văn bản mẫu",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "Nhà tuyển dụng, chẳng hạn như tên công ty hoặc tổ chức",
|
||||
"Bio": "bản vẻ đời sống",
|
||||
"Bio - Tooltip": "Tự giới thiệu của người dùng",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "Xác thực Captcha không thành công",
|
||||
"Captcha Verify Success": "Xác thực Captcha Thành công",
|
||||
"Country code": "Mã quốc gia",
|
||||
"Country/Region": "Quốc gia / Vùng miền",
|
||||
"Country/Region - Tooltip": "Quốc gia hoặc khu vực",
|
||||
"Edit User": "Chỉnh sửa người dùng",
|
||||
"Education": "Education",
|
||||
"Education - Tooltip": "Education - Tooltip",
|
||||
"Email cannot be empty": "Email không được để trống",
|
||||
"Email/phone reset successfully": "Đặt lại email/điện thoại thành công",
|
||||
"Empty input!": "Đầu vào trống!",
|
||||
"Gender": "Gender",
|
||||
"Gender - Tooltip": "Gender - Tooltip",
|
||||
"Homepage": "Trang chủ của người dùng",
|
||||
"Homepage - Tooltip": "Địa chỉ URL của trang chủ của người dùng",
|
||||
"ID card": "Thẻ căn cước dân sự",
|
||||
"ID card - Tooltip": "ID card - Tooltip",
|
||||
"ID card type": "ID card type",
|
||||
"ID card type - Tooltip": "ID card type - Tooltip",
|
||||
"Input your email": "Nhập địa chỉ email của bạn",
|
||||
"Input your phone number": "Nhập số điện thoại của bạn",
|
||||
"Is admin": "Là quản trị viên",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "Người dùng bị cấm không thể đăng nhập nữa",
|
||||
"Is global admin": "Là quản trị viên toàn cầu",
|
||||
"Is global admin - Tooltip": "Là một quản trị viên của Casdoor",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "Chìa khóa",
|
||||
"Language": "Language",
|
||||
"Language - Tooltip": "Language - Tooltip",
|
||||
"Link": "Liên kết",
|
||||
"Location": "Vị trí",
|
||||
"Location - Tooltip": "Thành phố cư trú",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "Vui lòng chọn avatar từ tài nguyên",
|
||||
"Properties": "Đặc tính",
|
||||
"Properties - Tooltip": "Các thuộc tính của người dùng",
|
||||
"Ranking": "Ranking",
|
||||
"Ranking - Tooltip": "Ranking - Tooltip",
|
||||
"Re-enter New": "Nhập lại New",
|
||||
"Reset Email...": "Thiết lập lại Email...",
|
||||
"Reset Phone...": "Đặt lại điện thoại...",
|
||||
"Score": "Score",
|
||||
"Score - Tooltip": "Score - Tooltip",
|
||||
"Select a photo...": "Chọn một bức ảnh...",
|
||||
"Set Password": "Đặt mật khẩu",
|
||||
"Set new profile picture": "Đặt hình đại diện mới",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": {
|
||||
"Chats & Messages": "聊天 & 消息",
|
||||
"Logout": "登出",
|
||||
"My Account": "我的账户",
|
||||
"Sign Up": "注册"
|
||||
@@ -117,6 +118,19 @@
|
||||
"Scope - Tooltip": "公钥证书的使用场景",
|
||||
"Type - Tooltip": "公钥证书的类型"
|
||||
},
|
||||
"chat": {
|
||||
"AI": "AI",
|
||||
"Edit Chat": "编辑聊天",
|
||||
"Group": "群聊",
|
||||
"Message count": "消息个数",
|
||||
"New Chat": "添加聊天",
|
||||
"Single": "单聊",
|
||||
"User1": "用户1",
|
||||
"User1 - Tooltip": "当聊天类型为单聊时,该值为聊天的发起者;当聊天类型为群聊时,该值为群主",
|
||||
"User2": "用户2",
|
||||
"User2 - Tooltip": "当聊天类型为单聊时,该值为聊天的对象;当聊天类型为群聊时,该值为管理员",
|
||||
"Users - Tooltip": "能够接收到聊天消息的所有用户"
|
||||
},
|
||||
"code": {
|
||||
"Code you received": "验证码",
|
||||
"Email code": "邮箱验证码",
|
||||
@@ -159,6 +173,7 @@
|
||||
"Cert": "证书",
|
||||
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
|
||||
"Certs": "证书",
|
||||
"Chats": "聊天",
|
||||
"Click to Upload": "点击上传",
|
||||
"Client IP": "客户端IP",
|
||||
"Close": "关闭",
|
||||
@@ -203,6 +218,7 @@
|
||||
"Master password": "万能密码",
|
||||
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
||||
"Menu": "目录",
|
||||
"Messages": "消息",
|
||||
"Method": "方法",
|
||||
"Model": "模型",
|
||||
"Model - Tooltip": "Casbin的访问控制模型",
|
||||
@@ -269,6 +285,7 @@
|
||||
"URL": "链接",
|
||||
"URL - Tooltip": "URL链接",
|
||||
"Up": "上移",
|
||||
"Updated time": "更新时间",
|
||||
"User": "用户",
|
||||
"User - Tooltip": "请确保用户名正确",
|
||||
"User containers": "用户池",
|
||||
@@ -293,8 +310,12 @@
|
||||
"Edit LDAP": "编辑LDAP",
|
||||
"Enable SSL": "启用SSL",
|
||||
"Enable SSL - Tooltip": "是否启用SSL",
|
||||
"Filter fields": "过滤字段",
|
||||
"Filter fields - Tooltip": "使用ldap用户登录Casdoor时, 用于搜索ldap服务器中该用户的字段 - Tooltip",
|
||||
"Group ID": "组ID",
|
||||
"Last Sync": "最近同步",
|
||||
"Search Filter": "Search Filter",
|
||||
"Search Filter - Tooltip": "Search Filter - Tooltip",
|
||||
"Server": "服务器",
|
||||
"Server host": "域名",
|
||||
"Server host - Tooltip": "LDAP服务器地址",
|
||||
@@ -303,7 +324,8 @@
|
||||
"Server port": "端口",
|
||||
"Server port - Tooltip": "LDAP服务器端口号",
|
||||
"The Auto Sync option will sync all users to specify organization": "自动同步选项将同步所有用户以指定组织",
|
||||
"UidNumber / Uid": "Uid号码 / Uid"
|
||||
"synced": "已同步",
|
||||
"unsynced": "未同步"
|
||||
},
|
||||
"login": {
|
||||
"Auto sign in": "下次自动登录",
|
||||
@@ -331,6 +353,16 @@
|
||||
"sign up now": "立即注册",
|
||||
"username, Email or phone": "用户名、Email或手机号"
|
||||
},
|
||||
"message": {
|
||||
"Author": "作者",
|
||||
"Author - Tooltip": "发出消息的用户",
|
||||
"Chat": "聊天",
|
||||
"Chat - Tooltip": "消息所属的聊天ID",
|
||||
"Edit Message": "编辑消息",
|
||||
"New Message": "添加消息",
|
||||
"Text": "内容",
|
||||
"Text - Tooltip": "消息的内容"
|
||||
},
|
||||
"model": {
|
||||
"Edit Model": "编辑模型",
|
||||
"Model text": "模型文本",
|
||||
@@ -709,18 +741,27 @@
|
||||
"Affiliation - Tooltip": "工作单位,如公司、组织名称",
|
||||
"Bio": "自我介绍",
|
||||
"Bio - Tooltip": "用户的自我介绍",
|
||||
"Birthday": "Birthday",
|
||||
"Birthday - Tooltip": "Birthday - Tooltip",
|
||||
"Captcha Verify Failed": "验证码校验失败",
|
||||
"Captcha Verify Success": "验证码校验成功",
|
||||
"Country code": "国家代码",
|
||||
"Country/Region": "国家/地区",
|
||||
"Country/Region - Tooltip": "国家或地区",
|
||||
"Edit User": "编辑用户",
|
||||
"Education": "教育",
|
||||
"Education - Tooltip": "教育 - Tooltip",
|
||||
"Email cannot be empty": "邮箱不能为空",
|
||||
"Email/phone reset successfully": "邮箱或手机号重置成功",
|
||||
"Empty input!": "输入为空!",
|
||||
"Gender": "性别",
|
||||
"Gender - Tooltip": "性别 - Tooltip",
|
||||
"Homepage": "个人主页",
|
||||
"Homepage - Tooltip": "个人主页链接",
|
||||
"ID card": "身份证号",
|
||||
"ID card - Tooltip": "身份证号 - Tooltip",
|
||||
"ID card type": "身份证类型",
|
||||
"ID card type - Tooltip": "身份证类型 - Tooltip",
|
||||
"Input your email": "请输入邮箱",
|
||||
"Input your phone number": "输入手机号",
|
||||
"Is admin": "是组织管理员",
|
||||
@@ -731,7 +772,12 @@
|
||||
"Is forbidden - Tooltip": "被禁用的用户无法再登录",
|
||||
"Is global admin": "是全局管理员",
|
||||
"Is global admin - Tooltip": "是Casdoor平台的管理员",
|
||||
"Is online": "Is online",
|
||||
"Karma": "Karma",
|
||||
"Karma - Tooltip": "Karma - Tooltip",
|
||||
"Keys": "键",
|
||||
"Language": "语言",
|
||||
"Language - Tooltip": "语言 - Tooltip",
|
||||
"Link": "绑定",
|
||||
"Location": "城市",
|
||||
"Location - Tooltip": "居住地址所在的城市",
|
||||
@@ -747,9 +793,13 @@
|
||||
"Please select avatar from resources": "从资源中选择...",
|
||||
"Properties": "属性",
|
||||
"Properties - Tooltip": "用户的属性",
|
||||
"Ranking": "排名",
|
||||
"Ranking - Tooltip": "排名 - Tooltip",
|
||||
"Re-enter New": "重复新密码",
|
||||
"Reset Email...": "重置邮箱...",
|
||||
"Reset Phone...": "重置手机号...",
|
||||
"Score": "积分",
|
||||
"Score - Tooltip": "积分 - Tooltip",
|
||||
"Select a photo...": "选择图片...",
|
||||
"Set Password": "设置密码",
|
||||
"Set new profile picture": "设置新头像",
|
||||
|
@@ -81,16 +81,27 @@ class AccountTable extends React.Component {
|
||||
{name: "Country code", displayName: i18next.t("user:Country code")},
|
||||
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||
{name: "Location", displayName: i18next.t("user:Location")},
|
||||
{name: "Address", displayName: i18next.t("user:Address")},
|
||||
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||
{name: "Title", displayName: i18next.t("user:Title")},
|
||||
{name: "ID card type", displayName: i18next.t("user:ID card type")},
|
||||
{name: "ID card", displayName: i18next.t("user:ID card")},
|
||||
{name: "Homepage", displayName: i18next.t("user:Homepage")},
|
||||
{name: "Bio", displayName: i18next.t("user:Bio")},
|
||||
{name: "Tag", displayName: i18next.t("user:Tag")},
|
||||
{name: "Language", displayName: i18next.t("user:Language")},
|
||||
{name: "Gender", displayName: i18next.t("user:Gender")},
|
||||
{name: "Birthday", displayName: i18next.t("user:Birthday")},
|
||||
{name: "Education", displayName: i18next.t("user:Education")},
|
||||
{name: "Score", displayName: i18next.t("user:Score")},
|
||||
{name: "Karma", displayName: i18next.t("user:Karma")},
|
||||
{name: "Ranking", displayName: i18next.t("user:Ranking")},
|
||||
{name: "Signup application", displayName: i18next.t("general:Signup application")},
|
||||
{name: "Roles", displayName: i18next.t("general:Roles")},
|
||||
{name: "Permissions", displayName: i18next.t("general:Permissions")},
|
||||
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
|
||||
{name: "Properties", displayName: i18next.t("user:Properties")},
|
||||
{name: "Is online", displayName: i18next.t("user:Is online")},
|
||||
{name: "Is admin", displayName: i18next.t("user:Is admin")},
|
||||
{name: "Is global admin", displayName: i18next.t("user:Is global admin")},
|
||||
{name: "Is forbidden", displayName: i18next.t("user:Is forbidden")},
|
||||
|
@@ -45,8 +45,8 @@ class LdapTable extends React.Component {
|
||||
serverName: "Example LDAP Server",
|
||||
host: "example.com",
|
||||
port: 389,
|
||||
admin: "cn=admin,dc=example,dc=com",
|
||||
passwd: "123",
|
||||
username: "cn=admin,dc=example,dc=com",
|
||||
password: "123",
|
||||
baseDn: "ou=People,dc=example,dc=com",
|
||||
autosync: 0,
|
||||
lastSync: "",
|
||||
|
@@ -186,6 +186,11 @@ class SignupTable extends React.Component {
|
||||
{id: "Normal", name: i18next.t("application:Normal")},
|
||||
{id: "No verification", name: i18next.t("application:No verification")},
|
||||
];
|
||||
} else if (record.name === "Phone") {
|
||||
options = [
|
||||
{id: "Normal", name: i18next.t("application:Normal")},
|
||||
{id: "No verification", name: i18next.t("application:No verification")},
|
||||
];
|
||||
} else if (record.name === "Agreement") {
|
||||
options = [
|
||||
{id: "None", name: i18next.t("application:Only signup")},
|
||||
|
188
web/yarn.lock
188
web/yarn.lock
@@ -37,6 +37,19 @@
|
||||
rc-util "^5.27.0"
|
||||
stylis "^4.0.13"
|
||||
|
||||
"@ant-design/cssinjs@^1.8.1":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@ant-design/cssinjs/-/cssinjs-1.8.1.tgz#326682e779f5cd074668391a6698b50342a07d92"
|
||||
integrity sha512-pOQJV9H9viB6qB9u7hkpKEOIQGx4dd8zjpwzF1v8YNwjffbZTlyUNQYln56gwpFF7SFskpYpnSfgoqTK4sFE/Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.1"
|
||||
"@emotion/hash" "^0.8.0"
|
||||
"@emotion/unitless" "^0.7.5"
|
||||
classnames "^2.3.1"
|
||||
csstype "^3.0.10"
|
||||
rc-util "^5.27.0"
|
||||
stylis "^4.0.13"
|
||||
|
||||
"@ant-design/icons-svg@^4.2.1":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
|
||||
@@ -1807,6 +1820,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.5.0.tgz#6e52b3d1c38d13130101771821e09cdd414a16bc"
|
||||
integrity sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==
|
||||
|
||||
"@ctrl/tinycolor@^3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8"
|
||||
integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
|
||||
|
||||
"@cypress/request@^2.88.10":
|
||||
version "2.88.11"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047"
|
||||
@@ -2383,6 +2401,17 @@
|
||||
rc-trigger "^5.3.4"
|
||||
rc-util "^5.24.4"
|
||||
|
||||
"@rc-component/tour@~1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@rc-component/tour/-/tour-1.6.0.tgz#ef3888c8ccc5d1a2e465d7d2615abd1d2dd51e27"
|
||||
integrity sha512-b/s7LCb7bW4wxpWfZyNpl7khHUzSyObSlsLaIScRGd+W/v1wFVk8F7gRytl/z8ik9ZSXbLWx9EvexIuHoO/RcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.0"
|
||||
"@rc-component/portal" "^1.0.0-9"
|
||||
classnames "^2.3.2"
|
||||
rc-trigger "^5.3.4"
|
||||
rc-util "^5.24.4"
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
|
||||
@@ -3445,7 +3474,60 @@ antd-token-previewer@^1.1.0-22:
|
||||
tinycolor2 "^1.4.2"
|
||||
use-debouncy "^4.3.0"
|
||||
|
||||
antd@5.1.6, antd@^5:
|
||||
antd@5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.2.3.tgz#049bdd5f654f2fd42584c9c0eb71152df9da6e3c"
|
||||
integrity sha512-mUSVH4ZhzC8h3eLNyL7PWquKUmvDcWAVRZYi060MOw6uiKl0pqsB/FC8OpfwntpBlVYgGMk+y3GB0loqTMSmJA==
|
||||
dependencies:
|
||||
"@ant-design/colors" "^7.0.0"
|
||||
"@ant-design/cssinjs" "^1.5.6"
|
||||
"@ant-design/icons" "^5.0.0"
|
||||
"@ant-design/react-slick" "~1.0.0"
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@ctrl/tinycolor" "^3.6.0"
|
||||
"@rc-component/mutate-observer" "^1.0.0"
|
||||
"@rc-component/tour" "~1.6.0"
|
||||
classnames "^2.2.6"
|
||||
copy-to-clipboard "^3.2.0"
|
||||
dayjs "^1.11.1"
|
||||
qrcode.react "^3.1.0"
|
||||
rc-cascader "~3.8.0"
|
||||
rc-checkbox "~2.3.0"
|
||||
rc-collapse "~3.5.2"
|
||||
rc-dialog "~9.0.2"
|
||||
rc-drawer "~6.1.1"
|
||||
rc-dropdown "~4.0.0"
|
||||
rc-field-form "~1.27.0"
|
||||
rc-image "~5.13.0"
|
||||
rc-input "~0.2.1"
|
||||
rc-input-number "~7.4.0"
|
||||
rc-mentions "~2.0.0"
|
||||
rc-menu "~9.8.2"
|
||||
rc-motion "^2.6.1"
|
||||
rc-notification "~5.0.0"
|
||||
rc-pagination "~3.2.0"
|
||||
rc-picker "~3.1.1"
|
||||
rc-progress "~3.4.1"
|
||||
rc-rate "~2.9.0"
|
||||
rc-resize-observer "^1.2.0"
|
||||
rc-segmented "~2.1.2"
|
||||
rc-select "~14.2.0"
|
||||
rc-slider "~10.1.0"
|
||||
rc-steps "~6.0.0"
|
||||
rc-switch "~4.0.0"
|
||||
rc-table "~7.30.2"
|
||||
rc-tabs "~12.5.6"
|
||||
rc-textarea "~1.0.0"
|
||||
rc-tooltip "~5.3.1"
|
||||
rc-tree "~5.7.0"
|
||||
rc-tree-select "~5.6.0"
|
||||
rc-trigger "^5.3.4"
|
||||
rc-upload "~4.3.0"
|
||||
rc-util "^5.27.0"
|
||||
scroll-into-view-if-needed "^3.0.3"
|
||||
throttle-debounce "^5.0.0"
|
||||
|
||||
antd@^5:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/antd/-/antd-5.1.6.tgz#9ac912279f9f8e571674b3220b668897b4ffb0b3"
|
||||
integrity sha512-9bn2B4rZ1c7IXtn5U95aNGJXmLOZyL1V5TiGq3pk3S3bGD13BA2eynSI//e3c2FAslZlqQlQEGV+NHms9ygAVA==
|
||||
@@ -9650,6 +9732,16 @@ rc-collapse@~3.4.2:
|
||||
rc-util "^5.2.1"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-collapse@~3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.5.2.tgz#abb7d144ad55bd9cbd201fa95bc5b271da2aa7c3"
|
||||
integrity sha512-/TNiT3DW1t3sUCiVD/DPUYooJZ3BLA93/2rZsB3eM2bGJCCla2X9D2E4tgm7LGMQGy5Atb2lMUn2FQuvQNvavQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "2.x"
|
||||
rc-motion "^2.3.4"
|
||||
rc-util "^5.27.0"
|
||||
|
||||
rc-dialog@~9.0.0, rc-dialog@~9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-9.0.2.tgz#aadfebdeba145f256c1fac9b9f509f893cdbb5b8"
|
||||
@@ -9713,6 +9805,15 @@ rc-input-number@~7.4.0:
|
||||
classnames "^2.2.5"
|
||||
rc-util "^5.23.0"
|
||||
|
||||
rc-input@^0.2.1, rc-input@^0.2.2, rc-input@~0.2.1:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-0.2.2.tgz#de1c3c0e090d15777a6b1a3fd4a9566e25b3fbee"
|
||||
integrity sha512-xgkVcFgtRO0Hl9hmvslZhObNyxbSpTmy3nR1Tk4XrjjZ9lFJ7GcJBy6ss30Pdb0oX36cHzLN8I7VCjBGeRNB9A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.1"
|
||||
classnames "^2.2.1"
|
||||
rc-util "^5.18.1"
|
||||
|
||||
rc-input@~0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-0.1.4.tgz#45cb4ba209ae6cc835a2acb8629d4f8f0cb347e0"
|
||||
@@ -9722,6 +9823,15 @@ rc-input@~0.1.4:
|
||||
classnames "^2.2.1"
|
||||
rc-util "^5.18.1"
|
||||
|
||||
rc-input@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/rc-input/-/rc-input-1.0.4.tgz#2f2c73c884f41e80685bb2eb7b9d5533e8540a77"
|
||||
integrity sha512-clY4oneVHRtKHYf/HCxT/MO+4BGzCIywSNLosXWOm7fcQAS0jQW7n0an8Raa8JMB8kpxc8m28p7SNwFZmlMj6g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.1"
|
||||
classnames "^2.2.1"
|
||||
rc-util "^5.18.1"
|
||||
|
||||
rc-mentions@~1.13.1:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.13.1.tgz#c884b70e1505a197f1b32a7c6b39090db6992a72"
|
||||
@@ -9734,6 +9844,19 @@ rc-mentions@~1.13.1:
|
||||
rc-trigger "^5.0.4"
|
||||
rc-util "^5.22.5"
|
||||
|
||||
rc-mentions@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-2.0.0.tgz#688846d698943340334657d96c1448d8b57e5666"
|
||||
integrity sha512-58NSeM6R5MrgYAhR2TH27JgAN7ivp3iBTmty3q6gvrrGHelPMdGxpJ5aH7AIlodCrPWLAm1lT4XoiuI4s9snXA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.6"
|
||||
rc-input "^0.2.2"
|
||||
rc-menu "~9.8.0"
|
||||
rc-textarea "^1.0.0"
|
||||
rc-trigger "^5.0.4"
|
||||
rc-util "^5.22.5"
|
||||
|
||||
rc-menu@~9.8.0:
|
||||
version "9.8.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.8.0.tgz#d3d820f52ec37e27c5aec42b3cc68bbcfcaba1f7"
|
||||
@@ -9864,6 +9987,16 @@ rc-segmented@~2.1.0:
|
||||
rc-motion "^2.4.4"
|
||||
rc-util "^5.17.0"
|
||||
|
||||
rc-segmented@~2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.1.2.tgz#14c9077a1dae9c2ccb2ef5fbc5662c1c48c7ce8e"
|
||||
integrity sha512-qGo1bCr83ESXpXVOCXjFe1QJlCAQXyi9KCiy8eX3rIMYlTeJr/ftySIaTnYsitL18SvWf5ZEHsfqIWoX0EMfFQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.1"
|
||||
classnames "^2.2.1"
|
||||
rc-motion "^2.4.4"
|
||||
rc-util "^5.17.0"
|
||||
|
||||
rc-select@~14.2.0:
|
||||
version "14.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.2.0.tgz#60e83a7d5a5dae7fd9e3918d9b209074ea2c92d4"
|
||||
@@ -9888,6 +10021,15 @@ rc-slider@~10.0.0:
|
||||
rc-util "^5.18.1"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-slider@~10.1.0:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.1.1.tgz#5e82036e60b61021aba3ea0e353744dd7c74e104"
|
||||
integrity sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.5"
|
||||
rc-util "^5.27.0"
|
||||
|
||||
rc-steps@~6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-6.0.0.tgz#f7148f8097d5d135f19b96c1b4f4b50ad6093753"
|
||||
@@ -9930,6 +10072,19 @@ rc-tabs@~12.5.1:
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.16.0"
|
||||
|
||||
rc-tabs@~12.5.6:
|
||||
version "12.5.10"
|
||||
resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-12.5.10.tgz#0e41c723fac66c4f0bcad3271429fff6653b0721"
|
||||
integrity sha512-Ay0l0jtd4eXepFH9vWBvinBjqOpqzcsJTerBGwJy435P2S90Uu38q8U/mvc1sxUEVOXX5ZCFbxcWPnfG3dH+tQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
classnames "2.x"
|
||||
rc-dropdown "~4.0.0"
|
||||
rc-menu "~9.8.0"
|
||||
rc-motion "^2.6.2"
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.16.0"
|
||||
|
||||
rc-textarea@^0.4.0, rc-textarea@~0.4.5:
|
||||
version "0.4.7"
|
||||
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.4.7.tgz#627f662d46f99e0059d1c1ebc8db40c65339fe90"
|
||||
@@ -9941,6 +10096,28 @@ rc-textarea@^0.4.0, rc-textarea@~0.4.5:
|
||||
rc-util "^5.24.4"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
rc-textarea@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.2.2.tgz#111fa90fcedba6d244bc94615b7971b8d8f68815"
|
||||
integrity sha512-S9fkiek5VezfwJe2McEs/NH63xgnnZ4iDh6a8n01mIfzyNJj0HkS0Uz6boyR3/eONYjmKaqhrpuJJuEClRDEBw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.1"
|
||||
rc-input "~1.0.4"
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.27.0"
|
||||
|
||||
rc-textarea@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-1.0.1.tgz#44b1e09eda37a7af19cf884251e530cc7b03050b"
|
||||
integrity sha512-dtIm96apjJpCUcCeTtbnLGJaVlqbOqVgN0P9z+bqMSi7rcV5QVeUtBnG+jQTGk/uD183Z7jbhc8Dx7G3luDCwg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.1"
|
||||
classnames "^2.2.1"
|
||||
rc-input "^0.2.1"
|
||||
rc-resize-observer "^1.0.0"
|
||||
rc-util "^5.27.0"
|
||||
|
||||
rc-tooltip@^5.0.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.1.1.tgz#94178ed162d0252bc4993b725f5dc2ac0fccf154"
|
||||
@@ -9958,6 +10135,15 @@ rc-tooltip@~5.2.0:
|
||||
classnames "^2.3.1"
|
||||
rc-trigger "^5.0.0"
|
||||
|
||||
rc-tooltip@~5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.3.1.tgz#3dde4e1865f79cd23f202bba4e585c2a1173024b"
|
||||
integrity sha512-e6H0dMD38EPaSPD2XC8dRfct27VvT2TkPdoBSuNl3RRZ5tspiY/c5xYEmGC0IrABvMBgque4Mr2SMZuliCvoiQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
classnames "^2.3.1"
|
||||
rc-trigger "^5.3.1"
|
||||
|
||||
rc-tree-select@~5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.6.0.tgz#f34147f4c14341430bcece481804496d0abd3371"
|
||||
|
Reference in New Issue
Block a user