mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-29 10:30:30 +08:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cc456f265f | ||
![]() |
7058a34f87 | ||
![]() |
8e6755845f | ||
![]() |
967fa4be68 | ||
![]() |
805cf20d04 | ||
![]() |
2a8001f490 | ||
![]() |
451fc9034f | ||
![]() |
0e14a2597e | ||
![]() |
ff87c4ea33 | ||
![]() |
4f5396c70e | ||
![]() |
3c30222fce | ||
![]() |
2d04731622 | ||
![]() |
e0d2bc3dc9 | ||
![]() |
0bda29f143 | ||
![]() |
05703720c5 | ||
![]() |
cc566bf31f | ||
![]() |
e93d8c19d9 | ||
![]() |
f2e3182a69 | ||
![]() |
f934531083 | ||
![]() |
e1c0af345f | ||
![]() |
3b3bfe39f9 | ||
![]() |
18cc952f8e | ||
![]() |
43439bc8c6 | ||
![]() |
9a2800e3b3 | ||
![]() |
fdaad2b608 |
@@ -21,4 +21,5 @@ isDemoMode = false
|
||||
batchSize = 100
|
||||
ldapServerPort = 389
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataFile = "./init_data.json"
|
@@ -66,6 +66,8 @@ func GetConfigString(key string) string {
|
||||
if res == "" {
|
||||
if key == "staticBaseUrl" {
|
||||
res = "https://cdn.casbin.org"
|
||||
} else if key == "logConfig" {
|
||||
res = "{\"filename\": \"logs/casdoor.log\", \"maxdays\":99999, \"perm\":\"0770\"}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,10 +110,10 @@ func GetLanguage(language string) string {
|
||||
return "en"
|
||||
}
|
||||
|
||||
if len(language) < 2 {
|
||||
if len(language) != 2 {
|
||||
return "en"
|
||||
} else {
|
||||
return language[0:2]
|
||||
return language
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -528,7 +528,7 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
count, err := object.GetUserCount(application.Organization, "", "")
|
||||
count, err := object.GetUserCount(application.Organization, "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -21,6 +21,16 @@ import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// Enforce
|
||||
// @Title Enforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin Enforce API
|
||||
// @Param body body object.CasbinRequest true "Casbin request"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param resourceId query string false "resource id"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /enforce [post]
|
||||
func (c *ApiController) Enforce() {
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
@@ -34,33 +44,59 @@ func (c *ApiController) Enforce() {
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
c.ResponseOk(object.Enforce(permissionId, &request))
|
||||
enforceResult, err := object.Enforce(permissionId, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseOk(res)
|
||||
return
|
||||
}
|
||||
|
||||
permissions := make([]*object.Permission, 0)
|
||||
res := []bool{}
|
||||
|
||||
permissions := []*object.Permission{}
|
||||
if modelId != "" {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
} else if resourceId != "" {
|
||||
permissions, err = object.GetPermissionsByResource(resourceId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.Enforce(permission.GetId(), &request))
|
||||
enforceResult, err := object.Enforce(permission.GetId(), &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
c.ResponseOk(res)
|
||||
}
|
||||
|
||||
// BatchEnforce
|
||||
// @Title BatchEnforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin BatchEnforce API
|
||||
// @Param body body object.CasbinRequest true "array of casbin requests"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /batch-enforce [post]
|
||||
func (c *ApiController) BatchEnforce() {
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
@@ -68,26 +104,47 @@ func (c *ApiController) BatchEnforce() {
|
||||
var requests []object.CasbinRequest
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if permissionId != "" {
|
||||
c.Data["json"] = object.BatchEnforce(permissionId, &requests)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions, err := object.GetPermissionsByModel(owner, modelName)
|
||||
enforceResult, err := object.BatchEnforce(permissionId, &requests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
for _, permission := range permissions {
|
||||
res = append(res, object.BatchEnforce(permission.GetId(), &requests))
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseOk(res)
|
||||
return
|
||||
}
|
||||
|
||||
permissions := []*object.Permission{}
|
||||
if modelId != "" {
|
||||
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
for _, permission := range permissions {
|
||||
enforceResult, err := object.BatchEnforce(permission.GetId(), &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res = append(res, enforceResult)
|
||||
}
|
||||
c.ResponseOk(res)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
|
148
controllers/group.go
Normal file
148
controllers/group.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGroups
|
||||
// @Title GetGroups
|
||||
// @Tag Group API
|
||||
// @Description get groups
|
||||
// @Param owner query string true "The owner of groups"
|
||||
// @Success 200 {array} object.Group The Response object
|
||||
// @router /get-groups [get]
|
||||
func (c *ApiController) GetGroups() {
|
||||
owner := c.Input().Get("owner")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
withTree := c.Input().Get("withTree")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
groups, err := object.GetGroups(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
if withTree == "true" {
|
||||
c.ResponseOk(object.ConvertToTreeData(groups, owner))
|
||||
return
|
||||
}
|
||||
c.ResponseOk(groups)
|
||||
}
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetGroupCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
groups, err := object.GetPaginationGroups(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
c.ResponseOk(groups, paginator.Nums())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetGroup
|
||||
// @Title GetGroup
|
||||
// @Tag Group API
|
||||
// @Description get group
|
||||
// @Param id query string true "The id ( owner/name ) of the group"
|
||||
// @Success 200 {object} object.Group The Response object
|
||||
// @router /get-group [get]
|
||||
func (c *ApiController) GetGroup() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
group, err := object.GetGroup(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
c.ResponseOk(group)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateGroup
|
||||
// @Title UpdateGroup
|
||||
// @Tag Group API
|
||||
// @Description update group
|
||||
// @Param id query string true "The id ( owner/name ) of the group"
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-group [post]
|
||||
func (c *ApiController) UpdateGroup() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateGroup(id, &group))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddGroup
|
||||
// @Title AddGroup
|
||||
// @Tag Group API
|
||||
// @Description add group
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-group [post]
|
||||
func (c *ApiController) AddGroup() {
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddGroup(&group))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteGroup
|
||||
// @Title DeleteGroup
|
||||
// @Tag Group API
|
||||
// @Description delete group
|
||||
// @Param body body object.Group true "The details of the group"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-group [post]
|
||||
func (c *ApiController) DeleteGroup() {
|
||||
var group object.Group
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteGroup(&group))
|
||||
c.ServeJSON()
|
||||
}
|
@@ -34,6 +34,7 @@ import (
|
||||
// @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")
|
||||
@@ -41,7 +42,7 @@ func (c *ApiController) GetMessages() {
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
chat := c.Input().Get("chat")
|
||||
organization := c.Input().Get("organization")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var messages []*object.Message
|
||||
var err error
|
||||
|
@@ -47,21 +47,31 @@ func (c *ApiController) GetOrganizations() {
|
||||
c.Data["json"] = maskedOrganizations
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetOrganizationCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
if !isGlobalAdmin {
|
||||
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(maskedOrganizations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetOrganizationCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(organizations, paginator.Nums())
|
||||
c.ResponseOk(organizations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,14 +84,13 @@ func (c *ApiController) GetOrganizations() {
|
||||
// @router /get-organization [get]
|
||||
func (c *ApiController) GetOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
maskedOrganization, err := object.GetMaskedOrganization(object.GetOrganization(id))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = maskedOrganization
|
||||
c.ServeJSON()
|
||||
c.ResponseOk(maskedOrganization)
|
||||
}
|
||||
|
||||
// UpdateOrganization ...
|
||||
@@ -180,12 +189,12 @@ func (c *ApiController) GetDefaultApplication() {
|
||||
// @Title GetOrganizationNames
|
||||
// @Tag Organization API
|
||||
// @Param owner query string true "owner"
|
||||
// @Description get all organization names
|
||||
// @Description get all organization name and displayName
|
||||
// @Success 200 {array} object.Organization The Response object
|
||||
// @router /get-organization-names [get]
|
||||
func (c *ApiController) GetOrganizationNames() {
|
||||
owner := c.Input().Get("owner")
|
||||
organizationNames, err := object.GetOrganizationsByFields(owner, "name")
|
||||
organizationNames, err := object.GetOrganizationsByFields(owner, []string{"name", "display_name"}...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -31,6 +31,7 @@ import (
|
||||
// @router /get-payments [get]
|
||||
func (c *ApiController) GetPayments() {
|
||||
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")
|
||||
@@ -48,13 +49,13 @@ func (c *ApiController) GetPayments() {
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetPaymentCount(owner, field, value)
|
||||
count, err := object.GetPaymentCount(owner, organization, field, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
payments, err := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
payments, err := object.GetPaginationPayments(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -177,10 +178,11 @@ func (c *ApiController) NotifyPayment() {
|
||||
providerName := c.Ctx.Input.Param(":provider")
|
||||
productName := c.Ctx.Input.Param(":product")
|
||||
paymentName := c.Ctx.Input.Param(":payment")
|
||||
orderId := c.Ctx.Input.Param("order")
|
||||
|
||||
body := c.Ctx.Input.RequestBody
|
||||
|
||||
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
||||
err, errorResponse := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName, orderId)
|
||||
|
||||
_, err2 := c.Ctx.ResponseWriter.Write([]byte(errorResponse))
|
||||
if err2 != nil {
|
||||
|
@@ -180,11 +180,11 @@ func (c *ApiController) BuyProduct() {
|
||||
return
|
||||
}
|
||||
|
||||
payUrl, err := object.BuyProduct(id, providerName, user, host)
|
||||
payUrl, orderId, err := object.BuyProduct(id, providerName, user, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payUrl)
|
||||
c.ResponseOk(payUrl, orderId)
|
||||
}
|
||||
|
@@ -139,8 +139,9 @@ func (c *ApiController) DeleteResource() {
|
||||
return
|
||||
}
|
||||
|
||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
provider, err := c.GetProviderFromContext("Storage")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -187,8 +188,9 @@ func (c *ApiController) UploadResource() {
|
||||
return
|
||||
}
|
||||
|
||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
provider, err := c.GetProviderFromContext("Storage")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -69,9 +69,9 @@ func (c *ApiController) SendEmail() {
|
||||
|
||||
} else {
|
||||
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
|
||||
var ok bool
|
||||
provider, _, ok = c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
provider, err = c.GetProviderFromContext("Email")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -127,13 +127,14 @@ func (c *ApiController) SendEmail() {
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, _, ok := c.GetProviderFromContext("SMS")
|
||||
if !ok {
|
||||
provider, err := c.GetProviderFromContext("SMS")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm SmsForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
err = json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -80,6 +80,7 @@ func (c *ApiController) GetGlobalUsers() {
|
||||
// @router /get-users [get]
|
||||
func (c *ApiController) GetUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
groupId := c.Input().Get("groupId")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
@@ -88,6 +89,16 @@ func (c *ApiController) GetUsers() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
if groupId != "" {
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetGroupUsers(groupId))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.ResponseOk(maskedUsers)
|
||||
return
|
||||
}
|
||||
|
||||
maskedUsers, err := object.GetMaskedUsers(object.GetUsers(owner))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -97,14 +108,14 @@ func (c *ApiController) GetUsers() {
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetUserCount(owner, field, value)
|
||||
count, err := object.GetUserCount(owner, field, value, groupId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -287,7 +298,7 @@ func (c *ApiController) AddUser() {
|
||||
return
|
||||
}
|
||||
|
||||
count, err := object.GetUserCount("", "", "")
|
||||
count, err := object.GetUserCount("", "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -505,7 +516,7 @@ func (c *ApiController) GetUserCount() {
|
||||
var count int64
|
||||
var err error
|
||||
if isOnline == "" {
|
||||
count, err = object.GetUserCount(owner, "", "")
|
||||
count, err = object.GetUserCount(owner, "", "", "")
|
||||
} else {
|
||||
count, err = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
|
||||
}
|
||||
@@ -517,3 +528,34 @@ func (c *ApiController) GetUserCount() {
|
||||
c.Data["json"] = count
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddUserkeys
|
||||
// @Title AddUserkeys
|
||||
// @router /add-user-keys [post]
|
||||
// @Tag User API
|
||||
func (c *ApiController) AddUserkeys() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := c.IsAdmin()
|
||||
affected, err := object.AddUserkeys(&user, isAdmin)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(affected)
|
||||
}
|
||||
|
||||
func (c *ApiController) RemoveUserFromGroup() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
name := c.Ctx.Request.Form.Get("name")
|
||||
groupId := c.Ctx.Request.Form.Get("groupId")
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.RemoveUserFromGroup(owner, name, groupId))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@@ -139,47 +139,46 @@ func (c *ApiController) IsMaskedEnabled() (bool, bool) {
|
||||
return true, isMaskEnabled
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
|
||||
providerName := c.Input().Get("provider")
|
||||
if providerName != "" {
|
||||
provider, err := object.GetProvider(util.GetId("admin", providerName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("util:The provider: %s is not found"), providerName))
|
||||
return nil, nil, false
|
||||
err = fmt.Errorf(c.T("util:The provider: %s is not found"), providerName)
|
||||
return nil, err
|
||||
}
|
||||
return provider, nil, true
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
return nil, fmt.Errorf(c.T("general:Please login first"))
|
||||
}
|
||||
|
||||
application, user, err := object.GetApplicationByUserId(userId)
|
||||
application, err := object.GetApplicationByUserId(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("util:No application is found for userId: %s"), userId))
|
||||
return nil, nil, false
|
||||
return nil, fmt.Errorf(c.T("util:No application is found for userId: %s"), userId)
|
||||
}
|
||||
|
||||
provider, err := application.GetProviderByCategory(category)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name))
|
||||
return nil, nil, false
|
||||
return nil, fmt.Errorf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name)
|
||||
}
|
||||
|
||||
return provider, user, true
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func checkQuotaForApplication(count int) error {
|
||||
|
1
go.mod
1
go.mod
@@ -59,6 +59,7 @@ require (
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/xorm-io/builder v0.3.13 // indirect
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
|
11
main.go
11
main.go
@@ -30,13 +30,18 @@ import (
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
|
||||
func getCreateDatabaseFlag() bool {
|
||||
res := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
|
||||
flag.Parse()
|
||||
return *res
|
||||
}
|
||||
|
||||
func main() {
|
||||
createDatabase := getCreateDatabaseFlag()
|
||||
|
||||
object.InitAdapter()
|
||||
object.CreateTables(createDatabase)
|
||||
object.DoMigration()
|
||||
object.CreateTables(*createDatabase)
|
||||
|
||||
object.InitDb()
|
||||
object.InitFromFile()
|
||||
|
@@ -41,18 +41,26 @@ func InitConfig() {
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
|
||||
InitAdapter()
|
||||
DoMigration()
|
||||
CreateTables(true)
|
||||
DoMigration()
|
||||
}
|
||||
|
||||
func InitAdapter() {
|
||||
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
||||
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
adapter.Engine.SetTableMapper(tbMapper)
|
||||
}
|
||||
|
||||
func CreateTables(createDatabase bool) {
|
||||
if createDatabase {
|
||||
adapter.CreateDatabase()
|
||||
err := adapter.CreateDatabase()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.createTable()
|
||||
}
|
||||
|
||||
@@ -122,10 +130,6 @@ func (a *Adapter) createTable() {
|
||||
showSql := conf.GetConfigBool("showSql")
|
||||
a.Engine.ShowSQL(showSql)
|
||||
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
a.Engine.SetTableMapper(tbMapper)
|
||||
|
||||
err := a.Engine.Sync2(new(Organization))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -136,6 +140,16 @@ func (a *Adapter) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Group))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(UserGroupRelation))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Role))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -295,6 +309,9 @@ func GetSessionForUser(owner string, offset, limit int, field, value, sortField,
|
||||
}
|
||||
if field != "" && value != "" {
|
||||
if filterField(field) {
|
||||
if offset != -1 {
|
||||
field = fmt.Sprintf("a.%s", field)
|
||||
}
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
}
|
||||
@@ -302,7 +319,8 @@ func GetSessionForUser(owner string, offset, limit int, field, value, sortField,
|
||||
sortField = "created_time"
|
||||
}
|
||||
|
||||
tableName := "user"
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
tableName := tableNamePrefix + "user"
|
||||
if offset == -1 {
|
||||
if sortOrder == "ascend" {
|
||||
session = session.Asc(util.SnakeString(sortField))
|
||||
|
@@ -235,16 +235,16 @@ func GetApplicationByUser(user *User) (*Application, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByUserId(userId string) (application *Application, user *User, err error) {
|
||||
func GetApplicationByUserId(userId string) (application *Application, err error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
if owner == "app" {
|
||||
application, err = getApplication("admin", name)
|
||||
return
|
||||
}
|
||||
|
||||
user, err = GetUser(userId)
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
application, err = GetApplicationByUser(user)
|
||||
return
|
||||
|
228
object/group.go
Normal file
228
object/group.go
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk unique" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100) not null index" json:"id"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Manager string `xorm:"varchar(100)" json:"manager"`
|
||||
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
ParentId string `xorm:"varchar(100)" json:"parentId"`
|
||||
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
|
||||
Users *[]string `xorm:"-" json:"users"`
|
||||
|
||||
Title string `json:"title,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Children []*Group `json:"children,omitempty"`
|
||||
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
type GroupNode struct{}
|
||||
|
||||
func GetGroupCount(owner, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Group{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func GetGroups(owner string) ([]*Group, error) {
|
||||
groups := []*Group{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&groups, &Group{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func GetPaginationGroups(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Group, error) {
|
||||
groups := []*Group{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func getGroup(owner string, name string) (*Group, error) {
|
||||
if owner == "" || name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
group := Group{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &group, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupById(id string) (*Group, error) {
|
||||
if id == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
group := Group{Id: id}
|
||||
existed, err := adapter.Engine.Get(&group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &group, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetGroup(id string) (*Group, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getGroup(owner, name)
|
||||
}
|
||||
|
||||
func UpdateGroup(id string, group *Group) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
oldGroup, err := getGroup(owner, name)
|
||||
if oldGroup == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
group.UpdatedTime = util.GetCurrentTime()
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(group)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddGroup(group *Group) (bool, error) {
|
||||
if group.Id == "" {
|
||||
group.Id = util.GenerateId()
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(group)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddGroups(groups []*Group) (bool, error) {
|
||||
if len(groups) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
affected, err := adapter.Engine.Insert(groups)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func DeleteGroup(group *Group) (bool, error) {
|
||||
_, err := adapter.Engine.Get(group)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count, err := adapter.Engine.Where("parent_id = ?", group.Id).Count(&Group{}); err != nil {
|
||||
return false, err
|
||||
} else if count > 0 {
|
||||
return false, errors.New("group has children group")
|
||||
}
|
||||
|
||||
if count, err := GetGroupUserCount(group.GetId(), "", ""); err != nil {
|
||||
return false, err
|
||||
} else if count > 0 {
|
||||
return false, errors.New("group has users")
|
||||
}
|
||||
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
if err := session.Begin(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := session.Delete(&UserGroupRelation{GroupId: group.Id}); err != nil {
|
||||
session.Rollback()
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := session.ID(core.PK{group.Owner, group.Name}).Delete(&Group{})
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := session.Commit(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (group *Group) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", group.Owner, group.Name)
|
||||
}
|
||||
|
||||
func ConvertToTreeData(groups []*Group, parentId string) []*Group {
|
||||
treeData := []*Group{}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.ParentId == parentId {
|
||||
node := &Group{
|
||||
Title: group.DisplayName,
|
||||
Key: group.Name,
|
||||
Type: group.Type,
|
||||
Owner: group.Owner,
|
||||
Id: group.Id,
|
||||
}
|
||||
children := ConvertToTreeData(groups, group.Id)
|
||||
if len(children) > 0 {
|
||||
node.Children = children
|
||||
}
|
||||
treeData = append(treeData, node)
|
||||
}
|
||||
}
|
||||
return treeData
|
||||
}
|
@@ -61,6 +61,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
|
||||
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "Groups", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
|
||||
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
|
||||
|
@@ -14,7 +14,10 @@
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/casdoor/casdoor/util"
|
||||
import (
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type InitData struct {
|
||||
Organizations []*Organization `json:"organizations"`
|
||||
@@ -35,7 +38,12 @@ type InitData struct {
|
||||
}
|
||||
|
||||
func InitFromFile() {
|
||||
initData, err := readInitDataFromFile("./init_data.json")
|
||||
initDataFile := conf.GetConfigString("initDataFile")
|
||||
if initDataFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
initData, err := readInitDataFromFile(initDataFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/builder"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
@@ -75,11 +76,18 @@ func GetOrganizationCount(owner, field, value string) (int64, error) {
|
||||
return session.Count(&Organization{})
|
||||
}
|
||||
|
||||
func GetOrganizations(owner string) ([]*Organization, error) {
|
||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||
organizations := []*Organization{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if name != nil && len(name) > 0 {
|
||||
err := adapter.Engine.Desc("created_time").Where(builder.In("name", name)).Find(&organizations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return organizations, nil
|
||||
@@ -334,6 +342,13 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
group := new(Group)
|
||||
group.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(group)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := new(Role)
|
||||
_, err = adapter.Engine.Where("owner=?", oldName).Get(role)
|
||||
if err != nil {
|
||||
|
@@ -56,9 +56,9 @@ type Payment struct {
|
||||
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
|
||||
}
|
||||
|
||||
func GetPaymentCount(owner, field, value string) (int64, error) {
|
||||
func GetPaymentCount(owner, organization, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Payment{})
|
||||
return session.Count(&Payment{Organization: organization})
|
||||
}
|
||||
|
||||
func GetPayments(owner string) ([]*Payment, error) {
|
||||
@@ -81,10 +81,10 @@ func GetUserPayments(owner string, organization string, user string) ([]*Payment
|
||||
return payments, nil
|
||||
}
|
||||
|
||||
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Payment, error) {
|
||||
func GetPaginationPayments(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Payment, error) {
|
||||
payments := []*Payment{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&payments)
|
||||
err := session.Find(&payments, &Payment{Organization: organization})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func DeletePayment(payment *Payment) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error, string) {
|
||||
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (*Payment, error, string) {
|
||||
provider, err := getProvider(owner, providerName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -180,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
|
||||
return payment, err, pProvider.GetResponseError(err)
|
||||
}
|
||||
|
||||
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
||||
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
|
||||
if err != nil {
|
||||
return payment, err, pProvider.GetResponseError(err)
|
||||
}
|
||||
@@ -199,8 +199,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, providerNam
|
||||
return payment, err, pProvider.GetResponseError(err)
|
||||
}
|
||||
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (error, string) {
|
||||
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
||||
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string, orderId string) (error, string) {
|
||||
payment, err, errorResponse := notifyPayment(request, body, owner, providerName, productName, paymentName, orderId)
|
||||
if payment != nil {
|
||||
if err != nil {
|
||||
payment.State = "Error"
|
||||
|
@@ -241,33 +241,29 @@ func removePolicies(permission *Permission) {
|
||||
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
func Enforce(permissionId string, request *CasbinRequest) bool {
|
||||
func Enforce(permissionId string, request *CasbinRequest) (bool, error) {
|
||||
permission, err := GetPermission(permissionId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
enforcer := getEnforcer(permission)
|
||||
|
||||
allow, err := enforcer.Enforce(*request...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return allow
|
||||
return enforcer.Enforce(*request...)
|
||||
}
|
||||
|
||||
func BatchEnforce(permissionId string, requests *[]CasbinRequest) []bool {
|
||||
func BatchEnforce(permissionId string, requests *[]CasbinRequest) ([]bool, error) {
|
||||
permission, err := GetPermission(permissionId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
res := []bool{}
|
||||
for i := 0; i < len(*requests); i++ {
|
||||
res = append(res, false)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
enforcer := getEnforcer(permission)
|
||||
allow, err := enforcer.BatchEnforce(*requests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return allow
|
||||
return enforcer.BatchEnforce(*requests)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
|
||||
|
@@ -156,24 +156,24 @@ func (product *Product) getProvider(providerId string) (*Provider, error) {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
|
||||
func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) {
|
||||
product, err := GetProduct(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if product == nil {
|
||||
return "", fmt.Errorf("the product: %s does not exist", id)
|
||||
return "", "", fmt.Errorf("the product: %s does not exist", id)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
pProvider, _, err := provider.getPaymentProvider()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
@@ -186,9 +186,9 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||
|
||||
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
payment := Payment{
|
||||
@@ -217,14 +217,14 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
|
||||
|
||||
affected, err := AddPayment(&payment)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if !affected {
|
||||
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
}
|
||||
|
||||
return payUrl, err
|
||||
return payUrl, orderId, err
|
||||
}
|
||||
|
||||
func ExtendProductWithProviders(product *Product) error {
|
||||
|
@@ -38,7 +38,7 @@ func TestProduct(t *testing.T) {
|
||||
paymentName := util.GenerateTimeId()
|
||||
returnUrl := ""
|
||||
notifyUrl := ""
|
||||
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl)
|
||||
payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -225,8 +225,10 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
|
||||
session = session.Omit("client_secret2")
|
||||
}
|
||||
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
if provider.Type != "Keycloak" {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
}
|
||||
|
||||
affected, err := session.Update(provider)
|
||||
if err != nil {
|
||||
@@ -237,8 +239,10 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) (bool, error) {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
if provider.Type != "Keycloak" {
|
||||
provider.Endpoint = util.GetEndPoint(provider.Endpoint)
|
||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
|
@@ -628,7 +628,12 @@ func GetPasswordToken(application *Application, username string, password string
|
||||
ErrorDescription: "the user does not exist",
|
||||
}, nil
|
||||
}
|
||||
msg := CheckPassword(user, password, "en")
|
||||
var msg string
|
||||
if user.Ldap != "" {
|
||||
msg = checkLdapUserPassword(user, password, "en")
|
||||
} else {
|
||||
msg = CheckPassword(user, password, "en")
|
||||
}
|
||||
if msg != "" {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
|
@@ -77,6 +77,9 @@ type User struct {
|
||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||
Groups []string `xorm:"varchar(1000)" json:"groups"`
|
||||
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
|
||||
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
||||
|
||||
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||
@@ -218,8 +221,13 @@ func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOr
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUserCount(owner, field, value string) (int64, error) {
|
||||
func GetUserCount(owner, field, value string, groupId string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
|
||||
if groupId != "" {
|
||||
return GetGroupUserCount(groupId, field, value)
|
||||
}
|
||||
|
||||
return session.Count(&User{})
|
||||
}
|
||||
|
||||
@@ -257,14 +265,18 @@ func GetSortedUsers(owner string, sorter string, limit int) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
||||
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string, groupId string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
|
||||
if groupId != "" {
|
||||
return GetPaginationGroupUsers(groupId, offset, limit, field, value, sortField, sortOrder)
|
||||
}
|
||||
|
||||
session := GetSessionForUser(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
@@ -375,6 +387,23 @@ func GetUserByUserId(owner string, userId string) (*User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByAccessKey(accessKey string) (*User, error) {
|
||||
if accessKey == "" {
|
||||
return nil, nil
|
||||
}
|
||||
user := User{AccessKey: accessKey}
|
||||
existed, err := adapter.Engine.Get(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetUser(id string) (*User, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getUser(owner, name)
|
||||
@@ -479,7 +508,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
"owner", "display_name", "avatar",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time",
|
||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "secret_key",
|
||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||
@@ -493,7 +522,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
columns = append(columns, "name", "email", "phone", "country_code")
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
|
||||
affected, err := updateUser(oldUser, user, columns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -501,6 +530,35 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func updateUser(oldUser, user *User, columns []string) (int64, error) {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
session.Begin()
|
||||
|
||||
if util.ContainsString(columns, "groups") {
|
||||
affected, err := updateUserGroupRelation(session, user)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return affected, err
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := session.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return affected, err
|
||||
}
|
||||
|
||||
err = session.Commit()
|
||||
if err != nil {
|
||||
session.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
var err error
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
@@ -580,7 +638,7 @@ func AddUser(user *User) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
count, err := GetUserCount(user.Owner, "", "")
|
||||
count, err := GetUserCount(user.Owner, "", "", "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -668,6 +726,11 @@ func DeleteUser(user *User) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err = deleteRelationByUser(user.Id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
@@ -852,3 +915,14 @@ func (user *User) GetPreferMfa(masked bool) *MfaProps {
|
||||
return user.MultiFactorAuths[0]
|
||||
}
|
||||
}
|
||||
|
||||
func AddUserkeys(user *User, isAdmin bool) (bool, error) {
|
||||
if user == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
user.AccessKey = util.GenerateId()
|
||||
user.SecretKey = util.GenerateId()
|
||||
|
||||
return UpdateUser(user.GetId(), user, []string{}, isAdmin)
|
||||
}
|
||||
|
157
object/user_group.go
Normal file
157
object/user_group.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
"github.com/xorm-io/xorm"
|
||||
)
|
||||
|
||||
type UserGroupRelation struct {
|
||||
UserId string `xorm:"varchar(100) notnull pk" json:"userId"`
|
||||
GroupId string `xorm:"varchar(100) notnull pk" json:"groupId"`
|
||||
|
||||
CreatedTime string `xorm:"created" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"updated" json:"updatedTime"`
|
||||
}
|
||||
|
||||
func updateUserGroupRelation(session *xorm.Session, user *User) (int64, error) {
|
||||
physicalGroupCount, err := session.Where("type = ?", "Physical").In("id", user.Groups).Count(Group{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if physicalGroupCount > 1 {
|
||||
return 0, errors.New("user can only be in one physical group")
|
||||
}
|
||||
|
||||
groups := []*Group{}
|
||||
err = session.In("id", user.Groups).Find(&groups)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(groups) != len(user.Groups) {
|
||||
return 0, errors.New("group not found")
|
||||
}
|
||||
|
||||
_, err = session.Delete(&UserGroupRelation{UserId: user.Id})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
relations := []*UserGroupRelation{}
|
||||
for _, group := range groups {
|
||||
relations = append(relations, &UserGroupRelation{UserId: user.Id, GroupId: group.Id})
|
||||
}
|
||||
if len(relations) == 0 {
|
||||
return 1, nil
|
||||
}
|
||||
_, err = session.Insert(relations)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func RemoveUserFromGroup(owner, name, groupId string) (bool, error) {
|
||||
user, err := getUser(owner, name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
groups := []string{}
|
||||
for _, group := range user.Groups {
|
||||
if group != groupId {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
}
|
||||
user.Groups = groups
|
||||
|
||||
_, err = UpdateUser(util.GetId(owner, name), user, []string{"groups"}, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func deleteUserGroupRelation(session *xorm.Session, userId, groupId string) (int64, error) {
|
||||
affected, err := session.ID(core.PK{userId, groupId}).Delete(&UserGroupRelation{})
|
||||
return affected, err
|
||||
}
|
||||
|
||||
func deleteRelationByUser(id string) (int64, error) {
|
||||
affected, err := adapter.Engine.Delete(&UserGroupRelation{UserId: id})
|
||||
return affected, err
|
||||
}
|
||||
|
||||
func GetGroupUserCount(id string, field, value string) (int64, error) {
|
||||
group, err := GetGroup(id)
|
||||
if group == nil || err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if field == "" && value == "" {
|
||||
return adapter.Engine.Count(UserGroupRelation{GroupId: group.Id})
|
||||
} else {
|
||||
return adapter.Engine.Table("user").
|
||||
Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id").
|
||||
Where("r.group_id = ?", group.Id).
|
||||
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
|
||||
Count()
|
||||
}
|
||||
}
|
||||
|
||||
func GetPaginationGroupUsers(id string, offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
||||
group, err := GetGroup(id)
|
||||
if group == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := []*User{}
|
||||
session := adapter.Engine.Table("user").
|
||||
Join("INNER", []string{"user_group_relation", "r"}, "user.id = r.user_id").
|
||||
Where("r.group_id = ?", group.Id)
|
||||
|
||||
if offset != -1 && limit != -1 {
|
||||
session.Limit(limit, offset)
|
||||
}
|
||||
|
||||
if field != "" && value != "" {
|
||||
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%")
|
||||
}
|
||||
|
||||
if sortField == "" || sortOrder == "" {
|
||||
sortField = "created_time"
|
||||
}
|
||||
if sortOrder == "ascend" {
|
||||
session = session.Asc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
|
||||
} else {
|
||||
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
|
||||
}
|
||||
|
||||
err = session.Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetGroupUsers(id string) ([]*User, error) {
|
||||
group, err := GetGroup(id)
|
||||
if group == nil || err != nil {
|
||||
return []*User{}, err
|
||||
}
|
||||
|
||||
users := []*User{}
|
||||
err = adapter.Engine.Table("user_group_relation").Join("INNER", []string{"user", "u"}, "user_group_relation.user_id = u.id").
|
||||
Where("user_group_relation.group_id = ?", group.Id).
|
||||
Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
@@ -295,6 +295,13 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
oldUserGroupsJson, _ := json.Marshal(oldUser.Groups)
|
||||
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
||||
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
||||
item := GetAccountItemByName("Groups", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||
item := GetAccountItemByName("Is admin", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
|
@@ -224,7 +224,7 @@ func GetVerifyType(username string) (verificationCodeType string) {
|
||||
if strings.Contains(username, "@") {
|
||||
return VerifyTypeEmail
|
||||
} else {
|
||||
return VerifyTypeEmail
|
||||
return VerifyTypePhone
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
@@ -62,12 +62,12 @@ func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, pa
|
||||
|
||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return payUrl, nil
|
||||
return payUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
|
@@ -26,12 +26,12 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
payUrl := fmt.Sprintf("/payments/%s/result", paymentName)
|
||||
return payUrl, nil
|
||||
return payUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
return "", "", 0, "", "", nil
|
||||
}
|
||||
|
||||
|
20
pp/gc.go
20
pp/gc.go
@@ -153,7 +153,7 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
payReqInfo := GcPayReqInfo{
|
||||
OrderDate: util.GenerateSimpleTimeId(),
|
||||
OrderNo: paymentName,
|
||||
@@ -168,7 +168,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
||||
|
||||
b, err := json.Marshal(payReqInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
body := GcRequestBody{
|
||||
@@ -184,39 +184,39 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
respBytes, err := pp.doPost(bodyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var respBody GcResponseBody
|
||||
err = json.Unmarshal(respBytes, &respBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if respBody.ReturnCode != "SUCCESS" {
|
||||
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||
return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||
}
|
||||
|
||||
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var payRespInfo GcPayRespInfo
|
||||
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return payRespInfo.PayUrl, nil
|
||||
return payRespInfo.PayUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
reqBody := GcRequestBody{}
|
||||
m, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
|
92
pp/paypal.go
92
pp/paypal.go
@@ -16,10 +16,13 @@ package pp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/plutov/paypal/v4"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/paypal"
|
||||
"github.com/go-pay/gopay/pkg/util"
|
||||
)
|
||||
|
||||
type PaypalPaymentProvider struct {
|
||||
@@ -29,7 +32,7 @@ type PaypalPaymentProvider struct {
|
||||
func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentProvider, error) {
|
||||
pp := &PaypalPaymentProvider{}
|
||||
|
||||
client, err := paypal.NewClient(clientID, secret, paypal.APIBaseSandBox)
|
||||
client, err := paypal.NewClient(clientID, secret, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,51 +41,62 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
// pp.Client.SetLog(os.Stdout) // Set log to terminal stdout
|
||||
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn // Set log to terminal stdout
|
||||
|
||||
receiverEmail := "sb-tmsqa26118644@business.example.com"
|
||||
|
||||
amount := paypal.AmountPayout{
|
||||
Value: fmt.Sprintf("%.2f", price),
|
||||
Currency: "USD",
|
||||
}
|
||||
|
||||
description := fmt.Sprintf("%s-%s", providerName, productName)
|
||||
|
||||
payout := paypal.Payout{
|
||||
SenderBatchHeader: &paypal.SenderBatchHeader{
|
||||
EmailSubject: description,
|
||||
},
|
||||
Items: []paypal.PayoutItem{
|
||||
{
|
||||
RecipientType: "EMAIL",
|
||||
Receiver: receiverEmail,
|
||||
Amount: &amount,
|
||||
Note: description,
|
||||
SenderItemID: description,
|
||||
},
|
||||
priceStr := strconv.FormatFloat(price, 'f', 2, 64)
|
||||
var pus []*paypal.PurchaseUnit
|
||||
item := &paypal.PurchaseUnit{
|
||||
ReferenceId: util.GetRandomString(16),
|
||||
Amount: &paypal.Amount{
|
||||
CurrencyCode: currency,
|
||||
Value: priceStr,
|
||||
},
|
||||
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
|
||||
}
|
||||
pus = append(pus, item)
|
||||
|
||||
_, err := pp.Client.GetAccessToken(context.Background())
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("intent", "CAPTURE")
|
||||
bm.Set("purchase_units", pus)
|
||||
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
|
||||
b.Set("brand_name", "Casdoor")
|
||||
b.Set("locale", "en-PT")
|
||||
b.Set("return_url", returnUrl)
|
||||
})
|
||||
|
||||
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
if ppRsp.Code != paypal.Success {
|
||||
return "", "", errors.New(ppRsp.Error)
|
||||
}
|
||||
|
||||
payoutResponse, err := pp.Client.CreatePayout(context.Background(), payout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
payUrl := payoutResponse.Links[0].Href
|
||||
return payUrl, nil
|
||||
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
// The PayPal SDK does not directly support IPN verification.
|
||||
// So, you need to implement this part according to PayPal's IPN guide.
|
||||
return "", "", 0, "", "", nil
|
||||
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
ppRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
if ppRsp.Code != paypal.Success {
|
||||
return "", "", 0, "", "", errors.New(ppRsp.Error)
|
||||
}
|
||||
|
||||
paymentName := ppRsp.Response.Id
|
||||
price, err := strconv.ParseFloat(ppRsp.Response.PurchaseUnits[0].Amount.Value, 64)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
productDisplayName, productName, providerName, err := parseAttachString(ppRsp.Response.PurchaseUnits[0].Description)
|
||||
if err != nil {
|
||||
return "", "", 0, "", "", err
|
||||
}
|
||||
|
||||
return productDisplayName, paymentName, price, productName, providerName, nil
|
||||
}
|
||||
|
||||
func (pp *PaypalPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
|
@@ -17,8 +17,8 @@ package pp
|
||||
import "net/http"
|
||||
|
||||
type PaymentProvider interface {
|
||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
|
||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
|
||||
Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error)
|
||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||
GetResponseError(err error) string
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||
|
||||
bm := gopay.BodyMap{}
|
||||
@@ -73,17 +73,17 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
|
||||
|
||||
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if wxRsp.Code != wechat.Success {
|
||||
return "", errors.New(wxRsp.Error)
|
||||
return "", "", errors.New(wxRsp.Error)
|
||||
}
|
||||
|
||||
return wxRsp.Response.CodeUrl, nil
|
||||
return wxRsp.Response.CodeUrl, "", nil
|
||||
}
|
||||
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (string, string, float64, string, string, error) {
|
||||
notifyReq, err := wechat.V3ParseNotify(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@@ -26,8 +26,10 @@ import (
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
func getUsername(ctx *context.Context) (username string) {
|
||||
@@ -43,6 +45,9 @@ func getUsername(ctx *context.Context) (username string) {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
username = getUsernameByKeys(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,6 +103,30 @@ func getObject(ctx *context.Context) (string, string) {
|
||||
}
|
||||
}
|
||||
|
||||
func getKeys(ctx *context.Context) (string, string) {
|
||||
method := ctx.Request.Method
|
||||
|
||||
if method == http.MethodGet {
|
||||
accessKey := ctx.Input.Query("accesskey")
|
||||
secretKey := ctx.Input.Query("secretkey")
|
||||
return accessKey, secretKey
|
||||
} else {
|
||||
body := ctx.Input.RequestBody
|
||||
|
||||
if len(body) == 0 {
|
||||
return ctx.Request.Form.Get("accesskey"), ctx.Request.Form.Get("secretkey")
|
||||
}
|
||||
|
||||
var obj Object
|
||||
err := json.Unmarshal(body, &obj)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return obj.AccessKey, obj.SecretKey
|
||||
}
|
||||
}
|
||||
|
||||
func willLog(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if subOwner == "anonymous" && subName == "anonymous" && method == "GET" && (urlPath == "/api/get-account" || urlPath == "/api/get-app-login") && objOwner == "" && objName == "" {
|
||||
return false
|
||||
|
@@ -49,7 +49,7 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
userId := util.GetId(token.Organization, token.User)
|
||||
application, _, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@@ -84,6 +84,18 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
return fmt.Sprintf("app/%s", application.Name)
|
||||
}
|
||||
|
||||
func getUsernameByKeys(ctx *context.Context) string {
|
||||
accessKey, secretKey := getKeys(ctx)
|
||||
user, err := object.GetUserByAccessKey(accessKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if user != nil && secretKey == user.SecretKey {
|
||||
return user.GetId()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getSessionUser(ctx *context.Context) string {
|
||||
user := ctx.Input.CruSession.Get("username")
|
||||
if user == nil {
|
||||
|
@@ -73,9 +73,17 @@ func initAPI() {
|
||||
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
||||
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||
beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserkeys")
|
||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||
beego.Router("/api/remove-user-from-group", &controllers.ApiController{}, "POST:RemoveUserFromGroup")
|
||||
|
||||
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
|
||||
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
|
||||
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
|
||||
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
|
||||
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
|
||||
|
||||
beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
|
||||
beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
|
||||
|
@@ -777,6 +777,46 @@
|
||||
"operationId": "ApiController.HandleOfficialAccountEvent"
|
||||
}
|
||||
},
|
||||
"/api/batch-enforce": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Enforce API"
|
||||
],
|
||||
"description": "perform enforce",
|
||||
"operationId": "ApiController.BatchEnforce",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "casbin request array",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.CasbinRequest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "permissionId",
|
||||
"description": "permission id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "modelId",
|
||||
"description": "model id",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/buy-product": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -1384,6 +1424,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/enforce": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Enforce API"
|
||||
],
|
||||
"description": "perform enforce",
|
||||
"operationId": "ApiController.Enforce",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "body",
|
||||
"description": "casbin request",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/object.CasbinRequest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "permissionId",
|
||||
"description": "permission id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "modelId",
|
||||
"description": "model id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "resourceId",
|
||||
"description": "resource id",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-account": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1954,6 +2040,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-organization-names": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Organization API"
|
||||
],
|
||||
"description": "get all organization names",
|
||||
"operationId": "ApiController.GetOrganizationNames",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "owner",
|
||||
"description": "owner",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.Organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-organizations": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2826,7 +2941,6 @@
|
||||
"in": "query",
|
||||
"name": "id",
|
||||
"description": "The id ( owner/name ) of the user",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
@@ -3062,6 +3176,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/health": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"System API"
|
||||
],
|
||||
"description": "check if the system is live",
|
||||
"operationId": "ApiController.Health",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controllers.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/invoice-payment": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -4501,11 +4632,11 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"1183.0x1400042eb70.false": {
|
||||
"1225.0xc0002e2ae0.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
"1217.0x1400042eba0.false": {
|
||||
"1260.0xc0002e2b10.false": {
|
||||
"title": "false",
|
||||
"type": "object"
|
||||
},
|
||||
@@ -4554,10 +4685,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/1183.0x1400042eb70.false"
|
||||
"$ref": "#/definitions/1225.0xc0002e2ae0.false"
|
||||
},
|
||||
"data2": {
|
||||
"$ref": "#/definitions/1217.0x1400042eba0.false"
|
||||
"$ref": "#/definitions/1260.0xc0002e2b10.false"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string"
|
||||
@@ -4595,6 +4726,10 @@
|
||||
"title": "JSONWebKey",
|
||||
"type": "object"
|
||||
},
|
||||
"object.\u0026{179844 0xc000a02f90 false}": {
|
||||
"title": "\u0026{179844 0xc000a02f90 false}",
|
||||
"type": "object"
|
||||
},
|
||||
"object.AccountItem": {
|
||||
"title": "AccountItem",
|
||||
"type": "object",
|
||||
@@ -4693,6 +4828,9 @@
|
||||
"formCss": {
|
||||
"type": "string"
|
||||
},
|
||||
"formCssMobile": {
|
||||
"type": "string"
|
||||
},
|
||||
"formOffset": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
@@ -4715,6 +4853,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgChoiceMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4772,6 +4913,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.CasbinRequest": {
|
||||
"title": "CasbinRequest",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.\u0026{179844 0xc000a02f90 false}"
|
||||
}
|
||||
},
|
||||
"object.Cert": {
|
||||
"title": "Cert",
|
||||
"type": "object",
|
||||
@@ -5008,6 +5156,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.MfaItem": {
|
||||
"title": "MfaItem",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"rule": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.MfaProps": {
|
||||
"title": "MfaProps",
|
||||
"type": "object",
|
||||
@@ -5190,6 +5350,12 @@
|
||||
"masterPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"mfaItems": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/object.MfaItem"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5395,9 +5561,18 @@
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5411,9 +5586,6 @@
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5737,6 +5909,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"object": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6341,6 +6516,9 @@
|
||||
"passwordSalt": {
|
||||
"type": "string"
|
||||
},
|
||||
"passwordType": {
|
||||
"type": "string"
|
||||
},
|
||||
"patreon": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6505,6 +6683,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6634,4 +6815,4 @@
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -502,6 +502,32 @@ paths:
|
||||
tags:
|
||||
- HandleOfficialAccountEvent API
|
||||
operationId: ApiController.HandleOfficialAccountEvent
|
||||
/api/batch-enforce:
|
||||
post:
|
||||
tags:
|
||||
- Enforce API
|
||||
description: perform enforce
|
||||
operationId: ApiController.BatchEnforce
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: casbin request array
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.CasbinRequest'
|
||||
- in: query
|
||||
name: permissionId
|
||||
description: permission id
|
||||
type: string
|
||||
- in: query
|
||||
name: modelId
|
||||
description: model id
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/buy-product:
|
||||
post:
|
||||
tags:
|
||||
@@ -893,6 +919,36 @@ paths:
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/enforce:
|
||||
post:
|
||||
tags:
|
||||
- Enforce API
|
||||
description: perform enforce
|
||||
operationId: ApiController.Enforce
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: casbin request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/object.CasbinRequest'
|
||||
- in: query
|
||||
name: permissionId
|
||||
description: permission id
|
||||
type: string
|
||||
- in: query
|
||||
name: modelId
|
||||
description: model id
|
||||
type: string
|
||||
- in: query
|
||||
name: resourceId
|
||||
description: resource id
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/get-account:
|
||||
get:
|
||||
tags:
|
||||
@@ -1267,6 +1323,25 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Application'
|
||||
/api/get-organization-names:
|
||||
get:
|
||||
tags:
|
||||
- Organization API
|
||||
description: get all organization names
|
||||
operationId: ApiController.GetOrganizationNames
|
||||
parameters:
|
||||
- in: query
|
||||
name: owner
|
||||
description: owner
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Organization'
|
||||
/api/get-organizations:
|
||||
get:
|
||||
tags:
|
||||
@@ -1841,7 +1916,6 @@ paths:
|
||||
- in: query
|
||||
name: id
|
||||
description: The id ( owner/name ) of the user
|
||||
required: true
|
||||
type: string
|
||||
- in: query
|
||||
name: owner
|
||||
@@ -1994,6 +2068,17 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.Webhook'
|
||||
/api/health:
|
||||
get:
|
||||
tags:
|
||||
- System API
|
||||
description: check if the system is live
|
||||
operationId: ApiController.Health
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
schema:
|
||||
$ref: '#/definitions/controllers.Response'
|
||||
/api/invoice-payment:
|
||||
post:
|
||||
tags:
|
||||
@@ -2940,10 +3025,10 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
definitions:
|
||||
1183.0x1400042eb70.false:
|
||||
1225.0xc0002e2ae0.false:
|
||||
title: "false"
|
||||
type: object
|
||||
1217.0x1400042eba0.false:
|
||||
1260.0xc0002e2b10.false:
|
||||
title: "false"
|
||||
type: object
|
||||
LaravelResponse:
|
||||
@@ -2979,9 +3064,9 @@ definitions:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/1183.0x1400042eb70.false'
|
||||
$ref: '#/definitions/1225.0xc0002e2ae0.false'
|
||||
data2:
|
||||
$ref: '#/definitions/1217.0x1400042eba0.false'
|
||||
$ref: '#/definitions/1260.0xc0002e2b10.false'
|
||||
msg:
|
||||
type: string
|
||||
name:
|
||||
@@ -3005,6 +3090,9 @@ definitions:
|
||||
jose.JSONWebKey:
|
||||
title: JSONWebKey
|
||||
type: object
|
||||
object.&{179844 0xc000a02f90 false}:
|
||||
title: '&{179844 0xc000a02f90 false}'
|
||||
type: object
|
||||
object.AccountItem:
|
||||
title: AccountItem
|
||||
type: object
|
||||
@@ -3072,6 +3160,8 @@ definitions:
|
||||
type: string
|
||||
formCss:
|
||||
type: string
|
||||
formCssMobile:
|
||||
type: string
|
||||
formOffset:
|
||||
type: integer
|
||||
format: int64
|
||||
@@ -3087,6 +3177,8 @@ definitions:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
orgChoiceMode:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
organizationObj:
|
||||
@@ -3124,6 +3216,11 @@ definitions:
|
||||
$ref: '#/definitions/object.ThemeData'
|
||||
tokenFormat:
|
||||
type: string
|
||||
object.CasbinRequest:
|
||||
title: CasbinRequest
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.&{179844 0xc000a02f90 false}'
|
||||
object.Cert:
|
||||
title: Cert
|
||||
type: object
|
||||
@@ -3284,6 +3381,14 @@ definitions:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
object.MfaItem:
|
||||
title: MfaItem
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
rule:
|
||||
type: string
|
||||
object.MfaProps:
|
||||
title: MfaProps
|
||||
type: object
|
||||
@@ -3407,6 +3512,10 @@ definitions:
|
||||
type: string
|
||||
masterPassword:
|
||||
type: string
|
||||
mfaItems:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.MfaItem'
|
||||
name:
|
||||
type: string
|
||||
owner:
|
||||
@@ -3544,8 +3653,14 @@ definitions:
|
||||
type: string
|
||||
displayName:
|
||||
type: string
|
||||
isEnabled:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
options:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
owner:
|
||||
type: string
|
||||
pricePerMonth:
|
||||
@@ -3556,8 +3671,6 @@ definitions:
|
||||
format: double
|
||||
role:
|
||||
type: string
|
||||
options:
|
||||
type: array
|
||||
object.Pricing:
|
||||
title: Pricing
|
||||
type: object
|
||||
@@ -3775,6 +3888,8 @@ definitions:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
object:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
owner:
|
||||
@@ -4181,6 +4296,8 @@ definitions:
|
||||
type: string
|
||||
passwordSalt:
|
||||
type: string
|
||||
passwordType:
|
||||
type: string
|
||||
patreon:
|
||||
type: string
|
||||
paypal:
|
||||
@@ -4291,6 +4408,8 @@ definitions:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
organization:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
picture:
|
||||
|
@@ -50,6 +50,11 @@ class AdapterEditPage extends React.Component {
|
||||
AdapterBackend.getAdapter("admin", this.state.adapterName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
if (res.data === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
adapter: res.data,
|
||||
});
|
||||
|
@@ -15,6 +15,9 @@
|
||||
import React, {Component} from "react";
|
||||
import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import GroupTreePage from "./GroupTreePage";
|
||||
import GroupEditPage from "./GroupEdit";
|
||||
import GroupListPage from "./GroupList";
|
||||
import * as Setting from "./Setting";
|
||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||
import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||
@@ -128,10 +131,12 @@ class App extends Component {
|
||||
});
|
||||
if (uri === "/") {
|
||||
this.setState({selectedMenuKey: "/"});
|
||||
} else if (uri.includes("/organizations")) {
|
||||
} 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")) {
|
||||
@@ -405,12 +410,13 @@ class App extends Component {
|
||||
|
||||
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/"));
|
||||
|
||||
if (Setting.isAdminUser(this.state.account)) {
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
res.push(Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>,
|
||||
"/organizations"));
|
||||
}
|
||||
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
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"
|
||||
));
|
||||
@@ -552,6 +558,10 @@ class App extends Component {
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/groups" render={(props) => this.renderLoginIfNotLoggedIn(<GroupListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/groups/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||
@@ -618,6 +628,11 @@ class App extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
isWithoutCard() {
|
||||
return Setting.isMobile() || window.location.pathname === "/chat" ||
|
||||
window.location.pathname.startsWith("/trees");
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const onClick = ({key}) => {
|
||||
if (key === "/swagger") {
|
||||
@@ -628,7 +643,6 @@ class App extends Component {
|
||||
};
|
||||
return (
|
||||
<Layout id="parent-area">
|
||||
{/* https://github.com/ant-design/ant-design/issues/40394 ant design bug. If it will be fixed, we can delete the code for control the color of Header*/}
|
||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}>
|
||||
{Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
@@ -664,7 +678,7 @@ class App extends Component {
|
||||
}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{(Setting.isMobile() || window.location.pathname === "/chat") ?
|
||||
{this.isWithoutCard() ?
|
||||
this.renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{this.renderRouter()}
|
||||
|
@@ -74,7 +74,6 @@ img {
|
||||
|
||||
.content-warp-card {
|
||||
box-shadow: 0 1px 5px 0 rgb(51 51 51 / 14%);
|
||||
margin: 5px;
|
||||
flex: 1;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
@@ -119,6 +119,11 @@ class ApplicationEditPage extends React.Component {
|
||||
getApplication() {
|
||||
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||
.then((application) => {
|
||||
if (application === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
|
||||
application.grantTypes = ["authorization_code"];
|
||||
}
|
||||
|
@@ -45,6 +45,11 @@ class CertEditPage extends React.Component {
|
||||
getCert() {
|
||||
CertBackend.getCert(this.state.owner, this.state.certName)
|
||||
.then((cert) => {
|
||||
if (cert === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cert: cert,
|
||||
});
|
||||
|
@@ -108,7 +108,7 @@ class CertListPage extends BaseListPage {
|
||||
key: "owner",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
|
||||
},
|
||||
|
@@ -41,6 +41,11 @@ class ChatEditPage extends React.Component {
|
||||
getChat() {
|
||||
ChatBackend.getChat("admin", this.state.chatName)
|
||||
.then((chat) => {
|
||||
if (chat === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
chat: chat,
|
||||
});
|
||||
|
268
web/src/GroupEdit.js
Normal file
268
web/src/GroupEdit.js
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class GroupEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
groupName: props.match.params.groupName,
|
||||
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
group: null,
|
||||
users: [],
|
||||
groups: [],
|
||||
organizations: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getGroup();
|
||||
this.getGroups(this.state.organizationName);
|
||||
this.getOrganizations();
|
||||
}
|
||||
|
||||
getGroup() {
|
||||
GroupBackend.getGroup(this.state.organizationName, this.state.groupName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
if (res.data === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
group: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getGroups(organizationName) {
|
||||
GroupBackend.getGroups(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizationNames("admin")
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
organizations: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parseGroupField(key, value) {
|
||||
if ([""].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
updateGroupField(key, value) {
|
||||
value = this.parseGroupField(key, value);
|
||||
|
||||
const group = this.state.group;
|
||||
group[key] = value;
|
||||
this.setState({
|
||||
group: group,
|
||||
});
|
||||
}
|
||||
|
||||
getParentIdOptions() {
|
||||
const groups = this.state.groups.filter((group) => group.id !== this.state.group.id);
|
||||
const organization = this.state.organizations.find((organization) => organization.name === this.state.group.owner);
|
||||
if (organization !== undefined) {
|
||||
groups.push({id: organization.name, displayName: organization.displayName});
|
||||
}
|
||||
return groups.map((group) => ({label: group.displayName, value: group.id}));
|
||||
}
|
||||
|
||||
renderGroup() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
<div>
|
||||
{this.state.mode === "add" ? i18next.t("group:New Group") : i18next.t("group:Edit Group")}
|
||||
<Button onClick={() => this.submitGroupEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitGroupEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteGroup()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
}
|
||||
style={(Setting.isMobile()) ? {margin: "5px"} : {}}
|
||||
type="inner"
|
||||
>
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.group.owner}
|
||||
onChange={(value => {
|
||||
this.updateGroupField("owner", value);
|
||||
this.getGroups(value);
|
||||
})}
|
||||
options={this.state.organizations.map((organization) => Setting.getOption(organization.displayName, 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.group.name} onChange={e => {
|
||||
this.updateGroupField("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.group.displayName} onChange={e => {
|
||||
this.updateGroupField("displayName", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select style={{width: "100%"}}
|
||||
options={
|
||||
[
|
||||
{label: i18next.t("group:Virtual"), value: "Virtual"},
|
||||
{label: i18next.t("group:Physical"), value: "Physical"},
|
||||
].map((item) => ({label: item.label, value: item.value}))
|
||||
}
|
||||
value={this.state.group.type} onChange={(value => {
|
||||
this.updateGroupField("type", value);
|
||||
}
|
||||
)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("group:Parent group"), i18next.t("group:Parent group - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select style={{width: "100%"}}
|
||||
options={this.getParentIdOptions()}
|
||||
value={this.state.group.parentId} onChange={(value => {
|
||||
this.updateGroupField("parentId", value);
|
||||
}
|
||||
)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Is enabled"), i18next.t("general:Is enabled - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.group.isEnabled} onChange={checked => {
|
||||
this.updateGroupField("isEnabled", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
submitGroupEdit(willExist) {
|
||||
const group = Setting.deepCopy(this.state.group);
|
||||
group["isTopGroup"] = this.state.organizations.some((organization) => organization.name === group.parentId);
|
||||
|
||||
GroupBackend.updateGroup(this.state.organizationName, this.state.groupName, group)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
this.setState({
|
||||
groupName: this.state.group.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
const groupTreeUrl = sessionStorage.getItem("groupTreeUrl");
|
||||
if (groupTreeUrl !== null) {
|
||||
sessionStorage.removeItem("groupTreeUrl");
|
||||
this.props.history.push(groupTreeUrl);
|
||||
} else {
|
||||
this.props.history.push("/groups");
|
||||
}
|
||||
} else {
|
||||
this.props.history.push(`/groups/${this.state.group.owner}/${this.state.group.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
this.updateGroupField("name", this.state.groupName);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteGroup() {
|
||||
GroupBackend.deleteGroup(this.state.group)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const groupTreeUrl = sessionStorage.getItem("groupTreeUrl");
|
||||
if (groupTreeUrl !== null) {
|
||||
sessionStorage.removeItem("groupTreeUrl");
|
||||
this.props.history.push(groupTreeUrl);
|
||||
} else {
|
||||
this.props.history.push("/groups");
|
||||
}
|
||||
} 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.group !== null ? this.renderGroup() : null
|
||||
}
|
||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||
<Button size="large" onClick={() => this.submitGroupEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitGroupEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteGroup()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupEditPage;
|
286
web/src/GroupList.js
Normal file
286
web/src/GroupList.js
Normal file
@@ -0,0 +1,286 @@
|
||||
// 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 GroupBackend from "./backend/GroupBackend";
|
||||
import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
|
||||
class GroupListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
...this.state,
|
||||
owner: Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner,
|
||||
groups: [],
|
||||
};
|
||||
}
|
||||
UNSAFE_componentWillMount() {
|
||||
super.UNSAFE_componentWillMount();
|
||||
this.getGroups(this.state.owner);
|
||||
}
|
||||
|
||||
getGroups(organizationName) {
|
||||
GroupBackend.getGroups(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
newGroup() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: this.props.account.owner,
|
||||
name: `group_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
displayName: `New Group - ${randomName}`,
|
||||
type: "Virtual",
|
||||
parentId: this.props.account.owner,
|
||||
isTopGroup: true,
|
||||
isEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
addGroup() {
|
||||
const newGroup = this.newGroup();
|
||||
GroupBackend.addGroup(newGroup)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push({pathname: `/groups/${newGroup.owner}/${newGroup.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}`);
|
||||
});
|
||||
}
|
||||
|
||||
deleteGroup(i) {
|
||||
GroupBackend.deleteGroup(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(data) {
|
||||
const columns = [
|
||||
{
|
||||
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={`/groups/${record.owner}/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${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: "150px",
|
||||
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("general:Type"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
{text: i18next.t("group:Virtual"), value: "Virtual"},
|
||||
{text: i18next.t("group:Physical"), value: "Physical"},
|
||||
],
|
||||
render: (text, record, index) => {
|
||||
return i18next.t("group:" + text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("group:Parent group"),
|
||||
dataIndex: "parentId",
|
||||
key: "parentId",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("parentId"),
|
||||
render: (text, record, index) => {
|
||||
if (record.isTopGroup) {
|
||||
return <Link to={`/organizations/${record.parentId}`}>
|
||||
{record.parentId}
|
||||
</Link>;
|
||||
}
|
||||
const parentGroup = this.state.groups.find((group) => group.id === text);
|
||||
if (parentGroup === undefined) {
|
||||
return "";
|
||||
}
|
||||
return <Link to={`/groups/${parentGroup.owner}/${parentGroup.name}`}>
|
||||
{parentGroup?.displayName}
|
||||
</Link>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "170px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
const haveChildren = this.state.groups.find((group) => group.parentId === record.id) !== undefined;
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/groups/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
disabled={haveChildren}
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteGroup(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={data} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Groups")}
|
||||
<Button type="primary" size="small" onClick={this.addGroup.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});
|
||||
GroupBackend.getGroups(this.state.owner, false, 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default GroupListPage;
|
336
web/src/GroupTreePage.js
Normal file
336
web/src/GroupTreePage.js
Normal file
@@ -0,0 +1,336 @@
|
||||
// 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 {DeleteOutlined, EditOutlined, HolderOutlined, PlusOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Empty, Row, Space, Tree} from "antd";
|
||||
import i18next from "i18next";
|
||||
import moment from "moment/moment";
|
||||
import React from "react";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import OrganizationSelect from "./common/select/OrganizationSelect";
|
||||
import UserListPage from "./UserListPage";
|
||||
|
||||
class GroupTreePage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
owner: Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner,
|
||||
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
groupName: this.props.match?.params.groupName,
|
||||
groupId: undefined,
|
||||
treeData: [],
|
||||
selectedKeys: [this.props.match?.params.groupName],
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getTreeData();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (this.state.organizationName !== prevState.organizationName) {
|
||||
this.getTreeData();
|
||||
}
|
||||
|
||||
if (prevState.treeData !== this.state.treeData) {
|
||||
this.setTreeExpandedKeys();
|
||||
}
|
||||
}
|
||||
|
||||
getTreeData() {
|
||||
GroupBackend.getGroups(this.state.organizationName, true).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
treeData: res.data,
|
||||
groupId: this.findNodeId({children: res.data}, this.state.groupName),
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
findNodeId(node, targetName) {
|
||||
if (node.key === targetName) {
|
||||
return node.id;
|
||||
}
|
||||
if (node.children) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
const result = this.findNodeId(node.children[i], targetName);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setTreeTitle(treeData) {
|
||||
const haveChildren = Array.isArray(treeData.children) && treeData.children.length > 0;
|
||||
const isSelected = this.state.groupName === treeData.key;
|
||||
return {
|
||||
id: treeData.id,
|
||||
key: treeData.key,
|
||||
title: <Space>
|
||||
{treeData.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||
<span>{treeData.title}</span>
|
||||
{isSelected && (
|
||||
<React.Fragment>
|
||||
<PlusOutlined
|
||||
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();
|
||||
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
|
||||
this.addGroup();
|
||||
}}
|
||||
/>
|
||||
<EditOutlined
|
||||
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();
|
||||
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
|
||||
this.props.history.push(`/groups/${this.state.organizationName}/${treeData.key}`);
|
||||
}}
|
||||
/>
|
||||
{!haveChildren &&
|
||||
<DeleteOutlined
|
||||
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();
|
||||
GroupBackend.deleteGroup({owner: treeData.owner, name: treeData.key})
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||
this.getTreeData();
|
||||
} 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}`);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Space>,
|
||||
children: haveChildren ? treeData.children.map(i => this.setTreeTitle(i)) : [],
|
||||
};
|
||||
}
|
||||
|
||||
setTreeExpandedKeys = () => {
|
||||
const expandedKeys = [];
|
||||
const setExpandedKeys = (nodes) => {
|
||||
for (const node of nodes) {
|
||||
expandedKeys.push(node.key);
|
||||
if (node.children) {
|
||||
setExpandedKeys(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
setExpandedKeys(this.state.treeData);
|
||||
this.setState({
|
||||
expandedKeys: expandedKeys,
|
||||
});
|
||||
};
|
||||
|
||||
renderTree() {
|
||||
const onSelect = (selectedKeys, info) => {
|
||||
this.setState({
|
||||
selectedKeys: selectedKeys,
|
||||
groupName: info.node.key,
|
||||
groupId: info.node.id,
|
||||
});
|
||||
this.props.history.push(`/trees/${this.state.organizationName}/${info.node.key}`);
|
||||
};
|
||||
const onExpand = (expandedKeysValue) => {
|
||||
this.setState({
|
||||
expandedKeys: expandedKeysValue,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.state.treeData.length === 0) {
|
||||
return <Empty />;
|
||||
}
|
||||
|
||||
const treeData = this.state.treeData.map(i => this.setTreeTitle(i));
|
||||
return (
|
||||
<Tree
|
||||
blockNode={true}
|
||||
defaultSelectedKeys={[this.state.groupName]}
|
||||
defaultExpandAll={true}
|
||||
selectedKeys={this.state.selectedKeys}
|
||||
expandedKeys={this.state.expandedKeys}
|
||||
onSelect={onSelect}
|
||||
onExpand={onExpand}
|
||||
showIcon={true}
|
||||
treeData={treeData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderOrganizationSelect() {
|
||||
if (Setting.isAdminUser(this.props.account)) {
|
||||
return (
|
||||
<OrganizationSelect
|
||||
initValue={this.state.organizationName}
|
||||
style={{width: "100%"}}
|
||||
onChange={(value) => {
|
||||
this.setState({
|
||||
organizationName: value,
|
||||
});
|
||||
this.props.history.push(`/trees/${value}`);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
newGroup(isRoot) {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: this.state.organizationName,
|
||||
name: `group_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
updatedTime: moment().format(),
|
||||
displayName: `New Group - ${randomName}`,
|
||||
type: "Virtual",
|
||||
parentId: isRoot ? this.state.organizationName : this.state.groupId,
|
||||
isTopGroup: isRoot,
|
||||
isEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
addGroup(isRoot = false) {
|
||||
const newGroup = this.newGroup(isRoot);
|
||||
GroupBackend.addGroup(newGroup)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
sessionStorage.setItem("groupTreeUrl", window.location.pathname);
|
||||
this.props.history.push({pathname: `/groups/${newGroup.owner}/${newGroup.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}`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
padding: "5px 5px 2px 5px",
|
||||
}}>
|
||||
<Row>
|
||||
<Col span={5}>
|
||||
<Row>
|
||||
<Col span={24} style={{textAlign: "center"}}>
|
||||
{this.renderOrganizationSelect()}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={24} style={{marginTop: "10px"}}>
|
||||
<Button size={"small"}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
selectedKeys: [],
|
||||
groupName: null,
|
||||
groupId: undefined,
|
||||
});
|
||||
this.props.history.push(`/trees/${this.state.organizationName}`);
|
||||
}}
|
||||
>
|
||||
{i18next.t("group:Show all")}
|
||||
</Button>
|
||||
<Button size={"small"} type={"primary"} style={{marginLeft: "10px"}} onClick={() => this.addGroup(true)}>
|
||||
{i18next.t("general:Add")}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: 10}}>
|
||||
<Col span={24} style={{textAlign: "left"}}>
|
||||
{this.renderTree()}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={19}>
|
||||
<UserListPage
|
||||
organizationName={this.state.organizationName}
|
||||
groupName={this.state.groupName}
|
||||
groupId={this.state.groupId}
|
||||
{...this.props}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupTreePage;
|
@@ -46,6 +46,11 @@ class MessageEditPage extends React.Component {
|
||||
getMessage() {
|
||||
MessageBackend.getMessage("admin", this.state.messageName)
|
||||
.then((message) => {
|
||||
if (message === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
message: message,
|
||||
});
|
||||
|
@@ -48,6 +48,11 @@ class ModelEditPage extends React.Component {
|
||||
getModel() {
|
||||
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
|
||||
.then((model) => {
|
||||
if (model === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
model: model,
|
||||
});
|
||||
|
@@ -49,10 +49,20 @@ class OrganizationEditPage extends React.Component {
|
||||
|
||||
getOrganization() {
|
||||
OrganizationBackend.getOrganization("admin", this.state.organizationName)
|
||||
.then((organization) => {
|
||||
this.setState({
|
||||
organization: organization,
|
||||
});
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const organization = res.data;
|
||||
if (organization === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
organization: organization,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -60,8 +60,10 @@ class OrganizationListPage extends BaseListPage {
|
||||
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
|
||||
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||
{name: "API key", label: i18next.t("general:API key")},
|
||||
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||
{name: "Groups", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||
{name: "3rd-party logins", visible: true, viewRule: "Self", modifyRule: "Self"},
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{name: "Properties", visible: false, viewRule: "Admin", modifyRule: "Admin"},
|
||||
@@ -221,11 +223,12 @@ class OrganizationListPage extends BaseListPage {
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
key: "op",
|
||||
width: "240px",
|
||||
width: "320px",
|
||||
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(`/trees/${record.name}`)}>{i18next.t("general:Groups")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
@@ -253,7 +256,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Organizations")}
|
||||
<Button type="primary" size="small" onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
<Button type="primary" size="small" disabled={!Setting.isAdminUser(this.props.account)} onClick={this.addOrganization.bind(this)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
|
@@ -40,8 +40,13 @@ class PaymentEditPage extends React.Component {
|
||||
}
|
||||
|
||||
getPayment() {
|
||||
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
|
||||
PaymentBackend.getPayment("admin", this.state.paymentName)
|
||||
.then((payment) => {
|
||||
if (payment === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
payment: payment,
|
||||
});
|
||||
|
@@ -27,7 +27,7 @@ class PaymentListPage extends BaseListPage {
|
||||
newPayment() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: this.props.account.owner,
|
||||
owner: "admin",
|
||||
name: `payment_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Payment - ${randomName}`,
|
||||
@@ -265,7 +265,7 @@ class PaymentListPage extends BaseListPage {
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
PaymentBackend.getPayments(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
PaymentBackend.getPayments("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
|
@@ -25,6 +25,7 @@ class PaymentResultPage extends React.Component {
|
||||
classes: props,
|
||||
paymentName: props.match.params.paymentName,
|
||||
payment: null,
|
||||
timeout: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,15 +33,21 @@ class PaymentResultPage extends React.Component {
|
||||
this.getPayment();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.timeout !== null) {
|
||||
clearTimeout(this.state.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
getPayment() {
|
||||
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
|
||||
PaymentBackend.getPayment("admin", this.state.paymentName)
|
||||
.then((payment) => {
|
||||
this.setState({
|
||||
payment: payment,
|
||||
});
|
||||
|
||||
if (payment.state === "Created") {
|
||||
setTimeout(() => this.getPayment(), 1000);
|
||||
this.setState({timeout: setTimeout(() => this.getPayment(), 1000)});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -50,6 +50,11 @@ class PermissionEditPage extends React.Component {
|
||||
getPermission() {
|
||||
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
|
||||
.then((permission) => {
|
||||
if (permission === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
permission: permission,
|
||||
});
|
||||
@@ -264,10 +269,12 @@ class PermissionEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.resourceType} onChange={(value => {
|
||||
this.updatePermissionField("resourceType", value);
|
||||
this.updatePermissionField("resources", []);
|
||||
})}
|
||||
options={[
|
||||
{value: "Application", name: i18next.t("general:Application")},
|
||||
{value: "TreeNode", name: i18next.t("permission:TreeNode")},
|
||||
{value: "Custom", name: i18next.t("general:Custom")},
|
||||
].map((item) => Setting.getOption(item.name, item.value))}
|
||||
/>
|
||||
</Col>
|
||||
@@ -277,7 +284,7 @@ class PermissionEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Resources"), i18next.t("permission:Resources - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.resources}
|
||||
<Select virtual={false} mode={(this.state.permission.resourceType === "Custom") ? "tags" : "multiple"} style={{width: "100%"}} value={this.state.permission.resources}
|
||||
onChange={(value => {this.updatePermissionField("resources", value);})}
|
||||
options={this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`))
|
||||
} />
|
||||
|
@@ -47,6 +47,11 @@ class PlanEditPage extends React.Component {
|
||||
getPlan() {
|
||||
PlanBackend.getPlan(this.state.organizationName, this.state.planName)
|
||||
.then((plan) => {
|
||||
if (plan === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
plan: plan,
|
||||
});
|
||||
|
@@ -50,6 +50,11 @@ class PricingEditPage extends React.Component {
|
||||
getPricing() {
|
||||
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
|
||||
.then((pricing) => {
|
||||
if (pricing === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
pricing: pricing,
|
||||
});
|
||||
|
@@ -45,6 +45,11 @@ class ProductEditPage extends React.Component {
|
||||
getProduct() {
|
||||
ProductBackend.getProduct(this.props.account.owner, this.state.productName)
|
||||
.then((product) => {
|
||||
if (product === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
product: product,
|
||||
});
|
||||
|
@@ -100,7 +100,7 @@ class ProductListPage extends BaseListPage {
|
||||
key: "owner",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
...this.getColumnSearchProps("owner"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
|
@@ -50,6 +50,11 @@ class ProviderEditPage extends React.Component {
|
||||
getProvider() {
|
||||
ProviderBackend.getProvider(this.state.owner, this.state.providerName)
|
||||
.then((res) => {
|
||||
if (res === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
provider: res.data,
|
||||
|
@@ -111,7 +111,7 @@ class ProviderListPage extends BaseListPage {
|
||||
key: "owner",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
|
||||
},
|
||||
|
@@ -43,6 +43,11 @@ class RoleEditPage extends React.Component {
|
||||
getRole() {
|
||||
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
|
||||
.then((role) => {
|
||||
if (role === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
role: role,
|
||||
});
|
||||
|
@@ -55,10 +55,10 @@ class SessionListPage extends BaseListPage {
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "organization",
|
||||
key: "owner",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
|
@@ -47,6 +47,11 @@ class SubscriptionEditPage extends React.Component {
|
||||
getSubscription() {
|
||||
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
|
||||
.then((subscription) => {
|
||||
if (subscription === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
subscription: subscription,
|
||||
});
|
||||
|
@@ -48,6 +48,11 @@ class SyncerEditPage extends React.Component {
|
||||
getSyncer() {
|
||||
SyncerBackend.getSyncer("admin", this.state.syncerName)
|
||||
.then((syncer) => {
|
||||
if (syncer === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
syncer: syncer,
|
||||
});
|
||||
|
@@ -36,6 +36,11 @@ class TokenEditPage extends React.Component {
|
||||
getToken() {
|
||||
TokenBackend.getToken("admin", this.state.tokenName)
|
||||
.then((token) => {
|
||||
if (token === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
token: token,
|
||||
});
|
||||
|
@@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, List, Result, Row, Select, Spin, Switch, Tag} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, List, Result, Row, Select, Space, Spin, Switch, Tag} from "antd";
|
||||
import * as GroupBackend from "./backend/GroupBackend";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@@ -32,7 +33,7 @@ import PropertyTable from "./table/propertyTable";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
import {DeleteMfa} from "./backend/MfaBackend";
|
||||
import {CheckCircleOutlined} from "@ant-design/icons";
|
||||
import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||
import {SmsMfaType} from "./auth/MfaSetupPage";
|
||||
import * as MfaBackend from "./backend/MfaBackend";
|
||||
|
||||
@@ -47,6 +48,7 @@ class UserEditPage extends React.Component {
|
||||
userName: props.userName !== undefined ? props.userName : props.match.params.userName,
|
||||
user: null,
|
||||
application: null,
|
||||
groups: null,
|
||||
organizations: [],
|
||||
applications: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
@@ -63,9 +65,20 @@ class UserEditPage extends React.Component {
|
||||
this.setReturnUrl();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (prevState.application !== this.state.application) {
|
||||
this.getGroups(this.state.organizationName);
|
||||
}
|
||||
}
|
||||
|
||||
getUser() {
|
||||
UserBackend.getUser(this.state.organizationName, this.state.userName)
|
||||
.then((data) => {
|
||||
if (data === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === null || data.status !== "error") {
|
||||
this.setState({
|
||||
user: data,
|
||||
@@ -78,6 +91,17 @@ class UserEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
addUserKeys() {
|
||||
UserBackend.addUserKeys(this.state.user)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.getUser();
|
||||
} else {
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
@@ -102,9 +126,26 @@ class UserEditPage extends React.Component {
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isGroupsVisible: application.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getGroups(organizationName) {
|
||||
if (this.state.isGroupsVisible) {
|
||||
GroupBackend.getGroups(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setReturnUrl() {
|
||||
const searchParams = new URLSearchParams(this.props.location.search);
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
@@ -207,14 +248,6 @@ class UserEditPage extends React.Component {
|
||||
|
||||
const isAdmin = Setting.isAdminUser(this.props.account);
|
||||
|
||||
// return (
|
||||
// <div>
|
||||
// {
|
||||
// JSON.stringify({accountItem: accountItem, isSelf: isSelf, isAdmin: isAdmin})
|
||||
// }
|
||||
// </div>
|
||||
// )
|
||||
|
||||
if (accountItem.viewRule === "Self") {
|
||||
if (!this.isSelfOrAdmin()) {
|
||||
return null;
|
||||
@@ -244,6 +277,11 @@ class UserEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let isKeysGenerated = false;
|
||||
if (this.state.user.accessKey !== "" && this.state.user.accessKey !== "") {
|
||||
isKeysGenerated = true;
|
||||
}
|
||||
|
||||
if (accountItem.name === "Organization") {
|
||||
return (
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
@@ -254,6 +292,7 @@ class UserEditPage extends React.Component {
|
||||
<Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.owner} onChange={(value => {
|
||||
this.getApplicationsByOrganization(value);
|
||||
this.updateUserField("owner", value);
|
||||
this.getGroups(value);
|
||||
})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
@@ -262,6 +301,35 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Groups") {
|
||||
return (
|
||||
<Row style={{marginTop: "10px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Groups"), i18next.t("general:Groups - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={disabled} value={this.state.user.groups ?? []} onChange={(value => {
|
||||
if (this.state.groups?.filter(group => value.includes(group.id))
|
||||
.filter(group => group.type === "Physical").length > 1) {
|
||||
Setting.showMessage("error", i18next.t("general:You can only select one physical group"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateUserField("groups", value);
|
||||
})}
|
||||
>
|
||||
{
|
||||
this.state.groups?.map((group) => <Option key={group.id} value={group.id}>
|
||||
<Space>
|
||||
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||
{group.displayName}
|
||||
</Space>
|
||||
</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "ID") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@@ -639,6 +707,37 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "API key") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:API key"), i18next.t("general:API key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{Setting.getLabel(i18next.t("general:Access key"), i18next.t("general:Access key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.accessKey} disabled={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
|
||||
{Setting.getLabel(i18next.t("general:Secret key"), i18next.t("general:Secret key - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.secretKey} disabled={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col span={22} >
|
||||
<Button onClick={() => this.addUserKeys()}>{i18next.t(isKeysGenerated ? "general:update" : "general:generate")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Roles") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px", alignItems: "center"}} >
|
||||
@@ -920,7 +1019,12 @@ class UserEditPage extends React.Component {
|
||||
UserBackend.deleteUser(this.state.user)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.props.history.push("/users");
|
||||
const userListUrl = sessionStorage.getItem("userListUrl");
|
||||
if (userListUrl !== null) {
|
||||
this.props.history.push(userListUrl);
|
||||
} else {
|
||||
this.props.history.push("/users");
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Switch, Table, Upload} from "antd";
|
||||
import {Button, Space, Switch, Table, Upload} from "antd";
|
||||
import {UploadOutlined} from "@ant-design/icons";
|
||||
import moment from "moment";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
@@ -27,18 +27,41 @@ import PopconfirmModal from "./common/modal/PopconfirmModal";
|
||||
class UserListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
...this.state,
|
||||
organizationName: this.props.organizationName ?? this.props.match?.params.organizationName ?? this.props.account.owner,
|
||||
organization: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
organizationName: this.props.match.params.organizationName,
|
||||
organization: null,
|
||||
});
|
||||
UNSAFE_componentWillMount() {
|
||||
super.UNSAFE_componentWillMount();
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.props.match.path !== prevProps.match.path || this.props.organizationName !== prevProps.organizationName) {
|
||||
this.setState({
|
||||
organizationName: this.props.organizationName ?? this.props.match?.params.organizationName,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.organizationName !== prevState.organizationName) {
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
|
||||
if (prevProps.groupName !== this.props.groupName || this.state.organizationName !== prevState.organizationName) {
|
||||
this.fetch({
|
||||
pagination: this.state.pagination,
|
||||
searchText: this.state.searchText,
|
||||
searchedColumn: this.state.searchedColumn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
newUser() {
|
||||
const randomName = Setting.getRandomName();
|
||||
const owner = (this.state.organizationName !== undefined) ? this.state.organizationName : this.props.account.owner;
|
||||
const owner = this.state.organizationName;
|
||||
return {
|
||||
owner: owner,
|
||||
name: `user_${randomName}`,
|
||||
@@ -52,6 +75,7 @@ class UserListPage extends BaseListPage {
|
||||
phone: Setting.getRandomNumber(),
|
||||
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
|
||||
address: [],
|
||||
groups: this.props.groupId ? [this.props.groupId] : [],
|
||||
affiliation: "Example Inc.",
|
||||
tag: "staff",
|
||||
region: "",
|
||||
@@ -100,6 +124,26 @@ class UserListPage extends BaseListPage {
|
||||
});
|
||||
}
|
||||
|
||||
removeUserFromGroup(i) {
|
||||
const user = this.state.data[i];
|
||||
const group = this.props.groupId;
|
||||
UserBackend.removeUserFromGroup({groupId: group, owner: user.owner, name: user.name})
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully removed"));
|
||||
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 remove")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(info) {
|
||||
const {status, response: res} = info.file;
|
||||
if (status === "done") {
|
||||
@@ -116,6 +160,19 @@ class UserListPage extends BaseListPage {
|
||||
}
|
||||
}
|
||||
|
||||
getOrganization(organizationName) {
|
||||
OrganizationBackend.getOrganization("admin", organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
organization: res.data,
|
||||
});
|
||||
} else {
|
||||
Setting.showMessage("error", `Failed to get organization: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderUpload() {
|
||||
const props = {
|
||||
name: "file",
|
||||
@@ -339,20 +396,30 @@ class UserListPage extends BaseListPage {
|
||||
width: "190px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
const isTreePage = this.props.groupId !== undefined;
|
||||
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name) || (record.owner === "built-in" && record.name === "admin");
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => {
|
||||
<Space>
|
||||
<Button size={isTreePage ? "small" : "middle"} type="primary" onClick={() => {
|
||||
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||
this.props.history.push(`/users/${record.owner}/${record.name}`);
|
||||
}}>{i18next.t("general:Edit")}</Button>
|
||||
}}>{i18next.t("general:Edit")}
|
||||
</Button>
|
||||
{isTreePage ?
|
||||
<PopconfirmModal
|
||||
text={i18next.t("general:remove")}
|
||||
title={i18next.t("general:Sure to remove") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.removeUserFromGroup(index)}
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
/> : null}
|
||||
<PopconfirmModal
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteUser(index)}
|
||||
disabled={disabled}
|
||||
>
|
||||
</PopconfirmModal>
|
||||
</div>
|
||||
size={isTreePage ? "small" : "default"}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -388,7 +455,7 @@ class UserListPage extends BaseListPage {
|
||||
const field = params.searchedColumn, value = params.searchText;
|
||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||
this.setState({loading: true});
|
||||
if (this.props.match.params.organizationName === undefined) {
|
||||
if (this.props.match?.path === "/users") {
|
||||
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
@@ -404,15 +471,7 @@ class UserListPage extends BaseListPage {
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
} else {
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
isAuthorized: false,
|
||||
@@ -423,7 +482,9 @@ class UserListPage extends BaseListPage {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
UserBackend.getUsers(this.props.match.params.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
(this.props.groupName ?
|
||||
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder, `${this.state.organizationName}/${this.props.groupName}`) :
|
||||
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
@@ -438,13 +499,6 @@ class UserListPage extends BaseListPage {
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
} else {
|
||||
this.getOrganization(this.state.organizationName);
|
||||
}
|
||||
} else {
|
||||
if (Setting.isResponseDenied(res)) {
|
||||
this.setState({
|
||||
@@ -457,15 +511,6 @@ class UserListPage extends BaseListPage {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getOrganization(organizationName) {
|
||||
OrganizationBackend.getOrganization("admin", organizationName)
|
||||
.then((organization) => {
|
||||
this.setState({
|
||||
organization: organization,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UserListPage;
|
||||
|
@@ -123,6 +123,11 @@ class WebhookEditPage extends React.Component {
|
||||
getWebhook() {
|
||||
WebhookBackend.getWebhook("admin", this.state.webhookName)
|
||||
.then((webhook) => {
|
||||
if (webhook === null) {
|
||||
this.props.history.push("/404");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
webhook: webhook,
|
||||
});
|
||||
|
71
web/src/backend/GroupBackend.js
Normal file
71
web/src/backend/GroupBackend.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getGroups(owner = "", withTree = false, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-groups?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}&withTree=${withTree}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getGroup(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-group?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateGroup(owner, name, group) {
|
||||
const newGroup = Setting.deepCopy(group);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-group?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newGroup),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addGroup(group) {
|
||||
const newGroup = Setting.deepCopy(group);
|
||||
return fetch(`${Setting.ServerUrl}/api/add-group`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newGroup),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteGroup(group) {
|
||||
const newGroup = Setting.deepCopy(group);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-group`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newGroup),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
@@ -14,8 +14,8 @@
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getPayments(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-payments?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
export function getPayments(owner, organization, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-payments?owner=${owner}&organization=${organization}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
|
@@ -25,8 +25,8 @@ export function getGlobalUsers(page, pageSize, field = "", value = "", sortField
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getUsers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
export function getUsers(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "", groupId = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}&groupId=${groupId}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
@@ -45,6 +45,17 @@ export function getUser(owner, name) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function addUserKeys(user) {
|
||||
return fetch(`${Setting.ServerUrl}/api/add-user-keys`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(user),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function updateUser(owner, name, user) {
|
||||
const newUser = Setting.deepCopy(user);
|
||||
return fetch(`${Setting.ServerUrl}/api/update-user?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
@@ -211,3 +222,18 @@ export function checkUserPassword(values) {
|
||||
body: JSON.stringify(values),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function removeUserFromGroup({owner, name, groupId}) {
|
||||
const formData = new FormData();
|
||||
formData.append("owner", owner);
|
||||
formData.append("name", name);
|
||||
formData.append("groupId", groupId);
|
||||
return fetch(`${Setting.ServerUrl}/api/remove-user-from-group`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ import i18next from "i18next";
|
||||
import React from "react";
|
||||
|
||||
export const PopconfirmModal = (props) => {
|
||||
const text = props.text ? props.text : i18next.t("general:Delete");
|
||||
const size = props.size ? props.size : "middle";
|
||||
return (
|
||||
<Popconfirm
|
||||
title={props.title}
|
||||
@@ -25,7 +27,7 @@ export const PopconfirmModal = (props) => {
|
||||
okText={i18next.t("general:OK")}
|
||||
cancelText={i18next.t("general:Cancel")}
|
||||
>
|
||||
<Button style={{marginBottom: "10px"}} disabled={props.disabled} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
||||
<Button style={{...props.style}} size={size} disabled={props.disabled} type="primary" danger>{text}</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
};
|
||||
|
@@ -19,16 +19,14 @@ import * as OrganizationBackend from "../../backend/OrganizationBackend";
|
||||
import * as Setting from "../../Setting";
|
||||
|
||||
function OrganizationSelect(props) {
|
||||
const {onChange} = props;
|
||||
const {onChange, initValue, style, onSelect} = props;
|
||||
const [organizations, setOrganizations] = React.useState([]);
|
||||
const [value, setValue] = React.useState("");
|
||||
const [value, setValue] = React.useState(initValue);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (props.organizations === undefined) {
|
||||
getOrganizations();
|
||||
}
|
||||
const initValue = organizations.length > 0 ? organizations[0] : "";
|
||||
handleOnChange(initValue);
|
||||
}, []);
|
||||
|
||||
const getOrganizations = () => {
|
||||
@@ -36,6 +34,9 @@ function OrganizationSelect(props) {
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
setOrganizations(res.data);
|
||||
if (initValue === undefined) {
|
||||
setValue(organizations.length > 0 ? organizations[0] : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -47,14 +48,14 @@ function OrganizationSelect(props) {
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={organizations.map((organization) => Setting.getOption(organization.name, organization.name))}
|
||||
options={organizations.map((organization) => Setting.getOption(organization.displayName, organization.name))}
|
||||
virtual={false}
|
||||
showSearch
|
||||
placeholder={i18next.t("login:Please select an organization")}
|
||||
value={value}
|
||||
onChange={handleOnChange}
|
||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
{...props}
|
||||
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||
style={style}
|
||||
onSelect={onSelect}
|
||||
>
|
||||
</Select>
|
||||
);
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import * as Setting from "../../Setting";
|
||||
import {Dropdown} from "antd";
|
||||
import {Dropdown, Space} from "antd";
|
||||
import "../../App.less";
|
||||
import i18next from "i18next";
|
||||
import {CheckOutlined} from "@ant-design/icons";
|
||||
@@ -43,10 +43,10 @@ class ThemeSelect extends React.Component {
|
||||
|
||||
getThemeItems() {
|
||||
return Themes.map((theme) => Setting.getItem(
|
||||
<div style={{display: "flex", justifyContent: "space-between"}}>
|
||||
<div>{i18next.t(`theme:${theme.label}`)}</div>
|
||||
<Space>
|
||||
{i18next.t(`theme:${theme.label}`)}
|
||||
{this.props.themeAlgorithm.includes(theme.key) ? <CheckOutlined style={{marginLeft: "5px"}} /> : null}
|
||||
</div>,
|
||||
</Space>,
|
||||
theme.key, theme.icon));
|
||||
}
|
||||
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Schließen",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Erstellte Zeit",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Standard Anwendung",
|
||||
"Default application - Tooltip": "Standard-Anwendung für Benutzer, die direkt von der Organisationsseite registriert wurden",
|
||||
"Default avatar": "Standard-Avatar",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "Benutzerdefinierte URL für die \"Passwort vergessen\" Seite. Wenn nicht festgelegt, wird die standardmäßige Casdoor \"Passwort vergessen\" Seite verwendet. Wenn sie festgelegt ist, wird der \"Passwort vergessen\" Link auf der Login-Seite zu dieser URL umgeleitet",
|
||||
"Found some texts still not translated? Please help us translate at": "Haben Sie noch Texte gefunden, die nicht übersetzt wurden? Bitte helfen Sie uns beim Übersetzen",
|
||||
"Go to writable demo site?": "Gehe zur beschreibbaren Demo-Website?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Zuhause",
|
||||
"Home - Tooltip": "Homepage der Anwendung",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Abonnements",
|
||||
"Successfully added": "Erfolgreich hinzugefügt",
|
||||
"Successfully deleted": "Erfolgreich gelöscht",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Erfolgreich gespeichert",
|
||||
"Supported country codes": "Unterstützte Ländercodes",
|
||||
"Supported country codes - Tooltip": "Ländercodes, die von der Organisation unterstützt werden. Diese Codes können als Präfix ausgewählt werden, wenn SMS-Verifizierungscodes gesendet werden",
|
||||
"Sure to delete": "Sicher zu löschen",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Synchronisieren",
|
||||
"Syncers": "Syncers",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Dies ist eine schreibgeschützte Demo-Seite!",
|
||||
"Timestamp": "Zeitstempel",
|
||||
"Tokens": "Token",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL-Link",
|
||||
"Up": "Oben",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Benutzer",
|
||||
"Users under all organizations": "Benutzer unter allen Organisationen",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "leere",
|
||||
"remove": "remove",
|
||||
"{total} in total": "Insgesamt {total}"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN oder ID des LDAP-Serveradministrators",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Kostenlos",
|
||||
"Getting started": "Loslegen",
|
||||
"Has trial": "Testphase verfügbar",
|
||||
"Has trial - Tooltip": "Verfügbarkeit der Testphase nach Auswahl eines Plans",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Testphase Dauer",
|
||||
"Trial duration - Tooltip": "Dauer der Testphase",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Close",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Created time",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Default application",
|
||||
"Default application - Tooltip": "Default application for users registered directly from the organization page",
|
||||
"Default avatar": "Default avatar",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "Custom URL for the \"Forget password\" page. If not set, the default Casdoor \"Forget password\" page will be used. When set, the \"Forget password\" link on the login page will redirect to this URL",
|
||||
"Found some texts still not translated? Please help us translate at": "Found some texts still not translated? Please help us translate at",
|
||||
"Go to writable demo site?": "Go to writable demo site?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Home",
|
||||
"Home - Tooltip": "Home page of the application",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Subscriptions",
|
||||
"Successfully added": "Successfully added",
|
||||
"Successfully deleted": "Successfully deleted",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Successfully saved",
|
||||
"Supported country codes": "Supported country codes",
|
||||
"Supported country codes - Tooltip": "Country codes supported by the organization. These codes can be selected as a prefix when sending SMS verification codes",
|
||||
"Sure to delete": "Sure to delete",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "This is a read-only demo site!",
|
||||
"Timestamp": "Timestamp",
|
||||
"Tokens": "Tokens",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL link",
|
||||
"Up": "Up",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Users",
|
||||
"Users under all organizations": "Users under all organizations",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "empty",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} in total"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Free",
|
||||
"Getting started": "Getting started",
|
||||
"Has trial": "Has trial",
|
||||
"Has trial - Tooltip": "Availability of the trial period after choosing a plan",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Trial duration",
|
||||
"Trial duration - Tooltip": "Trial duration period",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Cerca",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Tiempo creado",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Aplicación predeterminada",
|
||||
"Default application - Tooltip": "Aplicación predeterminada para usuarios registrados directamente desde la página de la organización",
|
||||
"Default avatar": "Avatar predeterminado",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "URL personalizada para la página \"Olvidé mi contraseña\". Si no se establece, se utilizará la página \"Olvidé mi contraseña\" predeterminada de Casdoor. Cuando se establezca, el enlace \"Olvidé mi contraseña\" en la página de inicio de sesión redireccionará a esta URL",
|
||||
"Found some texts still not translated? Please help us translate at": "¿Encontraste algunos textos que aún no están traducidos? Por favor, ayúdanos a traducirlos en",
|
||||
"Go to writable demo site?": "¿Ir al sitio demo editable?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Hogar",
|
||||
"Home - Tooltip": "Página de inicio de la aplicación",
|
||||
"ID": "identificación",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Suscripciones",
|
||||
"Successfully added": "Éxito al agregar",
|
||||
"Successfully deleted": "Éxito en la eliminación",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Guardado exitosamente",
|
||||
"Supported country codes": "Códigos de país admitidos",
|
||||
"Supported country codes - Tooltip": "Códigos de país compatibles con la organización. Estos códigos se pueden seleccionar como prefijo al enviar códigos de verificación SMS",
|
||||
"Sure to delete": "Seguro que eliminar",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sincronización",
|
||||
"Syncers": "Sincronizadores",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "¡Este es un sitio de demostración solo de lectura!",
|
||||
"Timestamp": "Marca de tiempo",
|
||||
"Tokens": "Tokens",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "Dirección URL",
|
||||
"URL - Tooltip": "Enlace de URL",
|
||||
"Up": "Arriba",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Usuarios",
|
||||
"Users under all organizations": "Usuarios bajo todas las organizaciones",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "vacío",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} en total"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Administrador",
|
||||
"Admin - Tooltip": "CN o ID del administrador del servidor LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratis",
|
||||
"Getting started": "Empezar",
|
||||
"Has trial": "Tiene período de prueba",
|
||||
"Has trial - Tooltip": "Disponibilidad del período de prueba después de elegir un plan",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Duración del período de prueba",
|
||||
"Trial duration - Tooltip": "Duración del período de prueba",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Fermer",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Temps créé",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Application par défaut",
|
||||
"Default application - Tooltip": "Application par défaut pour les utilisateurs enregistrés directement depuis la page de l'organisation",
|
||||
"Default avatar": "Avatar par défaut",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "URL personnalisée pour la page \"Mot de passe oublié\". Si elle n'est pas définie, la page par défaut \"Mot de passe oublié\" de Casdoor sera utilisée. Lorsqu'elle est définie, le lien \"Mot de passe oublié\" sur la page de connexion sera redirigé vers cette URL",
|
||||
"Found some texts still not translated? Please help us translate at": "Trouvé des textes encore non traduits ? Veuillez nous aider à les traduire",
|
||||
"Go to writable demo site?": "Allez sur le site de démonstration modifiable ?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Maison",
|
||||
"Home - Tooltip": "Page d'accueil de l'application",
|
||||
"ID": "Identité",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Abonnements",
|
||||
"Successfully added": "Ajouté avec succès",
|
||||
"Successfully deleted": "Supprimé avec succès",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Succès enregistré",
|
||||
"Supported country codes": "Codes de pays pris en charge",
|
||||
"Supported country codes - Tooltip": "Codes de pays pris en charge par l'organisation. Ces codes peuvent être sélectionnés comme préfixe lors de l'envoi de codes de vérification SMS",
|
||||
"Sure to delete": "Sûr de supprimer",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Synchronisation",
|
||||
"Syncers": "Synchroniseurs",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Ceci est un site de démonstration en lecture seule !",
|
||||
"Timestamp": "Horodatage",
|
||||
"Tokens": "Les jetons",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Lien d'URL",
|
||||
"Up": "Haut",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Utilisateurs",
|
||||
"Users under all organizations": "Utilisateurs sous toutes les organisations",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "vide",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} au total"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN ou ID de l'administrateur du serveur LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratuit",
|
||||
"Getting started": "Commencer",
|
||||
"Has trial": "Essai gratuit disponible",
|
||||
"Has trial - Tooltip": "Disponibilité de la période d'essai après avoir choisi un forfait",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Durée de l'essai",
|
||||
"Trial duration - Tooltip": "Durée de la période d'essai",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Tutup",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Waktu dibuat",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Aplikasi default",
|
||||
"Default application - Tooltip": "Aplikasi default untuk pengguna yang terdaftar langsung dari halaman organisasi",
|
||||
"Default avatar": "Avatar default",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "URL kustom untuk halaman \"Lupa kata sandi\". Jika tidak diatur, halaman \"Lupa kata sandi\" default Casdoor akan digunakan. Ketika diatur, tautan \"Lupa kata sandi\" pada halaman masuk akan diarahkan ke URL ini",
|
||||
"Found some texts still not translated? Please help us translate at": "Menemukan beberapa teks yang masih belum diterjemahkan? Tolong bantu kami menerjemahkan di",
|
||||
"Go to writable demo site?": "Pergi ke situs demo yang dapat ditulis?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Rumah",
|
||||
"Home - Tooltip": "Halaman utama aplikasi",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Langganan",
|
||||
"Successfully added": "Berhasil ditambahkan",
|
||||
"Successfully deleted": "Berhasil dihapus",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Berhasil disimpan",
|
||||
"Supported country codes": "Kode negara yang didukung",
|
||||
"Supported country codes - Tooltip": "Kode negara yang didukung oleh organisasi. Kode-kode ini dapat dipilih sebagai awalan saat mengirim kode verifikasi SMS",
|
||||
"Sure to delete": "Pasti untuk menghapus",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sinkronisasi",
|
||||
"Syncers": "Sinkronisasi",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Ini adalah situs demo hanya untuk dibaca saja!",
|
||||
"Timestamp": "Waktu penanda waktu",
|
||||
"Tokens": "Token-token",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Tautan URL",
|
||||
"Up": "Ke atas",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Pengguna-pengguna",
|
||||
"Users under all organizations": "Pengguna di bawah semua organisasi",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "kosong",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} secara keseluruhan"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN atau ID dari administrator server LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Gratis",
|
||||
"Getting started": "Mulai",
|
||||
"Has trial": "Mempunyai periode percobaan",
|
||||
"Has trial - Tooltip": "Ketersediaan periode percobaan setelah memilih rencana",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Durasi percobaan",
|
||||
"Trial duration - Tooltip": "Durasi periode percobaan",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "閉じる",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "作成された時間",
|
||||
"Custom": "Custom",
|
||||
"Default application": "デフォルトアプリケーション",
|
||||
"Default application - Tooltip": "組織ページから直接登録されたユーザーのデフォルトアプリケーション",
|
||||
"Default avatar": "デフォルトのアバター",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "ファビコン",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "「パスワードをお忘れの場合」ページのカスタムURL。未設定の場合、デフォルトのCasdoor「パスワードをお忘れの場合」ページが使用されます。設定された場合、ログインページの「パスワードをお忘れの場合」リンクはこのURLにリダイレクトされます",
|
||||
"Found some texts still not translated? Please help us translate at": "まだ翻訳されていない文章が見つかりましたか?是非とも翻訳のお手伝いをお願いします",
|
||||
"Go to writable demo site?": "書き込み可能なデモサイトに移動しますか?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "ホーム",
|
||||
"Home - Tooltip": "アプリケーションのホームページ",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "サブスクリプション",
|
||||
"Successfully added": "正常に追加されました",
|
||||
"Successfully deleted": "正常に削除されました",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "成功的に保存されました",
|
||||
"Supported country codes": "サポートされている国コード",
|
||||
"Supported country codes - Tooltip": "組織でサポートされている国コード。これらのコードは、SMS認証コードのプレフィックスとして選択できます",
|
||||
"Sure to delete": "削除することが確実です",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "同期",
|
||||
"Syncers": "シンカーズ",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "これは読み取り専用のデモサイトです!",
|
||||
"Timestamp": "タイムスタンプ",
|
||||
"Tokens": "トークン",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URLリンク",
|
||||
"Up": "アップ",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "ユーザー",
|
||||
"Users under all organizations": "すべての組織のユーザー",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "空",
|
||||
"remove": "remove",
|
||||
"{total} in total": "総計{total}"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "LDAPサーバー管理者のCNまたはID",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "無料",
|
||||
"Getting started": "はじめる",
|
||||
"Has trial": "トライアル期間あり",
|
||||
"Has trial - Tooltip": "プラン選択後のトライアル期間の有無",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "トライアル期間の長さ",
|
||||
"Trial duration - Tooltip": "トライアル期間の長さ",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "닫다",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "작성한 시간",
|
||||
"Custom": "Custom",
|
||||
"Default application": "기본 애플리케이션",
|
||||
"Default application - Tooltip": "조직 페이지에서 직접 등록한 사용자의 기본 응용 프로그램",
|
||||
"Default avatar": "기본 아바타",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "파비콘",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "\"비밀번호를 잊어버렸을 경우\" 페이지에 대한 사용자 정의 URL. 설정되지 않은 경우 기본 Casdoor \"비밀번호를 잊어버렸을 경우\" 페이지가 사용됩니다. 설정된 경우 로그인 페이지의 \"비밀번호를 잊으셨나요?\" 링크는 이 URL로 리디렉션됩니다",
|
||||
"Found some texts still not translated? Please help us translate at": "아직 번역되지 않은 텍스트가 있나요? 번역에 도움을 주실 수 있나요?",
|
||||
"Go to writable demo site?": "쓰기 가능한 데모 사이트로 이동하시겠습니까?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "집",
|
||||
"Home - Tooltip": "어플리케이션 홈 페이지",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "구독",
|
||||
"Successfully added": "성공적으로 추가되었습니다",
|
||||
"Successfully deleted": "성공적으로 삭제되었습니다",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "성공적으로 저장되었습니다",
|
||||
"Supported country codes": "지원되는 국가 코드들",
|
||||
"Supported country codes - Tooltip": "조직에서 지원하는 국가 코드입니다. 이 코드들은 SMS 인증 코드를 보낼 때 접두사로 선택할 수 있습니다",
|
||||
"Sure to delete": "삭제하시겠습니까?",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "싱크",
|
||||
"Syncers": "싱크어스",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "이것은 읽기 전용 데모 사이트입니다!",
|
||||
"Timestamp": "타임스탬프",
|
||||
"Tokens": "토큰",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "URL 링크",
|
||||
"Up": "위로",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "사용자들",
|
||||
"Users under all organizations": "모든 조직의 사용자",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "빈",
|
||||
"remove": "remove",
|
||||
"{total} in total": "총 {total}개"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "LDAP 서버 관리자의 CN 또는 ID",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "무료",
|
||||
"Getting started": "시작하기",
|
||||
"Has trial": "무료 체험 가능",
|
||||
"Has trial - Tooltip": "플랜 선택 후 체험 기간의 가용 여부",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "체험 기간",
|
||||
"Trial duration - Tooltip": "체험 기간의 기간",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Fechar",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Hora de Criação",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Aplicação padrão",
|
||||
"Default application - Tooltip": "Aplicação padrão para usuários registrados diretamente na página da organização",
|
||||
"Default avatar": "Avatar padrão",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "URL personalizada para a página de \"Esqueci a senha\". Se não definido, será usada a página padrão de \"Esqueci a senha\" do Casdoor. Quando definido, o link de \"Esqueci a senha\" na página de login será redirecionado para esta URL",
|
||||
"Found some texts still not translated? Please help us translate at": "Encontrou algum texto ainda não traduzido? Ajude-nos a traduzir em",
|
||||
"Go to writable demo site?": "Acessar o site de demonstração gravável?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Página Inicial",
|
||||
"Home - Tooltip": "Página inicial do aplicativo",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Đăng ký",
|
||||
"Successfully added": "Adicionado com sucesso",
|
||||
"Successfully deleted": "Excluído com sucesso",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Salvo com sucesso",
|
||||
"Supported country codes": "Códigos de país suportados",
|
||||
"Supported country codes - Tooltip": "Códigos de país suportados pela organização. Esses códigos podem ser selecionados como prefixo ao enviar códigos de verificação SMS",
|
||||
"Sure to delete": "Tem certeza que deseja excluir",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sincronizar",
|
||||
"Syncers": "Sincronizadores",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Este é um site de demonstração apenas para leitura!",
|
||||
"Timestamp": "Carimbo de Data/Hora",
|
||||
"Tokens": "Tokens",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Link da URL",
|
||||
"Up": "Acima",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Usuários",
|
||||
"Users under all organizations": "Usuários em todas as organizações",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "vazio",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} no total"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Administrador",
|
||||
"Admin - Tooltip": "CN ou ID do administrador do servidor LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Miễn phí",
|
||||
"Getting started": "Bắt đầu",
|
||||
"Has trial": "Có thời gian thử nghiệm",
|
||||
"Has trial - Tooltip": "Khả dụng thời gian thử nghiệm sau khi chọn kế hoạch",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Thời gian thử nghiệm",
|
||||
"Trial duration - Tooltip": "Thời gian thử nghiệm",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Близко",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Созданное время",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Приложение по умолчанию",
|
||||
"Default application - Tooltip": "По умолчанию приложение для пользователей, зарегистрированных непосредственно со страницы организации",
|
||||
"Default avatar": "Стандартный аватар",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Фавикон",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "Настроенный URL для страницы \"Забыли пароль\". Если не установлено, будет использоваться стандартная страница \"Забыли пароль\" Casdoor. При установке, ссылка \"Забыли пароль\" на странице входа будет перенаправляться на этот URL",
|
||||
"Found some texts still not translated? Please help us translate at": "Нашли некоторые тексты, которые еще не переведены? Пожалуйста, помогите нам перевести на",
|
||||
"Go to writable demo site?": "Перейти на демонстрационный сайт для записи данных?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Дом",
|
||||
"Home - Tooltip": "Главная страница приложения",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Подписки",
|
||||
"Successfully added": "Успешно добавлено",
|
||||
"Successfully deleted": "Успешно удалено",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Успешно сохранено",
|
||||
"Supported country codes": "Поддерживаемые коды стран",
|
||||
"Supported country codes - Tooltip": "Коды стран, поддерживаемые организацией. Эти коды могут быть выбраны в качестве префикса при отправке SMS-кодов подтверждения",
|
||||
"Sure to delete": "Обязательное удаление",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Синхронизация",
|
||||
"Syncers": "Синкеры",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Это демонстрационный сайт только для чтения!",
|
||||
"Timestamp": "Отметка времени",
|
||||
"Tokens": "Токены",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Ссылка URL",
|
||||
"Up": "Вверх",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Пользователи",
|
||||
"Users under all organizations": "Пользователи всех организаций",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "пустые",
|
||||
"remove": "remove",
|
||||
"{total} in total": "{total} в общей сложности"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN или ID администратора сервера LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Бесплатно",
|
||||
"Getting started": "Выьрать план",
|
||||
"Has trial": "Есть пробный период",
|
||||
"Has trial - Tooltip": "Наличие пробного периода после выбора плана",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Продолжительность пробного периода",
|
||||
"Trial duration - Tooltip": "Продолжительность пробного периода",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "Đóng lại",
|
||||
"Confirm": "Confirm",
|
||||
"Created time": "Thời gian tạo",
|
||||
"Custom": "Custom",
|
||||
"Default application": "Ứng dụng mặc định",
|
||||
"Default application - Tooltip": "Ứng dụng mặc định cho người dùng đăng ký trực tiếp từ trang tổ chức",
|
||||
"Default avatar": "Hình đại diện mặc định",
|
||||
@@ -209,6 +210,7 @@
|
||||
"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",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "Đường dẫn tùy chỉnh cho trang \"Quên mật khẩu\". Nếu không được thiết lập, trang \"Quên mật khẩu\" mặc định của Casdoor sẽ được sử dụng. Khi cài đặt, liên kết \"Quên mật khẩu\" trên trang đăng nhập sẽ chuyển hướng đến URL này",
|
||||
"Found some texts still not translated? Please help us translate at": "Tìm thấy một số văn bản vẫn chưa được dịch? Vui lòng giúp chúng tôi dịch tại",
|
||||
"Go to writable demo site?": "Bạn có muốn đi đến trang demo có thể viết được không?",
|
||||
"Groups": "Groups",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "Nhà",
|
||||
"Home - Tooltip": "Trang chủ của ứng dụng",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "Đăng ký",
|
||||
"Successfully added": "Đã thêm thành công",
|
||||
"Successfully deleted": "Đã xóa thành công",
|
||||
"Successfully removed": "Successfully removed",
|
||||
"Successfully saved": "Thành công đã được lưu lại",
|
||||
"Supported country codes": "Các mã quốc gia được hỗ trợ",
|
||||
"Supported country codes - Tooltip": "Mã quốc gia được hỗ trợ bởi tổ chức. Những mã này có thể được chọn làm tiền tố khi gửi mã xác nhận SMS",
|
||||
"Sure to delete": "Chắc chắn muốn xóa",
|
||||
"Sure to remove": "Sure to remove",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Đồng bộ hoá",
|
||||
"Syncers": "Đồng bộ hóa",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "Đây là trang web giới thiệu chỉ có chức năng đọc!",
|
||||
"Timestamp": "Đánh dấu thời gian",
|
||||
"Tokens": "Mã thông báo",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"URL": "URL",
|
||||
"URL - Tooltip": "Đường dẫn URL",
|
||||
"Up": "Lên",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "Người dùng",
|
||||
"Users under all organizations": "Người dùng trong tất cả các tổ chức",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "You can only select one physical group",
|
||||
"empty": "trống",
|
||||
"remove": "remove",
|
||||
"{total} in total": "Trong tổng số {total}"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "Edit Group",
|
||||
"New Group": "New Group",
|
||||
"Parent group": "Parent group",
|
||||
"Parent group - Tooltip": "Parent group - Tooltip",
|
||||
"Physical": "Physical",
|
||||
"Show all": "Show all",
|
||||
"Virtual": "Virtual"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "Admin",
|
||||
"Admin - Tooltip": "CN hoặc ID của quản trị viên máy chủ LDAP",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "Edit Pricing",
|
||||
"Free": "Miễn phí",
|
||||
"Getting started": "Bắt đầu",
|
||||
"Has trial": "Có thời gian thử nghiệm",
|
||||
"Has trial - Tooltip": "Khả dụng thời gian thử nghiệm sau khi chọn kế hoạch",
|
||||
"New Pricing": "New Pricing",
|
||||
"Trial duration": "Thời gian thử nghiệm",
|
||||
"Trial duration - Tooltip": "Thời gian thử nghiệm",
|
||||
|
@@ -188,6 +188,7 @@
|
||||
"Close": "关闭",
|
||||
"Confirm": "确认",
|
||||
"Created time": "创建时间",
|
||||
"Custom": "自定义",
|
||||
"Default application": "默认应用",
|
||||
"Default application - Tooltip": "直接从组织页面注册的用户默认所属的应用",
|
||||
"Default avatar": "默认头像",
|
||||
@@ -209,6 +210,7 @@
|
||||
"Failed to delete": "删除失败",
|
||||
"Failed to enable": "启用失败",
|
||||
"Failed to get answer": "获取回答失败",
|
||||
"Failed to remove": "移除失败",
|
||||
"Failed to save": "保存失败",
|
||||
"Failed to verify": "验证失败",
|
||||
"Favicon": "Favicon",
|
||||
@@ -218,6 +220,8 @@
|
||||
"Forget URL - Tooltip": "自定义忘记密码页面的URL,不设置时采用Casdoor默认的忘记密码页面,设置后Casdoor各类页面的忘记密码链接会跳转到该URL",
|
||||
"Found some texts still not translated? Please help us translate at": "发现有些文字尚未翻译?请移步这里帮我们翻译:",
|
||||
"Go to writable demo site?": "跳转至可写演示站点?",
|
||||
"Groups": "用户组",
|
||||
"Groups - Tooltip": "Groups - Tooltip",
|
||||
"Home": "首页",
|
||||
"Home - Tooltip": "应用的首页",
|
||||
"ID": "ID",
|
||||
@@ -299,10 +303,12 @@
|
||||
"Subscriptions": "订阅",
|
||||
"Successfully added": "添加成功",
|
||||
"Successfully deleted": "删除成功",
|
||||
"Successfully removed": "移除成功",
|
||||
"Successfully saved": "保存成功",
|
||||
"Supported country codes": "支持的国家代码",
|
||||
"Supported country codes - Tooltip": "该组织所支持的国家代码,发送短信验证码时可以选择这些国家的代码前缀",
|
||||
"Sure to delete": "确定删除",
|
||||
"Sure to remove": "确定移除",
|
||||
"Swagger": "API文档",
|
||||
"Sync": "同步",
|
||||
"Syncers": "同步器",
|
||||
@@ -311,6 +317,8 @@
|
||||
"This is a read-only demo site!": "这是一个只读演示站点!",
|
||||
"Timestamp": "时间戳",
|
||||
"Tokens": "令牌",
|
||||
"Type": "类型",
|
||||
"Type - Tooltip": "类型",
|
||||
"URL": "链接",
|
||||
"URL - Tooltip": "URL链接",
|
||||
"Up": "上移",
|
||||
@@ -323,9 +331,20 @@
|
||||
"Users": "用户",
|
||||
"Users under all organizations": "所有组织里的用户",
|
||||
"Webhooks": "Webhooks",
|
||||
"You can only select one physical group": "只能选择一个实体组",
|
||||
"empty": "无",
|
||||
"remove": "移除",
|
||||
"{total} in total": "{total} 总计"
|
||||
},
|
||||
"group": {
|
||||
"Edit Group": "编辑用户组",
|
||||
"New Group": "新建用户组",
|
||||
"Parent group": "上级组",
|
||||
"Parent group - Tooltip": "上级组",
|
||||
"Physical": "物理组",
|
||||
"Show all": "显示全部",
|
||||
"Virtual": "虚拟组"
|
||||
},
|
||||
"ldap": {
|
||||
"Admin": "管理员",
|
||||
"Admin - Tooltip": "LDAP服务器管理员的CN或ID",
|
||||
@@ -539,8 +558,6 @@
|
||||
"Edit Pricing": "编辑定价",
|
||||
"Free": "免费",
|
||||
"Getting started": "开始使用",
|
||||
"Has trial": "有试用期",
|
||||
"Has trial - Tooltip": "选择计划后是否有试用期",
|
||||
"New Pricing": "添加定价",
|
||||
"Trial duration": "试用期时长",
|
||||
"Trial duration - Tooltip": "试用期时长",
|
||||
|
@@ -91,8 +91,10 @@ class AccountTable extends React.Component {
|
||||
{name: "Karma", label: i18next.t("user:Karma")},
|
||||
{name: "Ranking", label: i18next.t("user:Ranking")},
|
||||
{name: "Signup application", label: i18next.t("general:Signup application")},
|
||||
{name: "API key", label: i18next.t("general:API key")},
|
||||
{name: "Roles", label: i18next.t("general:Roles")},
|
||||
{name: "Permissions", label: i18next.t("general:Permissions")},
|
||||
{name: "Groups", label: i18next.t("general:Groups")},
|
||||
{name: "3rd-party logins", label: i18next.t("user:3rd-party logins")},
|
||||
{name: "Properties", label: i18next.t("user:Properties")},
|
||||
{name: "Is online", label: i18next.t("user:Is online")},
|
||||
|
Reference in New Issue
Block a user