Compare commits

...

36 Commits

Author SHA1 Message Date
Yaodong Yu
9899022bcd fix: check enforcer should not be nil (#2199)
* fix: check enforcer should not be nil

* fix: check enforcer should not be nil

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-11 12:31:49 +08:00
Yaodong Yu
1a9d02be46 feat: use the casbin model to store relationships between users and groups (#2178)
* fix:reslove conflict

* fix: remove interface
2023-08-11 10:59:18 +08:00
Yang Luo
eafaa135b4 Change builtInAvailableField back to 5 2023-08-11 02:45:11 +08:00
Yang Luo
6746551447 Improve error message in InitEnforcer() 2023-08-11 02:36:29 +08:00
Yang Luo
3cb46c3628 Add isKey to syncer's table 2023-08-09 00:33:04 +08:00
Yaodong Yu
558bcf95d6 feat: save policy in adapter edit page (#2190)
* fix: save policy in adapter

* fix: disable edit for builtin adapter
2023-08-09 00:12:53 +08:00
Yang Luo
bb937c30c1 Fix empty cert in getPaymentProvider() 2023-08-08 22:37:48 +08:00
Baihhh
8dfdf7f767 ci: add GoogleCloud and QiNiu in Storage (#2188)
* feat: add GoogleCloud and QiNiu in Storage

Signed-off-by: baihhh <2542274498@qq.com>

* Update qiniu_cloud.go

* Update storage.go

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-08 22:34:55 +08:00
Yang Luo
62b2082e82 Add getUserOrganization() to user edit page 2023-08-08 21:58:27 +08:00
Yang Luo
a1806439f8 Add UserPrincipalName and MemberOf to get-ldap-users API 2023-08-08 20:18:47 +08:00
Yang Luo
01e58158b7 feat: Remove useless code 2023-08-08 19:16:55 +08:00
Yaodong Yu
15427ad9d6 fix: fix add provider error (#2184) 2023-08-07 17:22:32 +08:00
YunShu
d058f78dc6 fix: fix broken links (#2181) 2023-08-07 01:02:03 +08:00
UsherFall
fd9dbf8251 feat: add multiple SMS providers (#2182)
* feat: add amazon sns and azure acs provider

* feat: add msg91 sms provider

* feat: add infobip sms provider

* feat: add ucloud sms provider

* feat: add baidu cloud sms provider

* fix: fix logo and azure acs
2023-08-07 00:59:17 +08:00
Yaodong Yu
3220a04fa9 fix: use org/groupName replace groupName (#2180) 2023-08-06 20:16:44 +08:00
Yaodong Yu
f06a4990bd fix: rename in init.go (#2179)
* fix: rename in init.go

* fix: remove blank line

* fix: remove blank line

* Update init.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-06 13:07:30 +08:00
Yang Luo
9df7de5f27 Improve menu icons 2023-08-05 18:00:24 +08:00
Yang Luo
56c808c091 Improve menu 2023-08-05 17:41:35 +08:00
Yang Luo
9fd2421564 Update @ant-design/cssinjs dependency to avoid build error 2023-08-04 01:22:57 +08:00
Yang Luo
689d45c7fa feat: fix org name cannot be changed bug 2023-08-03 18:48:37 +08:00
Yang Luo
c24343bd53 Fix XxxChangeTrigger() doesn't return error bug 2023-08-03 18:45:49 +08:00
Yang Luo
979f43638d Change builtInAvailableField to 10 2023-08-03 18:17:15 +08:00
Yaodong Yu
685a4514cd fix: revert adapter port vartype to int (#2174) 2023-08-03 09:35:16 +08:00
Yaodong Yu
a05ca3af24 feat: use role ID to search in GetPermissionsAndRolesByUser() (#2170) 2023-08-02 20:58:06 +08:00
Yang Luo
c6f301ff9e Support svg in downloadImage() 2023-07-31 20:23:28 +08:00
haiwu
d7b2bcf288 feat: support payment cancel state (#2165) 2023-07-31 15:24:13 +08:00
Yang Luo
67ac3d6d21 Fix typo 2023-07-31 15:23:44 +08:00
Yaodong Yu
912d5c6a7f fix: support enforcerId parameter in Enforce API (#2164) 2023-07-31 00:20:53 +08:00
Yang Luo
32fbb5b534 Support custom provider for storage API 2023-07-30 23:19:45 +08:00
Yang Luo
21004f3009 Fix GetResources() missing items bug 2023-07-30 22:47:14 +08:00
Yang Luo
463bacd53b Add GetDirectResources() 2023-07-30 22:01:10 +08:00
Baihhh
78dc660041 feat: support 3 more language (#2163)
Signed-off-by: baihhh <2542274498@qq.com>
2023-07-30 20:45:47 +08:00
Yang Luo
2fb9674171 Fix file not exist panic in StaticFilter() 2023-07-30 19:03:21 +08:00
Yang Luo
55c522d3b7 Improve provider type input box 2023-07-30 17:31:36 +08:00
Yang Luo
f879170663 Remove AI related code 2023-07-30 14:39:27 +08:00
Yang Luo
12e5d9b583 Remove adapter.file 2023-07-30 12:08:05 +08:00
101 changed files with 5702 additions and 3845 deletions

141
ai/ai.go
View File

@@ -1,141 +0,0 @@
// 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 ai
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/sashabaranov/go-openai"
)
func queryAnswer(authToken string, question string, timeout int) (string, error) {
// fmt.Printf("Question: %s\n", question)
client := getProxyClientFromToken(authToken)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(2+timeout*2)*time.Second)
defer cancel()
resp, err := client.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: question,
},
},
},
)
if err != nil {
return "", err
}
res := resp.Choices[0].Message.Content
res = strings.Trim(res, "\n")
// fmt.Printf("Answer: %s\n\n", res)
return res, nil
}
func QueryAnswerSafe(authToken string, question string) string {
var res string
var err error
for i := 0; i < 10; i++ {
res, err = queryAnswer(authToken, question, i)
if err != nil {
if i > 0 {
fmt.Printf("\tFailed (%d): %s\n", i+1, err.Error())
}
} else {
break
}
}
if err != nil {
panic(err)
}
return res
}
func QueryAnswerStream(authToken string, question string, writer io.Writer, builder *strings.Builder) error {
client := getProxyClientFromToken(authToken)
ctx := context.Background()
flusher, ok := writer.(http.Flusher)
if !ok {
return fmt.Errorf("writer does not implement http.Flusher")
}
// https://platform.openai.com/tokenizer
// https://github.com/pkoukk/tiktoken-go#available-encodings
promptTokens, err := getTokenSize(openai.GPT3TextDavinci003, question)
if err != nil {
return err
}
// https://platform.openai.com/docs/models/gpt-3-5
maxTokens := 4097 - promptTokens
respStream, err := client.CreateCompletionStream(
ctx,
openai.CompletionRequest{
Model: openai.GPT3TextDavinci003,
Prompt: question,
MaxTokens: maxTokens,
Stream: true,
},
)
if err != nil {
return err
}
defer respStream.Close()
isLeadingReturn := true
for {
completion, streamErr := respStream.Recv()
if streamErr != nil {
if streamErr == io.EOF {
break
}
return streamErr
}
data := completion.Choices[0].Text
if isLeadingReturn && len(data) != 0 {
if strings.Count(data, "\n") == len(data) {
continue
} else {
isLeadingReturn = false
}
}
fmt.Printf("%s", data)
// Write the streamed data as Server-Sent Events
if _, err = fmt.Fprintf(writer, "event: message\ndata: %s\n\n", data); err != nil {
return err
}
flusher.Flush()
// Append the response to the strings.Builder
builder.WriteString(data)
}
return nil
}

View File

@@ -1,42 +0,0 @@
// 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.
//go:build !skipCi
// +build !skipCi
package ai
import (
"testing"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/sashabaranov/go-openai"
)
func TestRun(t *testing.T) {
object.InitConfig()
proxy.InitHttpClient()
text, err := queryAnswer("", "hi", 5)
if err != nil {
panic(err)
}
println(text)
}
func TestToken(t *testing.T) {
println(getTokenSize(openai.GPT3TextDavinci003, ""))
}

View File

@@ -29,15 +29,12 @@ var Enforcer *casbin.Enforcer
func InitApi() {
var err error
e, err := object.GetEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
if err != nil {
panic(err)
}
Enforcer, err = e.InitEnforcer()
e, err := object.GetInitializedEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
if err != nil {
panic(err)
}
Enforcer = e.Enforcer
Enforcer.ClearPolicy()
// if len(Enforcer.GetPolicy()) == 0 {

View File

@@ -35,6 +35,7 @@ func (c *ApiController) Enforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
enforcerId := c.Input().Get("enforcerId")
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
@@ -43,6 +44,23 @@ func (c *ApiController) Enforce() {
return
}
if enforcerId != "" {
enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
res, err := enforcer.Enforce(request...)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(res)
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
@@ -121,6 +139,7 @@ func (c *ApiController) Enforce() {
func (c *ApiController) BatchEnforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
enforcerId := c.Input().Get("enforcerId")
var requests []object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
@@ -129,6 +148,23 @@ func (c *ApiController) BatchEnforce() {
return
}
if enforcerId != "" {
enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
res, err := enforcer.BatchEnforce(requests)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(res)
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {

View File

@@ -1,145 +0,0 @@
// 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 := "admin"
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 == "" {
maskedChats, err := object.GetMaskedChats(object.GetChats(owner))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(maskedChats)
} else {
limit := util.ParseInt(limit)
count, err := object.GetChatCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
chats, err := object.GetMaskedChats(object.GetPaginationChats(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil {
c.ResponseError(err.Error())
return
}
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")
maskedChat, err := object.GetMaskedChat(object.GetChat(id))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(maskedChat)
}
// 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()
}

View File

@@ -1,313 +0,0 @@
// 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"
"fmt"
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/ai"
"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")
organization := c.Input().Get("organization")
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
var err error
if chat == "" {
messages, err = object.GetMessages(owner)
} else {
messages, err = object.GetChatMessages(chat)
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedMessages(messages))
} else {
limit := util.ParseInt(limit)
count, err := object.GetMessageCount(owner, organization, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
paginationMessages, err := object.GetPaginationMessages(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
messages := object.GetMaskedMessages(paginationMessages)
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")
message, err := object.GetMessage(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(message)
}
func (c *ApiController) ResponseErrorStream(errorText string) {
event := fmt.Sprintf("event: myerror\ndata: %s\n\n", errorText)
_, err := c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil {
c.ResponseError(err.Error())
return
}
}
// GetMessageAnswer
// @Title GetMessageAnswer
// @Tag Message API
// @Description get message answer
// @Param id query string true "The id ( owner/name ) of the message"
// @Success 200 {object} object.Message The Response object
// @router /get-message-answer [get]
func (c *ApiController) GetMessageAnswer() {
id := c.Input().Get("id")
c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
message, err := object.GetMessage(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if message == nil {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The message: %s is not found"), id))
return
}
if message.Author != "AI" || message.ReplyTo == "" || message.Text != "" {
c.ResponseErrorStream(c.T("chat:The message is invalid"))
return
}
chatId := util.GetId("admin", message.Chat)
chat, err := object.GetChat(chatId)
if err != nil {
c.ResponseError(err.Error())
return
}
if chat == nil || chat.Organization != message.Organization {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
return
}
if chat.Type != "AI" {
c.ResponseErrorStream(c.T("chat:The chat type must be \"AI\""))
return
}
questionMessage, err := object.GetMessage(message.ReplyTo)
if questionMessage == nil {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The message: %s is not found"), id))
return
}
providerId := util.GetId(chat.Owner, chat.User2)
provider, err := object.GetProvider(providerId)
if err != nil {
c.ResponseError(err.Error())
return
}
if provider == nil {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The provider: %s is not found"), providerId))
return
}
if provider.Category != "AI" || provider.ClientSecret == "" {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The provider: %s is invalid"), providerId))
return
}
c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
authToken := provider.ClientSecret
question := questionMessage.Text
var stringBuilder strings.Builder
fmt.Printf("Question: [%s]\n", questionMessage.Text)
fmt.Printf("Answer: [")
err = ai.QueryAnswerStream(authToken, question, c.Ctx.ResponseWriter, &stringBuilder)
if err != nil {
c.ResponseErrorStream(err.Error())
return
}
fmt.Printf("]\n")
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil {
c.ResponseError(err.Error())
return
}
answer := stringBuilder.String()
message.Text = answer
_, err = object.UpdateMessage(message.GetId(), message)
if err != nil {
c.ResponseError(err.Error())
return
}
}
// 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
}
var chat *object.Chat
if message.Chat != "" {
chatId := util.GetId("admin", message.Chat)
chat, err = object.GetChat(chatId)
if err != nil {
c.ResponseError(err.Error())
return
}
if chat == nil || chat.Organization != message.Organization {
c.ResponseError(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
return
}
}
affected, err := object.AddMessage(&message)
if err != nil {
c.ResponseError(err.Error())
return
}
if affected {
if chat != nil && chat.Type == "AI" {
answerMessage := &object.Message{
Owner: message.Owner,
Name: fmt.Sprintf("message_%s", util.GetRandomName()),
CreatedTime: util.GetCurrentTimeEx(message.CreatedTime),
Organization: message.Organization,
Chat: message.Chat,
ReplyTo: message.GetId(),
Author: "AI",
Text: "",
}
_, err = object.AddMessage(answerMessage)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}
c.Data["json"] = wrapActionResponse(affected)
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()
}

View File

@@ -52,7 +52,22 @@ func (c *ApiController) GetResources() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
if sortField == "Direct" {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
prefix := sortOrder
resources, err := object.GetDirectResources(owner, user, provider, prefix, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(resources)
} else if limit == "" || page == "" {
resources, err := object.GetResources(owner, user)
if err != nil {
c.ResponseError(err.Error())
@@ -152,11 +167,16 @@ func (c *ApiController) DeleteResource() {
return
}
if resource.Provider != "" {
c.Input().Set("provider", resource.Provider)
}
c.Input().Set("fullFilePath", resource.Name)
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
_, resource.Name = refineFullFilePath(resource.Name)
err = object.DeleteFile(provider, resource.Name, c.GetAcceptLanguage())
if err != nil {
@@ -216,6 +236,7 @@ func (c *ApiController) UploadResource() {
c.ResponseError(err.Error())
return
}
_, fullFilePath = refineFullFilePath(fullFilePath)
fileType := "unknown"
contentType := header.Header.Get("Content-Type")

View File

@@ -90,7 +90,7 @@ func (c *ApiController) GetUsers() {
if limit == "" || page == "" {
if groupName != "" {
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupName))
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
if err != nil {
c.ResponseError(err.Error())
return
@@ -567,6 +567,22 @@ func (c *ApiController) RemoveUserFromGroup() {
name := c.Ctx.Request.Form.Get("name")
groupName := c.Ctx.Request.Form.Get("groupName")
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupName))
c.ServeJSON()
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
return
}
item := object.GetAccountItemByName("Groups", organization)
res, msg := object.CheckAccountItemModifyRule(item, c.IsAdmin(), c.GetAcceptLanguage())
if !res {
c.ResponseError(msg)
return
}
affected, err := object.DeleteGroupForUser(util.GetId(owner, name), groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(affected)
}

View File

@@ -16,6 +16,7 @@ package controllers
import (
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
@@ -143,8 +144,30 @@ func (c *ApiController) IsMaskedEnabled() (bool, bool) {
return true, isMaskEnabled
}
func refineFullFilePath(fullFilePath string) (string, string) {
tokens := strings.Split(fullFilePath, "/")
if len(tokens) >= 2 && tokens[0] == "Direct" && tokens[1] != "" {
providerName := tokens[1]
res := strings.Join(tokens[2:], "/")
return providerName, "/" + res
} else {
return "", fullFilePath
}
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
providerName := c.Input().Get("provider")
if providerName == "" {
field := c.Input().Get("field")
value := c.Input().Get("value")
if field == "provider" && value != "" {
providerName = value
} else {
fullFilePath := c.Input().Get("fullFilePath")
providerName, _ = refineFullFilePath(fullFilePath)
}
}
if providerName != "" {
provider, err := object.GetProvider(util.GetId("admin", providerName))
if err != nil {

23
go.mod
View File

@@ -12,13 +12,12 @@ require (
github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casdoor/go-sms-sender v0.6.1
github.com/casdoor/go-sms-sender v0.12.0
github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/oss v1.2.0
github.com/casdoor/oss v1.3.0
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/dlclark/regexp2 v1.9.0 // indirect
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0
@@ -29,9 +28,6 @@ require (
github.com/go-sql-driver/mysql v1.6.0
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/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.7.3 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
@@ -42,21 +38,19 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5
github.com/pkoukk/tiktoken-go v0.1.1
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.2.0
github.com/prometheus/client_model v0.3.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0
github.com/sashabaranov/go-openai v1.12.0
github.com/satori/go.uuid v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.3
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect
@@ -64,12 +58,11 @@ require (
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.6.0
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/crypto v0.11.0
golang.org/x/net v0.13.0
golang.org/x/oauth2 v0.10.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
modernc.org/sqlite v1.18.2
)

1427
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,9 @@ func TestGenerateI18nFrontend(t *testing.T) {
applyToOtherLanguage("frontend", "ru", data)
applyToOtherLanguage("frontend", "vi", data)
applyToOtherLanguage("frontend", "pt", data)
applyToOtherLanguage("frontend", "it", data)
applyToOtherLanguage("frontend", "ms", data)
applyToOtherLanguage("frontend", "tr", data)
}
func TestGenerateI18nBackend(t *testing.T) {
@@ -49,4 +52,7 @@ func TestGenerateI18nBackend(t *testing.T) {
applyToOtherLanguage("backend", "ru", data)
applyToOtherLanguage("backend", "vi", data)
applyToOtherLanguage("backend", "pt", data)
applyToOtherLanguage("backend", "it", data)
applyToOtherLanguage("backend", "ms", data)
applyToOtherLanguage("backend", "tr", data)
}

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Zugehörigkeit darf nicht leer sein",
"DisplayName cannot be blank": "Anzeigename kann nicht leer sein",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Afiliación no puede estar en blanco",
"DisplayName cannot be blank": "El nombre de visualización no puede estar en blanco",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation ne peut pas être vide",
"DisplayName cannot be blank": "Le nom d'affichage ne peut pas être vide",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Keterkaitan tidak boleh kosong",
"DisplayName cannot be blank": "Nama Pengguna tidak boleh kosong",

150
i18n/locales/it/data.json Normal file
View File

@@ -0,0 +1,150 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"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",
"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",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"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",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "サービス%sと%sは一致しません"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "所属は空白にできません",
"DisplayName cannot be blank": "表示名は空白にできません",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "소속은 비워 둘 수 없습니다",
"DisplayName cannot be blank": "DisplayName는 비어 있을 수 없습니다",

150
i18n/locales/ms/data.json Normal file
View File

@@ -0,0 +1,150 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"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",
"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",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"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",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Принадлежность не может быть пустым значением",
"DisplayName cannot be blank": "Имя отображения не может быть пустым",

150
i18n/locales/tr/data.json Normal file
View File

@@ -0,0 +1,150 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"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",
"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",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"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",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Tình trạng liên kết không thể để trống",
"DisplayName cannot be blank": "Tên hiển thị không thể để trống",

View File

@@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "服务%s与%s不匹配"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "工作单位不可为空",
"DisplayName cannot be blank": "显示名称不可为空",

View File

@@ -132,7 +132,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
"type": "User",
"blog": null,
"weibo": null,
"bio": "个人博客https://gitee.com/xxx/xxx/pages",
"bio": "bio",
"public_repos": 2,
"public_gists": 0,
"followers": 0,

View File

@@ -9,11 +9,11 @@
"passwordType": "plain",
"passwordSalt": "",
"passwordOptions": ["AtLeast6"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR"],
"defaultAvatar": "",
"defaultApplication": "",
"tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi"],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr"],
"masterPassword": "",
"initScore": 2000,
"enableSoftDeletion": false,

View File

@@ -49,6 +49,7 @@ func main() {
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
authz.InitApi()
object.InitUserManager()
util.SafeGoroutine(func() { object.RunSyncUsersJob() })

View File

@@ -23,6 +23,7 @@ import (
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core"
"github.com/xorm-io/xorm"
)
type Adapter struct {
@@ -33,13 +34,12 @@ type Adapter struct {
Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Host string `xorm:"varchar(100)" json:"host"`
Port string `xorm:"varchar(20)" json:"port"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"`
File string `xorm:"varchar(100)" json:"file"`
IsEnabled bool `json:"isEnabled"`
@@ -154,24 +154,27 @@ func (adapter *Adapter) initAdapter() error {
if adapter.Adapter == nil {
var dataSourceName string
if adapter.buildInAdapter() {
if adapter.builtInAdapter() {
dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database
}
} else {
switch adapter.DatabaseType {
case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%s?database=%s", adapter.User,
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%s)/", adapter.User,
adapter.Password, adapter.Host, adapter.Port)
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=disable dbname=%s", adapter.User,
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "CockroachDB":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=disable dbname=%s serial_normalization=virtual_sequence",
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "sqlite3":
dataSourceName = fmt.Sprintf("file:%s", adapter.File)
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
default:
return fmt.Errorf("unsupported database type: %s", adapter.DatabaseType)
}
@@ -182,7 +185,8 @@ func (adapter *Adapter) initAdapter() error {
}
var err error
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(NewAdapter(adapter.DatabaseType, dataSourceName, adapter.Database).Engine, adapter.getTable(), adapter.TableNamePrefix)
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
if err != nil {
return err
}
@@ -270,6 +274,11 @@ func UpdatePolicy(oldPolicy, newPolicy []string, adapter *Adapter) (bool, error)
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
@@ -286,6 +295,10 @@ func AddPolicy(policy []string, adapter *Adapter) (bool, error) {
}
casbinModel.AddPolicy("p", "p", policy)
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return true, nil
}
@@ -306,15 +319,20 @@ func RemovePolicy(policy []string, adapter *Adapter) (bool, error) {
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
func (adapter *Adapter) buildInAdapter() bool {
func (adapter *Adapter) builtInAdapter() bool {
if adapter.Owner != "built-in" {
return false
}
return adapter.Name == "permission-adapter-built-in" || adapter.Name == "api-adapter-built-in"
return adapter.Name == "user-adapter-built-in" || adapter.Name == "api-adapter-built-in"
}
func getModelDef() model.Model {

View File

@@ -162,7 +162,7 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
if name != cert.Name {
err := certChangeTrigger(name, cert.Name)
if err != nil {
return false, nil
return false, err
}
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)

View File

@@ -1,166 +0,0 @@
// 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, err ...error) (*Chat, error) {
if len(err) > 0 && err[0] != nil {
return nil, err[0]
}
if chat == nil {
return nil, nil
}
return chat, nil
}
func GetMaskedChats(chats []*Chat, errs ...error) ([]*Chat, error) {
if len(errs) > 0 && errs[0] != nil {
return nil, errs[0]
}
var err error
for _, chat := range chats {
chat, err = GetMaskedChat(chat)
if err != nil {
return nil, err
}
}
return chats, nil
}
func GetChatCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Chat{})
}
func GetChats(owner string) ([]*Chat, error) {
chats := []*Chat{}
err := ormer.Engine.Desc("created_time").Find(&chats, &Chat{Owner: owner})
if err != nil {
return chats, err
}
return chats, nil
}
func GetPaginationChats(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Chat, error) {
chats := []*Chat{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&chats)
if err != nil {
return chats, err
}
return chats, nil
}
func getChat(owner string, name string) (*Chat, error) {
if owner == "" || name == "" {
return nil, nil
}
chat := Chat{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&chat)
if err != nil {
return &chat, err
}
if existed {
return &chat, nil
} else {
return nil, nil
}
}
func GetChat(id string) (*Chat, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getChat(owner, name)
}
func UpdateChat(id string, chat *Chat) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if c, err := getChat(owner, name); err != nil {
return false, err
} else if c == nil {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(chat)
if err != nil {
return false, nil
}
return affected != 0, nil
}
func AddChat(chat *Chat) (bool, error) {
if chat.Type == "AI" && chat.User2 == "" {
provider, err := getDefaultAiProvider()
if err != nil {
return false, err
}
if provider != nil {
chat.User2 = provider.Name
}
}
affected, err := ormer.Engine.Insert(chat)
if err != nil {
return false, nil
}
return affected != 0, nil
}
func DeleteChat(chat *Chat) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{chat.Owner, chat.Name}).Delete(&Chat{})
if err != nil {
return false, err
}
if affected != 0 {
return DeleteChatMessages(chat.Name)
}
return affected != 0, nil
}
func (p *Chat) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

View File

@@ -15,7 +15,7 @@
package object
import (
"errors"
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casdoor/casdoor/util"
@@ -120,44 +120,65 @@ func DeleteEnforcer(enforcer *Enforcer) (bool, error) {
return affected != 0, nil
}
func (enforcer *Enforcer) InitEnforcer() (*casbin.Enforcer, error) {
if enforcer == nil {
return nil, errors.New("enforcer is nil")
}
if enforcer.Model == "" || enforcer.Adapter == "" {
return nil, errors.New("missing model or adapter")
func (p *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func (enforcer *Enforcer) InitEnforcer() error {
if enforcer.Enforcer != nil {
return nil
}
var err error
var m *Model
var a *Adapter
if enforcer.Model == "" {
return fmt.Errorf("the model for enforcer: %s should not be empty", enforcer.GetId())
}
if enforcer.Adapter == "" {
return fmt.Errorf("the adapter for enforcer: %s should not be empty", enforcer.GetId())
}
if m, err = GetModel(enforcer.Model); err != nil {
return nil, err
m, err := GetModel(enforcer.Model)
if err != nil {
return err
} else if m == nil {
return nil, errors.New("model not found")
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
}
if a, err = GetAdapter(enforcer.Adapter); err != nil {
return nil, err
a, err := GetAdapter(enforcer.Adapter)
if err != nil {
return err
} else if a == nil {
return nil, errors.New("adapter not found")
return fmt.Errorf("the adapter: %s for enforcer: %s is not found", enforcer.Adapter, enforcer.GetId())
}
err = m.initModel()
if err != nil {
return nil, err
return err
}
err = a.initAdapter()
if err != nil {
return nil, err
return err
}
e, err := casbin.NewEnforcer(m.Model, a.Adapter)
casbinEnforcer, err := casbin.NewEnforcer(m.Model, a.Adapter)
if err != nil {
return err
}
enforcer.Enforcer = casbinEnforcer
return nil
}
func GetInitializedEnforcer(enforcerId string) (*Enforcer, error) {
enforcer, err := GetEnforcer(enforcerId)
if err != nil {
return nil, err
} else if enforcer == nil {
return nil, fmt.Errorf("the enforcer: %s is not found", enforcerId)
}
err = enforcer.InitEnforcer()
if err != nil {
return nil, err
}
return e, nil
return enforcer, nil
}

View File

@@ -164,7 +164,7 @@ func DeleteGroup(group *Group) (bool, error) {
return false, errors.New("group has children group")
}
if count, err := GetGroupUserCount(group.Name, "", ""); err != nil {
if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil {
return false, err
} else if count > 0 {
return false, errors.New("group has users")
@@ -214,39 +214,33 @@ func ConvertToTreeData(groups []*Group, parentId string) []*Group {
return treeData
}
func RemoveUserFromGroup(owner, name, groupName string) (bool, error) {
user, err := getUser(owner, name)
func GetGroupUserCount(groupId string, field, value string) (int64, error) {
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return false, err
}
if user == nil {
return false, errors.New("user not exist")
return 0, err
}
user.Groups = util.DeleteVal(user.Groups, groupName)
affected, err := updateUser(user.GetId(), user, []string{"groups"})
if err != nil {
return false, err
}
return affected != 0, err
}
func GetGroupUserCount(groupName string, field, value string) (int64, error) {
if field == "" && value == "" {
return ormer.Engine.Where(builder.Like{"`groups`", groupName}).
Count(&User{})
return int64(len(names)), nil
} else {
return ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName}).
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
func GetPaginationGroupUsers(groupName string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{}
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return nil, err
}
session := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName + "\""})
Where("owner = ?", owner).In("name", names)
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
@@ -265,7 +259,7 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
}
err := session.Find(&users)
err = session.Find(&users)
if err != nil {
return nil, err
}
@@ -273,15 +267,15 @@ func GetPaginationGroupUsers(groupName string, offset, limit int, field, value,
return users, nil
}
func GetGroupUsers(groupName string) ([]*User, error) {
func GetGroupUsers(groupId string) ([]*User, error) {
users := []*User{}
err := ormer.Engine.Table("user").
Where(builder.Like{"`groups`", groupName + "\""}).
Find(&users)
owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
if err != nil {
return nil, err
}
return users, nil
}

View File

@@ -37,11 +37,11 @@ func InitDb() {
existed = initBuiltInApiModel()
if !existed {
initBuildInApiAdapter()
initBuiltInApiAdapter()
initBuiltInApiEnforcer()
initBuiltInPermissionModel()
initBuildInPermissionAdapter()
initBuiltInPermissionEnforcer()
initBuiltInUserModel()
initBuiltInUserAdapter()
initBuiltInUserEnforcer()
}
initWebAuthn()
@@ -303,8 +303,8 @@ func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}
func initBuiltInPermissionModel() {
model, err := GetModel("built-in/permission-model-built-in")
func initBuiltInUserModel() {
model, err := GetModel("built-in/user-model-built-in")
if err != nil {
panic(err)
}
@@ -315,7 +315,7 @@ func initBuiltInPermissionModel() {
model = &Model{
Owner: "built-in",
Name: "permission-model-built-in",
Name: "user-model-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Model",
IsEnabled: true,
@@ -325,11 +325,14 @@ r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`,
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`,
}
_, err = AddModel(model)
if err != nil {
@@ -347,8 +350,7 @@ func initBuiltInApiModel() bool {
return true
}
modelText := `
[request_definition]
modelText := `[request_definition]
r = subOwner, subName, method, urlPath, objOwner, objName
[policy_definition]
@@ -367,8 +369,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(r.urlPath == p.urlPath || p.urlPath == "*") && \
(r.objOwner == p.objOwner || p.objOwner == "*") && \
(r.objName == p.objName || p.objName == "*") || \
(r.subOwner == r.objOwner && r.subName == r.objName)
`
(r.subOwner == r.objOwner && r.subName == r.objName)`
model = &Model{
Owner: "built-in",
@@ -415,19 +416,19 @@ func initBuiltInPermission() {
}
}
func initBuildInPermissionAdapter() {
permissionAdapter, err := GetAdapter("built-in/permission-adapter-built-in")
func initBuiltInUserAdapter() {
adapter, err := GetAdapter("built-in/user-adapter-built-in")
if err != nil {
panic(err)
}
if permissionAdapter != nil {
if adapter != nil {
return
}
permissionAdapter = &Adapter{
adapter = &Adapter{
Owner: "built-in",
Name: "permission-adapter-built-in",
Name: "user-adapter-built-in",
CreatedTime: util.GetCurrentTime(),
Type: "Database",
DatabaseType: conf.GetConfigString("driverName"),
@@ -436,23 +437,23 @@ func initBuildInPermissionAdapter() {
Table: "casbin_user_rule",
IsEnabled: true,
}
_, err = AddAdapter(permissionAdapter)
_, err = AddAdapter(adapter)
if err != nil {
panic(err)
}
}
func initBuildInApiAdapter() {
apiAdapter, err := GetAdapter("built-in/api-adapter-built-in")
func initBuiltInApiAdapter() {
adapter, err := GetAdapter("built-in/api-adapter-built-in")
if err != nil {
panic(err)
}
if apiAdapter != nil {
if adapter != nil {
return
}
apiAdapter = &Adapter{
adapter = &Adapter{
Owner: "built-in",
Name: "api-adapter-built-in",
CreatedTime: util.GetCurrentTime(),
@@ -463,49 +464,49 @@ func initBuildInApiAdapter() {
Table: "casbin_api_rule",
IsEnabled: true,
}
_, err = AddAdapter(apiAdapter)
_, err = AddAdapter(adapter)
if err != nil {
panic(err)
}
}
func initBuiltInPermissionEnforcer() {
permissionEnforcer, err := GetEnforcer("built-in/permission-enforcer-built-in")
func initBuiltInUserEnforcer() {
enforcer, err := GetEnforcer("built-in/user-enforcer-built-in")
if err != nil {
panic(err)
}
if permissionEnforcer != nil {
if enforcer != nil {
return
}
permissionEnforcer = &Enforcer{
enforcer = &Enforcer{
Owner: "built-in",
Name: "permission-enforcer-built-in",
Name: "user-enforcer-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Permission Enforcer",
Model: "built-in/permission-model-built-in",
Adapter: "built-in/permission-adapter-built-in",
Model: "built-in/user-model-built-in",
Adapter: "built-in/user-adapter-built-in",
IsEnabled: true,
}
_, err = AddEnforcer(permissionEnforcer)
_, err = AddEnforcer(enforcer)
if err != nil {
panic(err)
}
}
func initBuiltInApiEnforcer() {
apiEnforcer, err := GetEnforcer("built-in/api-enforcer-built-in")
enforcer, err := GetEnforcer("built-in/api-enforcer-built-in")
if err != nil {
panic(err)
}
if apiEnforcer != nil {
if enforcer != nil {
return
}
apiEnforcer = &Enforcer{
enforcer = &Enforcer{
Owner: "built-in",
Name: "api-enforcer-built-in",
CreatedTime: util.GetCurrentTime(),
@@ -515,7 +516,7 @@ func initBuiltInApiEnforcer() {
IsEnabled: true,
}
_, err = AddEnforcer(apiEnforcer)
_, err = AddEnforcer(enforcer)
if err != nil {
panic(err)
}

View File

@@ -41,6 +41,7 @@ type LdapUser struct {
GidNumber string `json:"gidNumber"`
// Gcn string
Uuid string `json:"uuid"`
UserPrincipalName string `json:"userPrincipalName"`
DisplayName string `json:"displayName"`
Mail string
Email string `json:"email"`
@@ -51,9 +52,10 @@ type LdapUser struct {
RegisteredAddress string
PostalAddress string
GroupId string `json:"groupId"`
Phone string `json:"phone"`
Address string `json:"address"`
GroupId string `json:"groupId"`
Phone string `json:"phone"`
Address string `json:"address"`
MemberOf string `json:"memberOf"`
}
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
@@ -168,6 +170,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.Uuid = attribute.Values[0]
case "objectGUID":
user.Uuid = attribute.Values[0]
case "userPrincipalName":
user.UserPrincipalName = attribute.Values[0]
case "displayName":
user.DisplayName = attribute.Values[0]
case "mail":
@@ -186,6 +190,8 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
user.RegisteredAddress = attribute.Values[0]
case "postalAddress":
user.PostalAddress = attribute.Values[0]
case "memberOf":
user.MemberOf = attribute.Values[0]
}
}
ldapUsers = append(ldapUsers, user)

View File

@@ -1,143 +0,0 @@
// 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"`
ReplyTo string `xorm:"varchar(100) index" json:"replyTo"`
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, organization, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Message{Organization: organization})
}
func GetMessages(owner string) ([]*Message, error) {
messages := []*Message{}
err := ormer.Engine.Desc("created_time").Find(&messages, &Message{Owner: owner})
return messages, err
}
func GetChatMessages(chat string) ([]*Message, error) {
messages := []*Message{}
err := ormer.Engine.Asc("created_time").Find(&messages, &Message{Chat: chat})
return messages, err
}
func GetPaginationMessages(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Message, error) {
messages := []*Message{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&messages, &Message{Organization: organization})
return messages, err
}
func getMessage(owner string, name string) (*Message, error) {
if owner == "" || name == "" {
return nil, nil
}
message := Message{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&message)
if err != nil {
return nil, err
}
if existed {
return &message, nil
} else {
return nil, nil
}
}
func GetMessage(id string) (*Message, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getMessage(owner, name)
}
func UpdateMessage(id string, message *Message) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if m, err := getMessage(owner, name); err != nil {
return false, err
} else if m == nil {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(message)
if err != nil {
return false, err
}
return affected != 0, nil
}
func AddMessage(message *Message) (bool, error) {
affected, err := ormer.Engine.Insert(message)
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteMessage(message *Message) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{message.Owner, message.Name}).Delete(&Message{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteChatMessages(chat string) (bool, error) {
affected, err := ormer.Engine.Delete(&Message{Chat: chat})
if err != nil {
return false, err
}
return affected != 0, nil
}
func (p *Message) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

View File

@@ -189,7 +189,7 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
if name != organization.Name {
err := organizationChangeTrigger(name, organization.Name)
if err != nil {
return false, nil
return false, err
}
}
@@ -432,7 +432,7 @@ func organizationChangeTrigger(oldName string, newName string) error {
payment := new(Payment)
payment.Owner = newName
_, err = session.Where("organization=?", oldName).Update(payment)
_, err = session.Where("owner=?", oldName).Update(payment)
if err != nil {
return err
}

View File

@@ -249,16 +249,6 @@ func (a *Ormer) 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)

View File

@@ -186,20 +186,23 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
if err != nil {
return payment, notifyResult, err
return payment, nil, err
}
if notifyResult.PaymentStatus != pp.PaymentStatePaid {
return payment, notifyResult, nil
}
// Only check paid payment
if notifyResult.ProductDisplayName != "" && notifyResult.ProductDisplayName != product.DisplayName {
err = fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", notifyResult.ProductDisplayName, product.DisplayName)
return payment, notifyResult, err
return payment, nil, err
}
if notifyResult.Price != product.Price {
err = fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", notifyResult.Price, product.Price)
return payment, notifyResult, err
return payment, nil, err
}
return payment, notifyResult, err
return payment, notifyResult, nil
}
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) {
@@ -210,6 +213,7 @@ func NotifyPayment(request *http.Request, body []byte, owner string, paymentName
payment.Message = err.Error()
} else {
payment.State = notifyResult.PaymentStatus
payment.Message = notifyResult.NotifyMessage
}
_, err = UpdatePayment(payment.GetId(), payment)
if err != nil {

View File

@@ -58,10 +58,7 @@ type PermissionRule struct {
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
}
const (
builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
builtInAdapter = "permission_rule"
)
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name)
@@ -290,7 +287,7 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
for _, role := range roles {
perms := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+role.Name+"\"%").Find(&perms)
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
if err != nil {
return nil, nil, err
}

View File

@@ -335,7 +335,7 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
fieldsNum := len(policyDefinition)
if fieldsNum > builtInAvailableField {
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d", builtInAvailableField))
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum))
}
// filled empty field with "" and V5 with "permissionId"
for i := builtInAvailableField - fieldsNum; i > 0; i-- {

View File

@@ -182,20 +182,6 @@ func GetProvider(id string) (*Provider, error) {
return getProvider(owner, name)
}
func getDefaultAiProvider() (*Provider, error) {
provider := Provider{Owner: "admin", Category: "AI"}
existed, err := ormer.Engine.Get(&provider)
if err != nil {
return &provider, err
}
if !existed {
return nil, nil
}
return &provider, nil
}
func GetWechatMiniProgramProvider(application *Application) *Provider {
providers := application.Providers
for _, provider := range providers {
@@ -217,7 +203,7 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
if name != provider.Name {
err := providerChangeTrigger(name, provider.Name)
if err != nil {
return false, nil
return false, err
}
}
@@ -268,7 +254,8 @@ func DeleteProvider(provider *Provider) (bool, error) {
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
cert := &Cert{}
if p.Cert != "" {
cert, err := getCert(p.Owner, p.Cert)
var err error
cert, err = getCert(p.Owner, p.Cert)
if err != nil {
return nil, nil, err
}

View File

@@ -46,7 +46,7 @@ func GetResourceCount(owner, user, field, value string) (int64, error) {
}
func GetResources(owner string, user string) ([]*Resource, error) {
if owner == "built-in" {
if owner == "built-in" || owner == "" {
owner = ""
user = ""
}
@@ -61,7 +61,7 @@ func GetResources(owner string, user string) ([]*Resource, error) {
}
func GetPaginationResources(owner, user string, offset, limit int, field, value, sortField, sortOrder string) ([]*Resource, error) {
if owner == "built-in" {
if owner == "built-in" || owner == "" {
owner = ""
user = ""
}

46
object/resource_direct.go Normal file
View File

@@ -0,0 +1,46 @@
// 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 (
"strings"
"time"
"github.com/casdoor/casdoor/util"
)
func GetDirectResources(owner string, user string, provider *Provider, prefix string, lang string) ([]*Resource, error) {
storageProvider, err := getStorageProvider(provider, lang)
if err != nil {
return nil, err
}
res := []*Resource{}
objects, err := storageProvider.List(prefix)
for _, obj := range objects {
resource := &Resource{
Owner: owner,
Name: strings.TrimPrefix(obj.Path, "/"),
CreatedTime: obj.LastModified.Local().Format(time.RFC3339),
User: user,
Provider: "",
Application: "",
FileSize: int(obj.Size),
Url: util.UrlJoin(provider.Domain, obj.Path),
}
res = append(res, resource)
}
return res, err
}

View File

@@ -133,7 +133,7 @@ func UpdateRole(id string, role *Role) (bool, error) {
if name != role.Name {
err := roleChangeTrigger(name, role.Name)
if err != nil {
return false, nil
return false, err
}
}

View File

@@ -24,7 +24,7 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
var client sender.SmsClient
var err error
if provider.Type == sender.HuaweiCloud {
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)

View File

@@ -25,6 +25,7 @@ type TableColumn struct {
Name string `json:"name"`
Type string `json:"type"`
CasdoorName string `json:"casdoorName"`
IsKey bool `json:"isKey"`
IsHashed bool `json:"isHashed"`
Values []string `json:"values"`
}

View File

@@ -29,6 +29,19 @@ const (
UserPropertiesWechatOpenId = "wechatOpenId"
)
const UserEnforcerId = "built-in/user-enforcer-built-in"
var userEnforcer *UserGroupEnforcer
func InitUserManager() {
enforcer, err := GetInitializedEnforcer(UserEnforcerId)
if err != nil {
panic(err)
}
userEnforcer = NewUserGroupEnforcer(enforcer.Enforcer)
}
type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@@ -230,7 +243,7 @@ func GetUserCount(owner, field, value string, groupName string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
if groupName != "" {
return GetGroupUserCount(groupName, field, value)
return GetGroupUserCount(util.GetId(owner, groupName), field, value)
}
return session.Count(&User{})
@@ -274,7 +287,7 @@ func GetPaginationUsers(owner string, offset, limit int, field, value, sortField
users := []*User{}
if groupName != "" {
return GetPaginationGroupUsers(groupName, offset, limit, field, value, sortField, sortOrder)
return GetPaginationGroupUsers(util.GetId(owner, groupName), offset, limit, field, value, sortField, sortOrder)
}
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
@@ -531,6 +544,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
columns = append(columns, "name", "email", "phone", "country_code")
}
if util.ContainsString(columns, "groups") {
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
if err != nil {
return false, err
}
}
affected, err := updateUser(id, user, columns)
if err != nil {
return false, err
@@ -568,7 +588,7 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false, nil
return false, err
}
}
@@ -778,6 +798,10 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return
}
func DeleteGroupForUser(user string, group string) (bool, error) {
return userEnforcer.DeleteGroupForUser(user, group)
}
func userChangeTrigger(oldName string, newName string) error {
session := ormer.Engine.NewSession()
defer session.Close()

View File

@@ -69,6 +69,8 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
fileExtension = ".ico"
case "image/x-icon":
fileExtension = ".ico"
case "image/svg+xml":
fileExtension = ".svg"
default:
return nil, "", fmt.Errorf("unsupported content type: %s", contentType)
}

95
object/user_enforcer.go Normal file
View File

@@ -0,0 +1,95 @@
package object
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/errors"
"github.com/casdoor/casdoor/util"
)
type UserGroupEnforcer struct {
// use rbac model implement use group, the enforcer can also implement user role
enforcer *casbin.Enforcer
}
func NewUserGroupEnforcer(enforcer *casbin.Enforcer) *UserGroupEnforcer {
return &UserGroupEnforcer{
enforcer: enforcer,
}
}
func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) {
return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) {
g := make([]string, len(groups))
for i, group := range groups {
g[i] = GetGroupWithPrefix(group)
}
return e.enforcer.AddRolesForUser(user, g)
}
func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) {
return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group))
}
func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) {
return e.enforcer.DeleteRolesForUser(user)
}
func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
groups, err := e.enforcer.GetRolesForUser(user)
for i, group := range groups {
groups[i] = GetGroupWithoutPrefix(group)
}
return groups, err
}
func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
if err != nil {
if err == errors.ERR_NAME_NOT_FOUND {
return []string{}, nil
}
return nil, err
}
return users, nil
}
func GetGroupWithPrefix(group string) string {
return "group:" + group
}
func GetGroupWithoutPrefix(group string) string {
return group[len("group:"):]
}
func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) {
var names []string
userIds, err := e.GetAllUsersByGroup(groupName)
if err != nil {
return nil, err
}
for _, userId := range userIds {
_, name := util.GetOwnerAndNameFromIdNoCheck(userId)
names = append(names, name)
}
return names, nil
}
func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) {
_, err := e.DeleteGroupsForUser(user)
if err != nil {
return false, err
}
affected, err := e.AddGroupsForUser(user, groups)
if err != nil {
return false, err
}
return affected, nil
}

View File

@@ -17,6 +17,7 @@ package pp
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
@@ -89,14 +90,24 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
}
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{}
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
if err != nil {
return nil, err
}
if captureRsp.Code != paypal.Success {
errDetail := captureRsp.ErrorResponse.Details[0]
switch errDetail.Issue {
// If order is already captured, just skip this type of error and check the order detail
if !(len(captureRsp.ErrorResponse.Details) == 1 && captureRsp.ErrorResponse.Details[0].Issue == "ORDER_ALREADY_CAPTURED") {
return nil, errors.New(captureRsp.ErrorResponse.Message)
case "ORDER_ALREADY_CAPTURED":
// skip
case "ORDER_NOT_APPROVED":
notifyResult.PaymentStatus = PaymentStateCanceled
notifyResult.NotifyMessage = errDetail.Description
return notifyResult, nil
default:
err = fmt.Errorf(errDetail.Description)
return nil, err
}
}
// Check the order detail
@@ -105,7 +116,16 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
return nil, err
}
if captureRsp.Code != paypal.Success {
return nil, errors.New(captureRsp.ErrorResponse.Message)
errDetail := captureRsp.ErrorResponse.Details[0]
switch errDetail.Issue {
case "ORDER_NOT_APPROVED":
notifyResult.PaymentStatus = PaymentStateCanceled
notifyResult.NotifyMessage = errDetail.Description
return notifyResult, nil
default:
err = fmt.Errorf(errDetail.Description)
return nil, err
}
}
paymentName := detailRsp.Response.Id
@@ -126,7 +146,7 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
default:
paymentStatus = PaymentStateError
}
notifyResult := &NotifyResult{
notifyResult = &NotifyResult{
PaymentStatus: paymentStatus,
PaymentName: paymentName,

View File

@@ -21,16 +21,18 @@ import (
type PaymentState string
const (
PaymentStatePaid PaymentState = "Paid"
PaymentStateCreated PaymentState = "Created"
PaymentStateError PaymentState = "Error"
PaymentStatePaid PaymentState = "Paid"
PaymentStateCreated PaymentState = "Created"
PaymentStateCanceled PaymentState = "Canceled"
PaymentStateError PaymentState = "Error"
)
type NotifyResult struct {
PaymentName string
PaymentStatus PaymentState
ProviderName string
NotifyMessage string
ProviderName string
ProductName string
ProductDisplayName string
Price float64

View File

@@ -211,19 +211,6 @@ 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/get-message-answer", &controllers.ApiController{}, "GET:GetMessageAnswer")
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-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")

View File

@@ -55,15 +55,12 @@ func StaticFilter(ctx *context.Context) {
path += urlPath
}
path2 := strings.TrimPrefix(path, "web/build/images/")
if util.FileExist(path2) {
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path2)
return
}
if !util.FileExist(path) {
path = "web/build/index.html"
}
if !util.FileExist(path) {
return
}
if oldStaticBaseUrl == newStaticBaseUrl {
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path)

View File

@@ -12,17 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package ai
package storage
import (
"github.com/casdoor/casdoor/proxy"
"github.com/sashabaranov/go-openai"
"github.com/casdoor/oss"
"github.com/casdoor/oss/googlecloud"
)
func getProxyClientFromToken(authToken string) *openai.Client {
config := openai.DefaultConfig(authToken)
config.HTTPClient = proxy.ProxyHttpClient
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
sp, _ := googlecloud.New(&googlecloud.Config{
AccessID: clientId,
AccessKey: clientSecret,
Bucket: bucket,
Endpoint: endpoint,
})
c := openai.NewClientWithConfig(config)
return c
return sp
}

View File

@@ -12,17 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package ai
package storage
import "github.com/pkoukk/tiktoken-go"
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/qiniu"
)
func getTokenSize(model string, prompt string) (int, error) {
tkm, err := tiktoken.EncodingForModel(model)
if err != nil {
return 0, err
}
func NewQiniuCloudKodoStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := qiniu.New(&qiniu.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
})
token := tkm.Encode(prompt, nil, nil)
res := len(token)
return res, nil
return sp
}

View File

@@ -30,6 +30,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Azure Blob":
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Qiniu Cloud Kodo":
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Google Cloud Storage":
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
}
return nil

View File

@@ -25,5 +25,12 @@ func CasbinToSlice(casbinRule xormadapter.CasbinRule) []string {
casbinRule.V4,
casbinRule.V5,
}
// remove empty strings from end, for update model policy map
for i := len(s) - 1; i >= 0; i-- {
if s[i] != "" {
s = s[:i+1]
break
}
}
return s
}

View File

@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/cssinjs": "^1.8.1",
"@ant-design/cssinjs": "1.16.1",
"@ant-design/icons": "^4.7.0",
"@craco/craco": "^6.4.5",
"@crowdin/cli": "^3.7.10",

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
import * as AdapterBackend from "./backend/AdapterBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
@@ -91,64 +91,47 @@ class AdapterEditPage extends React.Component {
return null;
}
return (
<React.Fragment>{
this.state.adapter.databaseType === "sqlite3" ?
(
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:File"), i18next.t("provider:File - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.host} onChange={e => {
this.updateAdapterField("file", e.target.value);
}} />
</Col>
</Row>
) : (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.host} onChange={e => {
this.updateAdapterField("host", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.port} onChange={e => {
this.updateAdapterField("port", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.user} onChange={e => {
this.updateAdapterField("user", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.password} onChange={e => {
this.updateAdapterField("password", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
)
}
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.host} onChange={e => {
this.updateAdapterField("host", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
this.updateAdapterField("port", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.user} onChange={e => {
this.updateAdapterField("user", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.adapter.password} onChange={e => {
this.updateAdapterField("password", e.target.value);
}} />
</Col>
</Row>
</React.Fragment>
);
}

View File

@@ -32,7 +32,7 @@ class AdapterListPage extends BaseListPage {
createdTime: moment().format(),
type: "Database",
host: "localhost",
port: "3306",
port: 3306,
user: "root",
password: "123456",
databaseType: "mysql",

View File

@@ -18,7 +18,7 @@ import {Helmet} from "react-helmet";
import {MfaRuleRequired} from "./Setting";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {AppstoreTwoTone, BarsOutlined, DollarTwoTone, DownOutlined, HomeTwoTone, InfoCircleFilled, LockTwoTone, LogoutOutlined, SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone, WalletTwoTone} 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";
@@ -56,11 +56,6 @@ import PricingListPage from "./PricingListPage";
import PricingEditPage from "./PricingEditPage";
import PlanListPage from "./PlanListPage";
import PlanEditPage from "./PlanEditPage";
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";
@@ -96,6 +91,10 @@ import AccountAvatar from "./account/AccountAvatar";
const {Header, Footer, Content} = Layout;
import {setTwoToneColor} from "@ant-design/icons";
setTwoToneColor("rgb(87,52,211)");
class App extends Component {
constructor(props) {
super(props);
@@ -152,62 +151,24 @@ class App extends Component {
});
if (uri === "/") {
this.setState({selectedMenuKey: "/"});
} else if (uri.includes("/organizations") || uri.includes("/trees")) {
this.setState({selectedMenuKey: "/organizations"});
} else if (uri.includes("/users")) {
this.setState({selectedMenuKey: "/users"});
} else if (uri.includes("/groups")) {
this.setState({selectedMenuKey: "/groups"});
} else if (uri.includes("/roles")) {
this.setState({selectedMenuKey: "/roles"});
} else if (uri.includes("/permissions")) {
this.setState({selectedMenuKey: "/permissions"});
} else if (uri.includes("/models")) {
this.setState({selectedMenuKey: "/models"});
} else if (uri.includes("/adapters")) {
this.setState({selectedMenuKey: "/adapters"});
} else if (uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/enforcers"});
} else if (uri.includes("/providers")) {
this.setState({selectedMenuKey: "/providers"});
} else if (uri.includes("/applications")) {
this.setState({selectedMenuKey: "/applications"});
} else if (uri.includes("/resources")) {
this.setState({selectedMenuKey: "/resources"});
} else if (uri.includes("/records")) {
this.setState({selectedMenuKey: "/records"});
} else if (uri.includes("/tokens")) {
this.setState({selectedMenuKey: "/tokens"});
} else if (uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/sessions"});
} else if (uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/webhooks"});
} else if (uri.includes("/syncers")) {
this.setState({selectedMenuKey: "/syncers"});
} else if (uri.includes("/certs")) {
this.setState({selectedMenuKey: "/certs"});
} else if (uri.includes("/chats")) {
this.setState({selectedMenuKey: "/chats"});
} else if (uri.includes("/messages")) {
this.setState({selectedMenuKey: "/messages"});
} else if (uri.includes("/products")) {
this.setState({selectedMenuKey: "/products"});
} else if (uri.includes("/payments")) {
this.setState({selectedMenuKey: "/payments"});
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/users") || uri.includes("/groups")) {
this.setState({selectedMenuKey: "/orgs"});
} else if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {
this.setState({selectedMenuKey: "/auth"});
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
this.setState({selectedMenuKey: "/identity"});
} else if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions")) {
this.setState({selectedMenuKey: "/logs"});
} else if (uri.includes("/products") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions")) {
this.setState({selectedMenuKey: "/business"});
} else if (uri.includes("/sysinfo") || uri.includes("/syncers") || uri.includes("/webhooks")) {
this.setState({selectedMenuKey: "/admin"});
} else if (uri.includes("/signup")) {
this.setState({selectedMenuKey: "/signup"});
} else if (uri.includes("/login")) {
this.setState({selectedMenuKey: "/login"});
} else if (uri.includes("/result")) {
this.setState({selectedMenuKey: "/result"});
} else if (uri.includes("/sysinfo")) {
this.setState({selectedMenuKey: "/sysinfo"});
} else if (uri.includes("/subscriptions")) {
this.setState({selectedMenuKey: "/subscriptions"});
} else if (uri.includes("/plans")) {
this.setState({selectedMenuKey: "/plans"});
} else if (uri.includes("/pricings")) {
this.setState({selectedMenuKey: "/pricings"});
} else {
this.setState({selectedMenuKey: -1});
}
@@ -368,11 +329,6 @@ class App extends Component {
items.push(Setting.getItem(<><SettingOutlined />&nbsp;&nbsp;{i18next.t("account:My Account")}</>,
"/account"
));
if (Conf.EnableChatPages) {
items.push(Setting.getItem(<><CommentOutlined />&nbsp;&nbsp;{i18next.t("account:Chats & Messages")}</>,
"/chat"
));
}
}
items.push(Setting.getItem(<><LogoutOutlined />&nbsp;&nbsp;{i18next.t("account:Logout")}</>,
"/logout"));
@@ -382,8 +338,6 @@ class App extends Component {
this.props.history.push("/account");
} else if (e.key === "/subscription") {
this.props.history.push("/subscription");
} else if (e.key === "/chat") {
this.props.history.push("/chat");
} else if (e.key === "/logout") {
this.logout();
}
@@ -447,7 +401,7 @@ class App extends Component {
return [];
}
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/", <HomeTwoTone />));
if (Setting.isLocalAdminUser(this.state.account)) {
if (Conf.ShowGithubCorner) {
@@ -458,119 +412,54 @@ class App extends Component {
</a>, "#"));
}
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
"/organizations"));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone />, [
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
]));
res.push(Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>,
"/groups"));
res.push(Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>,
"/users"
));
res.push(Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>,
"/roles"
));
res.push(Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>,
"/permissions"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>, "/adapters"),
Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>, "/enforcers"),
].filter(item => {
if (!Setting.isLocalAdminUser(this.state.account) && ["/models", "/adapters", "/enforcers"].includes(item.key)) {
return false;
} else {
return true;
}
})));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>,
"/models"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
]));
res.push(Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>,
"/adapters"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/records">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
]));
res.push(Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>,
"/enforcers"
));
}
res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>, "/payments"),
Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>, "/plans"),
Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>, "/pricings"),
Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, "/subscriptions"),
]));
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>,
"/applications"
));
res.push(Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>,
"/providers"
));
if (Conf.EnableChatPages) {
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"
));
res.push(Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>,
"/records"
));
res.push(Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>,
"/plans"
));
res.push(Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>,
"/pricings"
));
res.push(Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>,
"/subscriptions"
));
}
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>,
"/tokens"
));
res.push(Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>,
"/sessions"
));
res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>,
"/webhooks"
));
res.push(Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>,
"/syncers"
));
res.push(Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>,
"/certs"
));
if (Conf.EnableExtraPages) {
res.push(Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>,
"/products"
));
res.push(Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>,
"/payments"
));
}
}
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo"
));
res.push(Setting.getItem(<a target="_blank" rel="noreferrer"
href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>,
"/swagger"
));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
}
return res;
@@ -628,11 +517,6 @@ 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/:organizationName/: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="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
@@ -668,8 +552,7 @@ class App extends Component {
};
isWithoutCard() {
return Setting.isMobile() || window.location.pathname === "/chat" ||
window.location.pathname.startsWith("/trees");
return Setting.isMobile() || window.location.pathname.startsWith("/trees");
}
renderContent() {

View File

@@ -1,217 +0,0 @@
// 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 {Alert, Avatar, Input, List, Spin} from "antd";
import {CopyOutlined, DislikeOutlined, LikeOutlined, SendOutlined} from "@ant-design/icons";
import i18next from "i18next";
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.props.messages !== undefined && this.props.messages !== null) {
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: ""});
};
renderText(text) {
const lines = text.split("\n").map((line, index) => (
<React.Fragment key={index}>
{line}
<br />
</React.Fragment>
));
return <div>{lines}</div>;
}
renderList() {
if (this.props.messages === undefined || this.props.messages === null) {
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "20%"}} />
</div>
);
}
return (
<div ref={this.listContainerRef} style={{position: "relative", maxHeight: "calc(100vh - 140px)", overflowY: "auto"}}>
<List
itemLayout="horizontal"
dataSource={[...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.includes("#ERROR#") ? this.renderText(item.text) : (
<Alert message={item.text.slice("#ERROR#: ".length)} type="error" showIcon />
)
}
</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;

View File

@@ -1,257 +0,0 @@
// 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((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
chat: res.data,
});
this.getUsers(res.data.organization);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.data || [],
});
});
}
getUsers(organizationName) {
UserBackend.getUsers(organizationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res.data,
});
});
}
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")}&nbsp;&nbsp;&nbsp;&nbsp;
<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} disabled={!Setting.isAdminUser(this.props.account)} 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 virtual={false} mode="multiple" 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;

View File

@@ -1,297 +0,0 @@
// 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 "./common/modal/PopconfirmModal";
class ChatListPage extends BaseListPage {
newChat() {
const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return {
owner: "admin", // this.props.account.applicationName,
name: `chat_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
organization: organizationName,
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",
fixed: "left",
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",
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",
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={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Chats")}&nbsp;&nbsp;&nbsp;&nbsp;
<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) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default ChatListPage;

View File

@@ -1,178 +0,0 @@
// 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, Menu} from "antd";
import {DeleteOutlined, LayoutOutlined, PlusOutlined} 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);
});
const selectedKeys = this.state === undefined ? [] : this.state.selectedKeys;
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);
const isSelected = selectedKeys.includes(`${index}-${chatIndex}`);
return {
key: `${index}-${chatIndex}`,
index: globalChatIndex,
label: (
<div
className="menu-item-container"
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{chat.displayName}
{isSelected && (
<DeleteOutlined
className="menu-item-delete-icon"
style={{
visibility: "visible",
color: "inherit",
transition: "color 0.3s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "inherit";
}}
onMouseDown={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.color = "rgba(89,54,213,0.6)";
}}
onClick={(e) => {
e.stopPropagation();
if (this.props.onDeleteChat) {
this.props.onDeleteChat(globalChatIndex);
}
}}
/>
)}
</div>
),
};
}),
};
});
}
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.onSelectChat) {
this.props.onSelectChat(selectedItem.index);
}
};
getRootSubmenuKeys(items) {
return items.map((item, index) => `${index}`);
}
setSelectedKeyToNewChat(chats) {
const items = this.chatsToItems(chats);
const openKeys = items.map((item) => item.key);
this.setState({
openKeys: openKeys,
selectedKeys: ["0-0"],
});
}
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 (
<div>
<Button
icon={<PlusOutlined />}
style={{
width: "calc(100% - 8px)",
height: "40px",
margin: "4px",
borderColor: "rgb(229,229,229)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = "rgba(89,54,213,0.6)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = "rgba(0, 0, 0, 0.1)";
}}
onMouseDown={(e) => {
e.currentTarget.style.borderColor = "rgba(89,54,213,0.4)";
}}
onMouseUp={(e) => {
e.currentTarget.style.borderColor = "rgba(89,54,213,0.6)";
}}
onClick={this.props.onAddChat}
>
New Chat
</Button>
<Menu
style={{maxHeight: "calc(100vh - 140px - 40px - 8px)", overflowY: "auto"}}
mode="inline"
openKeys={this.state.openKeys}
selectedKeys={this.state.selectedKeys}
onOpenChange={this.onOpenChange}
onSelect={this.onSelect}
items={items}
/>
</div>
);
}
}
export default ChatMenu;

View File

@@ -1,292 +0,0 @@
// 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 {
constructor(props) {
super(props);
this.menu = React.createRef();
}
newChat(chat) {
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: "AI",
category: chat !== undefined ? chat.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,
};
}
newMessage(text) {
const randomName = Setting.getRandomName();
return {
owner: this.props.account.owner, // this.props.account.messagename,
name: `message_${randomName}`,
createdTime: moment().format(),
organization: this.props.account.owner,
chat: this.state.chatName,
replyTo: "",
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((res) => {
const messages = res.data;
this.setState({
messages: messages,
});
if (messages.length > 0) {
const lastMessage = messages[messages.length - 1];
if (lastMessage.author === "AI" && lastMessage.replyTo !== "" && lastMessage.text === "") {
let text = "";
MessageBackend.getMessageAnswer(lastMessage.owner, lastMessage.name, (data) => {
if (data === "") {
data = "\n";
}
const lastMessage2 = Setting.deepCopy(lastMessage);
text += data;
lastMessage2.text = text;
messages[messages.length - 1] = lastMessage2;
this.setState({
messages: messages,
});
}, (error) => {
Setting.showMessage("error", `${i18next.t("general:Failed to get answer")}: ${error}`);
const lastMessage2 = Setting.deepCopy(lastMessage);
lastMessage2.text = `#ERROR#: ${error}`;
messages[messages.length - 1] = lastMessage2;
this.setState({
messages: messages,
});
});
}
}
Setting.scrollToDiv(`chatbox-list-item-${messages.length}`);
});
}
addChat(chat) {
const newChat = this.newChat(chat);
ChatBackend.addChat(newChat)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully added"));
this.setState({
chatName: newChat.name,
messages: null,
});
this.getMessages(newChat.name);
const {pagination} = this.state;
this.fetch({pagination}, false);
} 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(chats, i, chat) {
ChatBackend.deleteChat(chat)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
const data = Setting.deleteRow(this.state.data, i);
const j = Math.min(i, data.length - 1);
if (j < 0) {
this.setState({
chatName: undefined,
messages: [],
data: data,
});
} else {
const focusedChat = data[j];
this.setState({
chatName: focusedChat.name,
messages: null,
data: data,
});
this.getMessages(focusedChat.name);
}
} 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}`);
});
}
getCurrentChat() {
return this.state.data.filter(chat => chat.name === this.state.chatName)[0];
}
renderTable(chats) {
const onSelectChat = (i) => {
const chat = chats[i];
this.setState({
chatName: chat.name,
messages: null,
});
this.getMessages(chat.name);
};
const onAddChat = () => {
const chat = this.getCurrentChat();
this.addChat(chat);
};
const onDeleteChat = (i) => {
const chat = chats[i];
this.deleteChat(chats, i, chat);
};
if (this.state.loading) {
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "10%"}} />
</div>
);
}
return (
<div style={{display: "flex", height: "calc(100vh - 140px)"}}>
<div style={{width: "250px", height: "100%", backgroundColor: "white", borderRight: "1px solid rgb(245,245,245)", borderBottom: "1px solid rgb(245,245,245)"}}>
<ChatMenu ref={this.menu} chats={chats} onSelectChat={onSelectChat} onAddChat={onAddChat} onDeleteChat={onDeleteChat} />
</div>
<div style={{flex: 1, height: "100%", backgroundColor: "white", position: "relative"}}>
{
(this.state.messages === undefined || this.state.messages === null) ? null : (
<div style={{
position: "absolute",
top: -50,
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,
pointerEvents: "none",
}}>
</div>
)
}
<ChatBox messages={this.state.messages || []} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
</div>
</div>
);
}
fetch = (params = {}, setLoading = true) => {
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;
}
if (setLoading) {
this.setState({loading: true});
}
ChatBackend.getChats("admin", params.pagination.current, -1, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
messages: [],
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,
});
}
if (!setLoading) {
this.menu.current.setSelectedKeyToNewChat(chats);
}
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default ChatPage;

View File

@@ -20,9 +20,6 @@ export const IsDemoMode = false;
export const ForceLanguage = "";
export const DefaultLanguage = "en";
export const EnableExtraPages = true;
export const EnableChatPages = true;
export const InitThemeAlgorithm = true;
export const ThemeDefault = {
themeType: "default",

View File

@@ -221,6 +221,7 @@ class GroupTreePage extends React.Component {
onChange={(value) => {
this.setState({
organizationName: value,
groupName: "",
});
this.props.history.push(`/trees/${value}`);
}}

View File

@@ -1,233 +0,0 @@
// 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((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
message: res.data,
});
this.getUsers(res.data.organization);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.data || [],
});
});
}
getChats() {
ChatBackend.getChats("admin")
.then((res) => {
this.setState({
chats: res.data || [],
});
});
}
getUsers(organizationName) {
UserBackend.getUsers(organizationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res.data,
});
});
}
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")}&nbsp;&nbsp;&nbsp;&nbsp;
<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} disabled={!Setting.isAdminUser(this.props.account)} 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;

View File

@@ -1,240 +0,0 @@
// 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 "./common/modal/PopconfirmModal";
class MessageListPage extends BaseListPage {
newMessage() {
const randomName = Setting.getRandomName();
const organizationName = Setting.getRequestOrganization(this.props.account);
return {
owner: "admin", // this.props.account.messagename,
name: `message_${randomName}`,
createdTime: moment().format(),
organization: organizationName,
chat: "",
replyTo: "",
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",
fixed: "left",
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",
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",
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={(record) => `${record.owner}/${record.name}`}size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Messages")}&nbsp;&nbsp;&nbsp;&nbsp;
<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", Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default MessageListPage;

View File

@@ -163,7 +163,7 @@ class PaymentEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.payment.organization} onChange={e => {
<Input disabled={true} value={this.state.payment.owner} onChange={e => {
// this.updatePaymentField('organization', e.target.value);
}} />
</Col>

View File

@@ -115,6 +115,26 @@ class PaymentResultPage extends React.Component {
/>
</div>
);
} else if (payment.state === "Canceled") {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="warning"
title={`${i18next.t("payment:The payment has been canceled")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
this.goToPaymentUrl(payment);
}}>
{i18next.t("payment:Return to Website")}
</Button>,
]}
/>
</div>
);
} else {
return (
<div>
@@ -124,7 +144,7 @@ class PaymentResultPage extends React.Component {
<Result
status="error"
title={`${i18next.t("payment:The payment has failed")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
subTitle={`${i18next.t("payment:Failed reason")}: ${payment.message}`}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
this.goToPaymentUrl(payment);

View File

@@ -134,10 +134,14 @@ class ProviderEditPage extends React.Component {
case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS":
if (provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS" || provider.type === "Amazon SNS" || provider.type === "Baidu Cloud SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} else if (provider.type === "UCloud SMS") {
return Setting.getLabel(i18next.t("provider:Public key"), i18next.t("provider:Public key - Tooltip"));
} else if (provider.type === "Msg91 SMS" || provider.type === "Infobip SMS") {
return Setting.getLabel(i18next.t("provider:Sender Id"), i18next.t("provider:Sender Id - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
@@ -157,10 +161,16 @@ class ProviderEditPage extends React.Component {
case "Email":
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
case "SMS":
if (provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS" || provider.type === "Amazon SNS" || provider.type === "Baidu Cloud SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} else if (provider.type === "UCloud SMS") {
return Setting.getLabel(i18next.t("provider:Private Key"), i18next.t("provider:Private Key - Tooltip"));
} else if (provider.type === "Msg91 SMS") {
return Setting.getLabel(i18next.t("provider:Auth Key"), i18next.t("provider:Auth Key - Tooltip"));
} else if (provider.type === "Infobip SMS") {
return Setting.getLabel(i18next.t("provider:Api Key"), i18next.t("provider:Api Key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
@@ -170,8 +180,6 @@ class ProviderEditPage extends React.Component {
} else {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
}
case "AI":
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
default:
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
@@ -236,7 +244,7 @@ class ProviderEditPage extends React.Component {
tooltip = i18next.t("provider:Agent ID - Tooltip");
}
} else if (provider.category === "SMS") {
if (provider.type === "Twilio SMS") {
if (provider.type === "Twilio SMS" || provider.type === "Azure ACS") {
text = i18next.t("provider:Sender number");
tooltip = i18next.t("provider:Sender number - Tooltip");
} else if (provider.type === "Tencent Cloud SMS") {
@@ -248,6 +256,18 @@ class ProviderEditPage extends React.Component {
} else if (provider.type === "Huawei Cloud SMS") {
text = i18next.t("provider:Channel No.");
tooltip = i18next.t("provider:Channel No. - Tooltip");
} else if (provider.type === "Amazon SNS") {
text = i18next.t("provider:Region");
tooltip = i18next.t("provider:Region - Tooltip");
} else if (provider.type === "Baidu Cloud SMS") {
text = i18next.t("provider:Endpoint");
tooltip = i18next.t("provider:Endpoint - Tooltip");
} else if (provider.type === "Infobip SMS") {
text = i18next.t("provider:Base URL");
tooltip = i18next.t("provider:Base URL - Tooltip");
} else if (provider.type === "UCloud SMS") {
text = i18next.t("provider:Project Id");
tooltip = i18next.t("provider:Project Id - Tooltip");
}
} else if (provider.category === "Email") {
if (provider.type === "SUBMAIL") {
@@ -356,15 +376,12 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("type", "PayPal");
} else if (value === "Captcha") {
this.updateProviderField("type", "Default");
} else if (value === "AI") {
this.updateProviderField("type", "OpenAI API - GPT");
} else if (value === "Web3") {
this.updateProviderField("type", "MetaMask");
}
})}>
{
[
{id: "AI", name: "AI"},
{id: "Captcha", name: "Captcha"},
{id: "Email", name: "Email"},
{id: "OAuth", name: "OAuth"},
@@ -385,7 +402,7 @@ class ProviderEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.type} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} showSearch value={this.state.provider.type} onChange={(value => {
this.updateProviderField("type", value);
if (value === "Local File System") {
this.updateProviderField("domain", Setting.getFullServerUrl());
@@ -400,7 +417,10 @@ class ProviderEditPage extends React.Component {
{
Setting.getProviderTypeOptions(this.state.provider.category)
.sort((a, b) => a.name.localeCompare(b.name))
.map((providerType, index) => <Option key={index} value={providerType.id}>{providerType.name}</Option>)
.map((providerType, index) => <Option key={index} value={providerType.id}>
<img width={20} height={20} style={{marginBottom: "3px", marginRight: "10px"}} src={Setting.getProviderLogoURL({category: this.state.provider.category, type: providerType.id})} alt={providerType.id} />
{providerType.name}
</Option>)
}
</Select>
</Col>
@@ -529,20 +549,16 @@ class ProviderEditPage extends React.Component {
{
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || (this.state.provider.category === "Web3") || (this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ? null : (
<React.Fragment>
{
this.state.provider.category === "AI" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel(this.state.provider)} :
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField("clientId", e.target.value);
}} />
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel(this.state.provider)} :
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField("clientId", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecretLabel(this.state.provider)} :
@@ -601,7 +617,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.type !== "Adfs" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@@ -628,7 +644,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Local File System", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? null : (
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@@ -662,7 +678,7 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
{["MinIO"].includes(this.state.provider.type) ? null : (
{["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@@ -674,7 +690,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["AWS S3", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
@@ -763,7 +779,7 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
) : this.state.provider.category === "SMS" ? (
<React.Fragment>
{this.state.provider.type === "Twilio SMS" ?
{["Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
@@ -777,16 +793,20 @@ class ProviderEditPage extends React.Component {
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
{["Infobip SMS"].includes(this.state.provider.type) ?
null :
(<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Template code"), i18next.t("provider:Template code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.templateCode} onChange={e => {
this.updateProviderField("templateCode", e.target.value);
}} />
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :

View File

@@ -37,7 +37,7 @@ class ProviderListPage extends BaseListPage {
newProvider() {
const randomName = Setting.getRandomName();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.owner : Setting.getRequestOrganization();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.owner : Setting.getRequestOrganization(this.props.account);
return {
owner: owner,
name: `provider_${randomName}`,

View File

@@ -30,7 +30,6 @@ const {Option} = Select;
export const ServerUrl = "";
// export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static";
export const StaticBaseUrl = "https://cdn.casbin.org";
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
@@ -44,6 +43,9 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
{label: "TiếngViệt", key: "vi", country: "VN", alt: "TiếngViệt"},
{label: "Português", key: "pt", country: "BR", alt: "Português"},
{label: "Itariano", key: "it", country: "IT", alt: "Itariano"},
{label: "Marley", key: "ms", country: "MY", alt: "Marley"},
{label: "Tkiš", key: "tr", country: "TR", alt: "Tkiš"},
];
export function getThemeData(organization, application) {
@@ -83,10 +85,26 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/sms",
},
"Amazon SNS": {
logo: `${StaticBaseUrl}/img/social_aws.png`,
url: "https://aws.amazon.com/cn/sns/",
},
"Azure ACS": {
logo: `${StaticBaseUrl}/img/social_azure.png`,
url: "https://azure.microsoft.com/en-us/products/communication-services",
},
"Infobip SMS": {
logo: `${StaticBaseUrl}/img/social_infobip.png`,
url: "https://portal.infobip.com/homepage/",
},
"Tencent Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/sms",
},
"Baidu Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_baidu_cloud.png`,
url: "https://cloud.baidu.com/product/sms.html",
},
"Volc Engine SMS": {
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
url: "https://www.volcengine.com/products/cloud-sms",
@@ -95,6 +113,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html",
},
"UCloud SMS": {
logo: `${StaticBaseUrl}/img/social_ucloud.png`,
url: "https://www.ucloud.cn/site/product/usms.html",
},
"Twilio SMS": {
logo: `${StaticBaseUrl}/img/social_twilio.svg`,
url: "https://www.twilio.com/messaging",
@@ -107,6 +129,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_submail.svg`,
url: "https://www.mysubmail.com",
},
"Msg91 SMS": {
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
url: "https://control.msg91.com/app/",
},
"Mock SMS": {
logo: `${StaticBaseUrl}/img/social_default.png`,
url: "",
@@ -151,6 +177,14 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_azure.png`,
url: "https://azure.microsoft.com/en-us/services/storage/blobs/",
},
"Qiniu Cloud Kodo": {
logo: `${StaticBaseUrl}/img/social_qiniu_cloud.png`,
url: "https://www.qiniu.com/solutions/storage",
},
"Google Cloud Storage": {
logo: `${StaticBaseUrl}/img/social_google_cloud.png`,
url: "https://cloud.google.com/storage",
},
},
SAML: {
"Aliyun IDaaS": {
@@ -737,7 +771,7 @@ export function getClickable(text) {
export function getProviderLogoURL(provider) {
if (provider.category === "OAuth") {
if (provider.type === "Custom") {
if (provider.type === "Custom" && provider.customLogo) {
return provider.customLogo;
}
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
@@ -776,7 +810,7 @@ export function getProviderTypeOptions(category) {
{id: "WeCom", name: "WeCom"},
{id: "Lark", name: "Lark"},
{id: "GitLab", name: "GitLab"},
{id: "Adfs", name: "Adfs"},
{id: "ADFS", name: "ADFS"},
{id: "Baidu", name: "Baidu"},
{id: "Alipay", name: "Alipay"},
{id: "Casdoor", name: "Casdoor"},
@@ -851,13 +885,19 @@ export function getProviderTypeOptions(category) {
} else if (category === "SMS") {
return (
[
{id: "Aliyun SMS", name: "Aliyun SMS"},
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
{id: "Amazon SNS", name: "Amazon SNS"},
{id: "Azure ACS", name: "Azure ACS"},
{id: "Infobip SMS", name: "Infobip SMS"},
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
{id: "Baidu Cloud SMS", name: "Baidu Cloud SMS"},
{id: "Volc Engine SMS", name: "Volc Engine SMS"},
{id: "Huawei Cloud SMS", name: "Huawei Cloud SMS"},
{id: "UCloud SMS", name: "UCloud SMS"},
{id: "Twilio SMS", name: "Twilio SMS"},
{id: "SmsBao SMS", name: "SmsBao SMS"},
{id: "SUBMAIL SMS", name: "SUBMAIL SMS"},
{id: "Msg91 SMS", name: "Msg91 SMS"},
]
);
} else if (category === "Storage") {
@@ -869,6 +909,8 @@ export function getProviderTypeOptions(category) {
{id: "Aliyun OSS", name: "Aliyun OSS"},
{id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: "Azure Blob", name: "Azure Blob"},
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
]
);
} else if (category === "SAML") {
@@ -893,10 +935,6 @@ export function getProviderTypeOptions(category) {
{id: "GEETEST", name: "GEETEST"},
{id: "Cloudflare Turnstile", name: "Cloudflare Turnstile"},
]);
} else if (category === "AI") {
return ([
{id: "OpenAI API - GPT", name: "OpenAI API - GPT"},
]);
} else if (category === "Web3") {
return ([
{id: "MetaMask", name: "MetaMask"},
@@ -1045,13 +1083,7 @@ export function getLabel(text, tooltip) {
}
export function getItem(label, key, icon, children, type) {
return {
key,
icon,
children,
label,
type,
};
return {label: label, key: key, icon: icon, children: children, type: type};
}
export function getOption(label, value) {
@@ -1061,42 +1093,6 @@ export function getOption(label, value) {
};
}
function repeat(str, len) {
while (str.length < len) {
str += str.substr(0, len - str.length);
}
return str;
}
function maskString(s) {
if (s.length <= 2) {
return s;
} else {
return `${s[0]}${repeat("*", s.length - 2)}${s[s.length - 1]}`;
}
}
export function getMaskedPhone(s) {
return s.replace(/(\d{3})\d*(\d{4})/, "$1****$2");
}
export function getMaskedEmail(email) {
if (email === "") {return;}
const tokens = email.split("@");
let username = tokens[0];
username = maskString(username);
const domain = tokens[1];
const domainTokens = domain.split(".");
domainTokens[domainTokens.length - 2] = maskString(domainTokens[domainTokens.length - 2]);
return `${username}@${domainTokens.join(".")}`;
}
export function IsEmail(s) {
return s.includes("@");
}
export function getArrayItem(array, key, value) {
const res = array.filter(item => item[key] === value)[0];
return res;
@@ -1154,10 +1150,6 @@ export function getTag(color, text) {
);
}
export function getApplicationOrgName(application) {
return `${application?.organizationObj.owner}/${application?.organizationObj.name}`;
}
export function getApplicationName(application) {
return `${application?.owner}/${application?.name}`;
}
@@ -1221,11 +1213,11 @@ export function isDefaultOrganizationSelected(account) {
const BuiltInObjects = [
"api-enforcer-built-in",
"permission-enforcer-built-in",
"user-enforcer-built-in",
"api-model-built-in",
"permission-model-built-in",
"user-model-built-in",
"api-adapter-built-in",
"permission-adapter-built-in",
"user-adapter-built-in",
];
export function builtInObject(obj) {

View File

@@ -133,13 +133,25 @@ class UserEditPage extends React.Component {
this.setState({
application: res.data,
isGroupsVisible: res.data?.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
});
});
}
getUserOrganization() {
return this.state.organizations.filter(organization => organization.name === this.state.user.owner)[0];
}
isGroupsVisible() {
const organization = this.getUserOrganization();
if (!organization) {
return false;
} else {
return organization.accountItems?.some((item) => item.name === "Groups" && item.visible);
}
}
getGroups(organizationName) {
if (this.state.isGroupsVisible) {
if (this.isGroupsVisible()) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "ok") {
@@ -319,7 +331,7 @@ class UserEditPage extends React.Component {
})}
>
{
this.state.groups?.map((group) => <Option key={group.name} value={group.name}>
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
<Space>
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
{group.displayName}
@@ -401,7 +413,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col>
<Col span={22} >
<PasswordModal user={this.state.user} organization={this.state.application?.organizationObj} account={this.props.account} disabled={disabled} />
<PasswordModal user={this.state.user} organization={this.getUserOrganization()} account={this.props.account} disabled={disabled} />
</Col>
</Row>
);
@@ -442,7 +454,7 @@ class UserEditPage extends React.Component {
onChange={(value) => {
this.updateUserField("countryCode", value);
}}
countryCodes={this.state.application?.organizationObj.countryCodes}
countryCodes={this.getUserOrganization()?.countryCodes}
/>
<Input value={this.state.user.phone}
style={{width: "70%"}}
@@ -599,10 +611,10 @@ class UserEditPage extends React.Component {
</Col>
<Col span={22} >
{
this.state.application?.organizationObj.tags?.length > 0 ? (
this.getUserOrganization()?.tags?.length > 0 ? (
<Select virtual={false} style={{width: "100%"}} value={this.state.user.tag}
onChange={(value => {this.updateUserField("tag", value);})}
options={this.state.application.organizationObj.tags?.map((tag) => {
options={this.getUserOrganization()?.tags?.map((tag) => {
const tokens = tag.split("|");
const value = tokens[0];
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
@@ -888,7 +900,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("mfa:Multi-factor authentication"), i18next.t("mfa:Multi-factor authentication - Tooltip "))} :
</Col>
<Col span={22} >
<Card title={i18next.t("mfa:Multi-factor methods")}
<Card size="small" title={i18next.t("mfa:Multi-factor methods")}
extra={this.state.multiFactorAuths?.some(mfaProps => mfaProps.enabled) ?
<PopconfirmModal
text={i18next.t("general:Disable")}
@@ -1008,7 +1020,7 @@ class UserEditPage extends React.Component {
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
{
this.state.application?.organizationObj.accountItems?.map(accountItem => {
this.getUserOrganization()?.accountItems?.map(accountItem => {
return (
<React.Fragment key={accountItem.name}>
{

View File

@@ -76,7 +76,7 @@ class UserListPage extends BaseListPage {
phone: Setting.getRandomNumber(),
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
address: [],
groups: this.props.groupName ? [this.props.groupName] : [],
groups: this.props.groupName ? [`${owner}/${this.props.groupName}`] : [],
affiliation: "Example Inc.",
tag: "staff",
region: "",

View File

@@ -73,7 +73,7 @@ const authInfo = {
scope: "read_user+profile",
endpoint: "https://gitlab.com/oauth/authorize",
},
Adfs: {
ADFS: {
scope: "openid",
endpoint: "http://example.com",
},
@@ -433,7 +433,7 @@ export function getAuthUrl(application, provider, method) {
}
} else if (provider.type === "Lark") {
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
} else if (provider.type === "Adfs") {
} else if (provider.type === "ADFS") {
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
} else if (provider.type === "Baidu") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;

View File

@@ -71,7 +71,7 @@ function getSigninButton(provider) {
return <LarkLoginButton text={text} align={"center"} />;
} else if (provider.type === "GitLab") {
return <GitLabLoginButton text={text} align={"center"} />;
} else if (provider.type === "Adfs") {
} else if (provider.type === "ADFS") {
return <AdfsLoginButton text={text} align={"center"} />;
} else if (provider.type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />;

View File

@@ -1,71 +0,0 @@
// 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());
}

View File

@@ -1,98 +0,0 @@
// 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, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-messages?owner=${owner}&organization=${organization}&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 getMessageAnswer(owner, name, onMessage, onError) {
const eventSource = new EventSource(`${Setting.ServerUrl}/api/get-message-answer?id=${owner}/${encodeURIComponent(name)}`);
eventSource.addEventListener("message", (e) => {
onMessage(e.data);
});
eventSource.addEventListener("myerror", (e) => {
onError(e.data);
eventSource.close();
});
eventSource.addEventListener("end", (e) => {
eventSource.close();
});
}
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());
}

View File

@@ -24,6 +24,9 @@ import ko from "./locales/ko/data.json";
import ru from "./locales/ru/data.json";
import vi from "./locales/vi/data.json";
import pt from "./locales/pt/data.json";
import it from "./locales/it/data.json";
import ms from "./locales/ms/data.json";
import tr from "./locales/tr/data.json";
import * as Conf from "./Conf";
import {initReactI18next} from "react-i18next";
@@ -39,6 +42,9 @@ const resources = {
ru: ru,
vi: vi,
pt: pt,
it: it,
ms: ms,
tr: tr,
};
function initLanguage() {
@@ -88,6 +94,15 @@ function initLanguage() {
case "pt":
language = "pt";
break;
case "it":
language = "it";
break;
case "ms":
language = "ms";
break;
case "tr":
language = "tr";
break;
default:
language = Conf.DefaultLanguage;
}

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Abmelden",
"My Account": "Mein Konto",
"Sign Up": "Anmelden"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Die Verbindung zum Server konnte nicht hergestellt werden",
"Failed to delete": "Konnte nicht gelöscht werden",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Konnte nicht gespeichert werden",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Logout",
"My Account": "My Account",
"Sign Up": "Sign Up"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Failed to connect to server",
"Failed to delete": "Failed to delete",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Failed to save",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Cierre de sesión",
"My Account": "Mi cuenta",
"Sign Up": "Registrarse"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "No se pudo conectar al servidor",
"Failed to delete": "No se pudo eliminar",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "No se pudo guardar",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Déconnexion",
"My Account": "Mon Compte",
"Sign Up": "S'inscrire"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Échec de la connexion au serveur",
"Failed to delete": "Échec de la suppression",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Échec de sauvegarde",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Keluar",
"My Account": "Akun Saya",
"Sign Up": "Mendaftar"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Gagal terhubung ke server",
"Failed to delete": "Gagal menghapus",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Gagal menyimpan",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

1009
web/src/locales/it/data.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "ログアウト",
"My Account": "マイアカウント",
"Sign Up": "新規登録"
@@ -127,19 +126,6 @@
"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": "メールコード",
@@ -194,7 +180,6 @@
"Cert": "証明書",
"Cert - Tooltip": "このアプリケーションに対応するクライアントSDKによって検証する必要がある公開鍵証明書",
"Certs": "証明書",
"Chats": "Chats",
"Click to Upload": "アップロードするにはクリックしてください",
"Client IP": "クライアントIP",
"Close": "閉じる",
@@ -224,7 +209,6 @@
"Failed to connect to server": "サーバーに接続できませんでした",
"Failed to delete": "削除に失敗しました",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "保存に失敗しました",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"Master password": "マスターパスワード",
"Master password - Tooltip": "この組織のすべてのユーザーにログインするために使用でき、管理者が技術的な問題を解決するためにこのユーザーとしてログインするのに便利です",
"Menu": "メニュー",
"Messages": "Messages",
"Method": "方法",
"Model": "モデル",
"Model - Tooltip": "カスビンアクセスコントロールモデル",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "로그아웃",
"My Account": "내 계정",
"Sign Up": "가입하기"
@@ -127,19 +126,6 @@
"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": "이메일 코드",
@@ -194,7 +180,6 @@
"Cert": "인증서",
"Cert - Tooltip": "이 응용 프로그램에 해당하는 클라이언트 SDK에서 확인해야 하는 공개 키 인증서",
"Certs": "증명서",
"Chats": "Chats",
"Click to Upload": "클릭하여 업로드하세요",
"Client IP": "고객 IP",
"Close": "닫다",
@@ -224,7 +209,6 @@
"Failed to connect to server": "서버에 연결하지 못했습니다",
"Failed to delete": "삭제에 실패했습니다",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "저장에 실패했습니다",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"Master password": "마스터 비밀번호",
"Master password - Tooltip": "이 조직의 모든 사용자에게 로그인하는 데 사용될 수 있으며, 이 사용자로 로그인하여 기술 문제를 해결하는 관리자에게 편리합니다",
"Menu": "메뉴",
"Messages": "Messages",
"Method": "방법",
"Model": "모델",
"Model - Tooltip": "Casbin 접근 제어 모델",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

1009
web/src/locales/ms/data.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Conversas e Mensagens",
"Logout": "Sair",
"My Account": "Minha Conta",
"Sign Up": "Cadastrar-se"
@@ -127,19 +126,6 @@
"Scope - Tooltip": "Cenários de uso do certificado",
"Type - Tooltip": "Tipo de certificado"
},
"chat": {
"AI": "IA",
"Edit Chat": "Editar Chat",
"Group": "Grupo",
"Message count": "Contagem de Mensagens",
"New Chat": "Novo Chat",
"Single": "Individual",
"User1": "Usuário 1",
"User1 - Tooltip": "Usuário 1 - Tooltip",
"User2": "Usuário 2",
"User2 - Tooltip": "Usuário 2 - Tooltip",
"Users - Tooltip": "Usuários - Tooltip"
},
"code": {
"Code you received": "Código que você recebeu",
"Email code": "Código de e-mail",
@@ -194,7 +180,6 @@
"Cert": "Certificado",
"Cert - Tooltip": "O certificado da chave pública que precisa ser verificado pelo SDK do cliente correspondente a esta aplicação",
"Certs": "Certificados",
"Chats": "Chats",
"Click to Upload": "Clique para Enviar",
"Client IP": "IP do Cliente",
"Close": "Fechar",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Falha ao conectar ao servidor",
"Failed to delete": "Falha ao excluir",
"Failed to enable": "Falha ao habilitar",
"Failed to get answer": "Falha ao obter resposta",
"Failed to remove": "Failed to remove",
"Failed to save": "Falha ao salvar",
"Failed to verify": "Falha ao verificar",
@@ -257,7 +241,6 @@
"Master password": "Senha mestra",
"Master password - Tooltip": "Pode ser usada para fazer login em todos os usuários desta organização, facilitando para os administradores fazerem login como este usuário para resolver problemas técnicos",
"Menu": "Menu",
"Messages": "Mensagens",
"Method": "Método",
"Model": "Modelo",
"Model - Tooltip": "Modelo de controle de acesso do Casbin",
@@ -425,16 +408,6 @@
"sign up now": "Inscreva-se agora",
"username, Email or phone": "Nome de usuário, email ou telefone"
},
"message": {
"Author": "Autor",
"Author - Tooltip": "Autor - Dica de ferramenta",
"Chat": "Chat",
"Chat - Tooltip": "Chat - Dica de ferramenta",
"Edit Message": "Editar Mensagem",
"New Message": "Nova Mensagem",
"Text": "Texto",
"Text - Tooltip": "Texto - Dica de ferramenta"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Выход",
"My Account": "Мой аккаунт",
"Sign Up": "Зарегистрироваться"
@@ -127,19 +126,6 @@
"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": "Электронный код письма",
@@ -194,7 +180,6 @@
"Cert": "Сертификат",
"Cert - Tooltip": "Сертификат открытого ключа, который требуется проверить клиентским SDK, соответствующим этому приложению",
"Certs": "сертификаты",
"Chats": "Chats",
"Click to Upload": "Нажмите, чтобы загрузить",
"Client IP": "Клиентский IP",
"Close": "Близко",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Не удалось подключиться к серверу",
"Failed to delete": "Не удалось удалить",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Не удалось сохранить",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"Master password": "Главный пароль",
"Master password - Tooltip": "Можно использовать для входа в учетные записи всех пользователей этой организации, что удобно для администраторов, чтобы войти в качестве этого пользователя и решить технические проблемы",
"Menu": "Меню",
"Messages": "Messages",
"Method": "Метод",
"Model": "Модель",
"Model - Tooltip": "Модель контроля доступа Casbin",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

1009
web/src/locales/tr/data.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "Chats & Messages",
"Logout": "Đăng xuất",
"My Account": "Tài khoản của tôi",
"Sign Up": "Đăng ký"
@@ -127,19 +126,6 @@
"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",
@@ -194,7 +180,6 @@
"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",
@@ -224,7 +209,6 @@
"Failed to connect to server": "Không thể kết nối đến máy chủ",
"Failed to delete": "Không thể xoá",
"Failed to enable": "Failed to enable",
"Failed to get answer": "Failed to get answer",
"Failed to remove": "Failed to remove",
"Failed to save": "Không thể lưu được",
"Failed to verify": "Failed to verify",
@@ -257,7 +241,6 @@
"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",
@@ -425,16 +408,6 @@
"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"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
"Enable multi-factor authentication": "Enable multi-factor authentication",

View File

@@ -1,6 +1,5 @@
{
"account": {
"Chats & Messages": "聊天 & 消息",
"Logout": "登出",
"My Account": "我的账户",
"Sign Up": "注册"
@@ -127,19 +126,6 @@
"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": "邮箱验证码",
@@ -194,7 +180,6 @@
"Cert": "证书",
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
"Certs": "证书",
"Chats": "聊天",
"Click to Upload": "点击上传",
"Client IP": "客户端IP",
"Close": "关闭",
@@ -224,7 +209,6 @@
"Failed to connect to server": "连接服务器失败",
"Failed to delete": "删除失败",
"Failed to enable": "启用失败",
"Failed to get answer": "获取回答失败",
"Failed to remove": "移除失败",
"Failed to save": "保存失败",
"Failed to verify": "验证失败",
@@ -257,7 +241,6 @@
"Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
"Menu": "目录",
"Messages": "消息",
"Method": "方法",
"Model": "模型",
"Model - Tooltip": "Casbin的访问控制模型",
@@ -425,16 +408,6 @@
"sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号"
},
"message": {
"Author": "作者",
"Author - Tooltip": "发出消息的用户",
"Chat": "聊天",
"Chat - Tooltip": "消息所属的聊天ID",
"Edit Message": "编辑消息",
"New Message": "添加消息",
"Text": "内容",
"Text - Tooltip": "消息的内容"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "每次登录帐户时,都需要密码和认证码",
"Enable multi-factor authentication": "启用多因素认证",

View File

@@ -287,10 +287,10 @@ class PolicyTable extends React.Component {
) : (
<div>
<Tooltip placement="topLeft" title="Edit">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => this.edit(record, index)} />
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={() => this.edit(record, index)} />
</Tooltip>
<Tooltip placement="topLeft" title="Delete">
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" onClick={() => this.deletePolicy(table, index)} />
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" onClick={() => this.deletePolicy(table, index)} />
</Tooltip>
</div>
);
@@ -304,14 +304,14 @@ class PolicyTable extends React.Component {
onChange: (page) => this.setState({
page: page,
}),
disabled: this.state.editingIndex !== "",
disabled: this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name}),
current: this.state.page,
}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
loading={this.state.loading}
title={() => (
<div>
<Button disabled={this.state.editingIndex !== ""} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
<Button disabled={this.state.editingIndex !== "" || Setting.builtInObject({owner: this.props.owner, name: this.props.name})} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>

View File

@@ -38,7 +38,7 @@ class SyncerTableColumnTable extends React.Component {
}
addRow(table) {
const row = {name: `column${table.length}`, type: "string", values: []};
const row = {name: `column${table.length}`, type: "string", values: [], isKey: table.filter(row => row.isKey).length === 0};
if (table === undefined) {
table = [];
}
@@ -107,6 +107,26 @@ class SyncerTableColumnTable extends React.Component {
);
},
},
{
title: i18next.t("syncer:Is key"),
dataIndex: "isKey",
key: "isKey",
render: (text, record, index) => {
return (
<Switch checked={text} onChange={checked => {
if (!record.isKey && checked) {
table.forEach((row, i) => {
this.updateField(table, i, "isKey", false);
});
} else if (record.isKey && !checked) {
return;
}
this.updateField(table, index, "isKey", checked);
}} />
);
},
},
{
title: i18next.t("syncer:Is hashed"),
dataIndex: "isHashed",
@@ -133,7 +153,7 @@ class SyncerTableColumnTable extends React.Component {
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
<Button icon={<DeleteOutlined />} disabled={record.isKey && table.length > 1} size="small" onClick={() => this.deleteRow(table, index)} />
</Tooltip>
</div>
);

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