Compare commits

...

15 Commits

Author SHA1 Message Date
61c2fd5412 feat: fix the issue of jumping back to the login page after resetting password (#1288)
* fix: redirect to login page

* fix: front end router

* fix: front end router

* fix: signup page router

* fix: redirect to login page
2022-11-13 12:16:49 +08:00
d542208eb8 feat: fix select language box overlay (#1289)
* fix: select language box overlay

* fix: select language box position

* fix: select language box position

* fix: select language box position
2022-11-13 10:52:22 +08:00
f818200c95 feat: fix empty organization in adapter edit page (#1274) 2022-11-08 21:03:15 +08:00
5bc2e91344 fix: fix typo (#1264) 2022-11-06 21:14:26 +08:00
295f732b18 Show tag in i18n 2022-11-06 20:19:31 +08:00
770ae47471 feat: fix memory leak problem (#1257) 2022-11-06 01:43:27 +08:00
2ce4f96355 fix: forget page mobile view (#1263) 2022-11-05 22:54:22 +08:00
07ed834b27 fix: system info mobile view (#1261) 2022-11-05 22:46:52 +08:00
8d686411ee feat: support add providers inside the Organization scope (#1250)
* feat: support add providers inside the Organization scope

Signed-off-by: magicwind <2814461814@qq.com>

* Update ProviderListPage.js

* fix: gloabal admin can see all providers

* fix: table fixed column warning

* fix: edit application page can get all providers

Signed-off-by: magicwind <2814461814@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-11-04 21:31:08 +08:00
ce722897f1 feat: support prefix path for storage files (#1258) 2022-11-04 21:08:39 +08:00
a8381e875b feat: change all occurrences when a object name is changed (#1252) 2022-11-02 00:17:38 +08:00
4c81fd7d16 feat: fix generating wrong x.509 private key file header (#1253)
According to the [official x509 documentation](https://pkg.go.dev/crypto/x509#MarshalPKCS1PrivateKey), the private key generated using `x509.MarshalPKCS1PrivateKey` starts with `-----BEGIN RSA PRIVATE KEY-----` instead of `-----BEGIN PRIVATE KEY-----`. Otherwise, it will not be parsed by most tools (like OpenSSL, [jwt.io](https://jwt.io/), etc.) because it does not conform to the specification.
2022-11-01 22:19:38 +08:00
25ee4226d3 feat: clear the session of a signin but non-existent user (#1246) 2022-10-29 20:18:02 +08:00
9d5b019243 fix: nil error if init data is empty (#1247) 2022-10-29 20:04:43 +08:00
6bb7b545b4 feat: restrict DingTalk user log in who is under the DingTalk Org(which ClientId belong) (#1241)
* feat: fix bug in GetAcceptLanguage()

* feat: add appName when logging in with DingTalk

* fix review problems

* format code

* delete useless printf

* modify display name

Co-authored-by: Gucheng Wang <nomeguy@qq.com>
2022-10-28 22:14:05 +08:00
49 changed files with 1044 additions and 322 deletions

View File

@ -142,7 +142,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
userId := fmt.Sprintf("%s/%s", subOwner, subName) userId := fmt.Sprintf("%s/%s", subOwner, subName)
user := object.GetUser(userId) user := object.GetUser(userId)
if user != nil && user.IsAdmin && subOwner == objOwner { if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
return true return true
} }

View File

@ -245,8 +245,7 @@ func (c *ApiController) Logout() {
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication() application := c.GetSessionApplication()
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" { if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user) c.ResponseOk(user)

View File

@ -63,8 +63,7 @@ func (c *ApiController) GetSessionUsername() string {
if sessionData != nil && if sessionData != nil &&
sessionData.ExpireTime != 0 && sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() { sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
return "" return ""
} }
@ -85,13 +84,17 @@ func (c *ApiController) GetSessionApplication() *object.Application {
return application return application
} }
func (c *ApiController) ClearUserSession() {
c.SetSessionUsername("")
c.SetSessionData(nil)
}
func (c *ApiController) GetSessionOidc() (string, string) { func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData() sessionData := c.GetSessionData()
if sessionData != nil && if sessionData != nil &&
sessionData.ExpireTime != 0 && sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() { sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
return "", "" return "", ""
} }
scopeValue := c.GetSession("scope") scopeValue := c.GetSession("scope")

View File

@ -48,6 +48,30 @@ func (c *ApiController) GetProviders() {
} }
} }
// GetGlobalProviders
// @Title GetGlobalProviders
// @Tag Provider API
// @Description get Global providers
// @Success 200 {array} object.Provider The Response object
// @router /get-global-providers [get]
func (c *ApiController) GetGlobalProviders() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(providers, paginator.Nums())
}
}
// GetProvider // GetProvider
// @Title GetProvider // @Title GetProvider
// @Tag Provider API // @Tag Provider API

View File

@ -98,6 +98,7 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user := object.GetUser(userId) user := object.GetUser(userId)
if user == nil { if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId)) c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId))
return nil, false return nil, false
} }

View File

@ -15,9 +15,12 @@
package idp package idp
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -167,7 +170,10 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
Email: dtUserInfo.Email, Email: dtUserInfo.Email,
AvatarUrl: dtUserInfo.AvatarUrl, AvatarUrl: dtUserInfo.AvatarUrl,
} }
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
if !isUserInOrg {
return nil, err
}
return &userInfo, nil return &userInfo, nil
} }
@ -194,3 +200,62 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
return data, nil return data, nil
} }
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
appKey := idp.Config.ClientID
appSecret := idp.Config.ClientSecret
body := make(map[string]string)
body["appKey"] = appKey
body["appSecret"] = appSecret
bodyData, err := json.Marshal(body)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err.Error())
}
var data struct {
ExpireIn int `json:"expireIn"`
AccessToken string `json:"accessToken"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
log.Println(err.Error())
}
return data.AccessToken
}
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
body := make(map[string]string)
body["unionid"] = unionId
bodyData, err := json.Marshal(body)
if err != nil {
log.Println(err.Error())
}
reader := bytes.NewReader(bodyData)
accessToken := idp.getInnerAppAccessToken()
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
resp, err := idp.Client.Do(request)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err.Error())
}
var data struct {
ErrCode int `json:"errcode"`
ErrMessage string `json:"errmsg"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
log.Println(err.Error())
}
if data.ErrCode == 60121 {
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
}
return true, nil
}

View File

@ -270,6 +270,13 @@ func UpdateApplication(id string, application *Application) bool {
application.Name = name application.Name = name
} }
if name != application.Name {
err := applicationChangeTrigger(name, application.Name)
if err != nil {
return false
}
}
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
providerItem.Provider = nil providerItem.Provider = nil
} }
@ -400,3 +407,55 @@ func ExtendManagedAccountsWithUser(user *User) *User {
return user return user
} }
func applicationChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
organization := new(Organization)
organization.DefaultApplication = newName
_, err = session.Where("default_application=?", oldName).Update(organization)
if err != nil {
return err
}
user := new(User)
user.SignupApplication = newName
_, err = session.Where("signup_application=?", oldName).Update(user)
if err != nil {
return err
}
resource := new(Resource)
resource.Application = newName
_, err = session.Where("application=?", oldName).Update(resource)
if err != nil {
return err
}
var permissions []*Permission
err = adapter.Engine.Find(&permissions)
if err != nil {
return err
}
for i := 0; i < len(permissions); i++ {
permissionResoureces := permissions[i].Resources
for j := 0; j < len(permissionResoureces); j++ {
if permissionResoureces[j] == oldName {
permissionResoureces[j] = newName
}
}
permissions[i].Resources = permissionResoureces
_, err = session.Where("name=?", permissions[i].Name).Update(permissions[i])
if err != nil {
return err
}
}
return session.Commit()
}

View File

@ -114,6 +114,12 @@ func UpdateCert(id string, cert *Cert) bool {
return false return false
} }
if name != cert.Name {
err := certChangeTrigger(name, cert.Name)
if err != nil {
return false
}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert) affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
if err != nil { if err != nil {
panic(err) panic(err)
@ -161,3 +167,22 @@ func getCertByApplication(application *Application) *Cert {
func GetDefaultCert() *Cert { func GetDefaultCert() *Cert {
return getCert("admin", "cert-built-in") return getCert("admin", "cert-built-in")
} }
func certChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
application := new(Application)
application.Cert = newName
_, err = session.Where("cert=?", oldName).Update(application)
if err != nil {
return err
}
return session.Commit()
}

View File

@ -56,12 +56,40 @@ func readInitDataFromFile(filePath string) *InitData {
s := util.ReadStringFromPath(filePath) s := util.ReadStringFromPath(filePath)
data := &InitData{} data := &InitData{
Organizations: []*Organization{},
Applications: []*Application{},
Users: []*User{},
Certs: []*Cert{},
Providers: []*Provider{},
Ldaps: []*Ldap{},
}
err := util.JsonToStruct(s, data) err := util.JsonToStruct(s, data)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// transform nil slice to empty slice
for _, organization := range data.Organizations {
if organization.Tags == nil {
organization.Tags = []string{}
}
}
for _, application := range data.Applications {
if application.Providers == nil {
application.Providers = []*ProviderItem{}
}
if application.SignupItems == nil {
application.SignupItems = []*SignupItem{}
}
if application.GrantTypes == nil {
application.GrantTypes = []string{}
}
if application.RedirectUris == nil {
application.RedirectUris = []string{}
}
}
return data return data
} }

View File

@ -92,6 +92,12 @@ func UpdateModel(id string, modelObj *Model) bool {
return false return false
} }
if name != modelObj.Name {
err := modelChangeTrigger(name, modelObj.Name)
if err != nil {
return false
}
}
// check model grammar // check model grammar
_, err := model.NewModelFromString(modelObj.ModelText) _, err := model.NewModelFromString(modelObj.ModelText)
if err != nil { if err != nil {
@ -127,3 +133,22 @@ func DeleteModel(model *Model) bool {
func (model *Model) GetId() string { func (model *Model) GetId() string {
return fmt.Sprintf("%s/%s", model.Owner, model.Name) return fmt.Sprintf("%s/%s", model.Owner, model.Name)
} }
func modelChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
permission := new(Permission)
permission.Model = newName
_, err = session.Where("model=?", oldName).Update(permission)
if err != nil {
return err
}
return session.Commit()
}

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
@ -134,15 +135,10 @@ func UpdateOrganization(id string, organization *Organization) bool {
} }
if name != organization.Name { if name != organization.Name {
go func() { err := organizationChangeTrigger(name, organization.Name)
application := new(Application) if err != nil {
application.Organization = organization.Name return false
_, _ = adapter.Engine.Where("organization=?", name).Update(application) }
user := new(User)
user.Owner = organization.Name
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
}()
} }
if organization.MasterPassword != "" && organization.MasterPassword != "***" { if organization.MasterPassword != "" && organization.MasterPassword != "***" {
@ -252,3 +248,148 @@ func GetDefaultApplication(id string) (*Application, error) {
return defaultApplication, nil return defaultApplication, nil
} }
func organizationChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
application := new(Application)
application.Organization = newName
_, err = session.Where("organization=?", oldName).Update(application)
if err != nil {
return err
}
user := new(User)
user.Owner = newName
_, err = session.Where("owner=?", oldName).Update(user)
if err != nil {
return err
}
role := new(Role)
_, err = adapter.Engine.Where("owner=?", oldName).Get(role)
if err != nil {
return err
}
for i, u := range role.Users {
// u = organization/username
split := strings.Split(u, "/")
if split[0] == oldName {
split[0] = newName
role.Users[i] = split[0] + "/" + split[1]
}
}
for i, u := range role.Roles {
// u = organization/username
split := strings.Split(u, "/")
if split[0] == oldName {
split[0] = newName
role.Roles[i] = split[0] + "/" + split[1]
}
}
role.Owner = newName
_, err = session.Where("owner=?", oldName).Update(role)
if err != nil {
return err
}
permission := new(Permission)
_, err = adapter.Engine.Where("owner=?", oldName).Get(permission)
if err != nil {
return err
}
for i, u := range permission.Users {
// u = organization/username
split := strings.Split(u, "/")
if split[0] == oldName {
split[0] = newName
permission.Users[i] = split[0] + "/" + split[1]
}
}
for i, u := range permission.Roles {
// u = organization/username
split := strings.Split(u, "/")
if split[0] == oldName {
split[0] = newName
permission.Roles[i] = split[0] + "/" + split[1]
}
}
permission.Owner = newName
_, err = session.Where("owner=?", oldName).Update(permission)
if err != nil {
return err
}
casbinAdapter := new(CasbinAdapter)
casbinAdapter.Owner = newName
casbinAdapter.Organization = newName
_, err = session.Where("owner=?", oldName).Update(casbinAdapter)
if err != nil {
return err
}
ldap := new(Ldap)
ldap.Owner = newName
_, err = session.Where("owner=?", oldName).Update(ldap)
if err != nil {
return err
}
model := new(Model)
model.Owner = newName
_, err = session.Where("owner=?", oldName).Update(model)
if err != nil {
return err
}
payment := new(Payment)
payment.Organization = newName
_, err = session.Where("organization=?", oldName).Update(payment)
if err != nil {
return err
}
record := new(Record)
record.Owner = newName
record.Organization = newName
_, err = session.Where("organization=?", oldName).Update(record)
if err != nil {
return err
}
resource := new(Resource)
resource.Owner = newName
_, err = session.Where("owner=?", oldName).Update(resource)
if err != nil {
return err
}
syncer := new(Syncer)
syncer.Organization = newName
_, err = session.Where("organization=?", oldName).Update(syncer)
if err != nil {
return err
}
token := new(Token)
token.Organization = newName
_, err = session.Where("organization=?", oldName).Update(token)
if err != nil {
return err
}
webhook := new(Webhook)
webhook.Organization = newName
_, err = session.Where("organization=?", oldName).Update(webhook)
if err != nil {
return err
}
return session.Commit()
}

View File

@ -60,6 +60,7 @@ type Provider struct {
IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"` IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"`
Domain string `xorm:"varchar(100)" json:"domain"` Domain string `xorm:"varchar(100)" json:"domain"`
Bucket string `xorm:"varchar(100)" json:"bucket"` Bucket string `xorm:"varchar(100)" json:"bucket"`
PathPrefix string `xorm:"varchar(100)" json:"pathPrefix"`
Metadata string `xorm:"mediumtext" json:"metadata"` Metadata string `xorm:"mediumtext" json:"metadata"`
IdP string `xorm:"mediumtext" json:"idP"` IdP string `xorm:"mediumtext" json:"idP"`
@ -101,6 +102,16 @@ func GetProviderCount(owner, field, value string) int {
return int(count) return int(count)
} }
func GetGlobalProviderCount(field, value string) int {
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Count(&Provider{})
if err != nil {
panic(err)
}
return int(count)
}
func GetProviders(owner string) []*Provider { func GetProviders(owner string) []*Provider {
providers := []*Provider{} providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner}) err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
@ -111,8 +122,18 @@ func GetProviders(owner string) []*Provider {
return providers return providers
} }
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider { func GetGlobalProviders() []*Provider {
providers := []*Provider{} providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Find(&providers)
if err != nil {
panic(err)
}
return providers
}
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
var providers []*Provider
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&providers) err := session.Find(&providers)
if err != nil { if err != nil {
@ -122,6 +143,17 @@ func GetPaginationProviders(owner string, offset, limit int, field, value, sortF
return providers return providers
} }
func GetPaginationGlobalProviders(offset, limit int, field, value, sortField, sortOrder string) []*Provider {
var providers []*Provider
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&providers)
if err != nil {
panic(err)
}
return providers
}
func getProvider(owner string, name string) *Provider { func getProvider(owner string, name string) *Provider {
if owner == "" || name == "" { if owner == "" || name == "" {
return nil return nil
@ -175,6 +207,13 @@ func UpdateProvider(id string, provider *Provider) bool {
return false return false
} }
if name != provider.Name {
err := providerChangeTrigger(name, provider.Name)
if err != nil {
return false
}
}
session := adapter.Engine.ID(core.PK{owner, name}).AllCols() session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if provider.ClientSecret == "***" { if provider.ClientSecret == "***" {
session = session.Omit("client_secret") session = session.Omit("client_secret")
@ -262,3 +301,41 @@ func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang stri
} }
return nil, nil return nil, nil
} }
func providerChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
var applications []*Application
err = adapter.Engine.Find(&applications)
if err != nil {
return err
}
for i := 0; i < len(applications); i++ {
providers := applications[i].Providers
for j := 0; j < len(providers); j++ {
if providers[j].Name == oldName {
providers[j].Name = newName
}
}
applications[i].Providers = providers
_, err = session.Where("name=?", applications[i].Name).Update(applications[i])
if err != nil {
return err
}
}
resource := new(Resource)
resource.Provider = newName
_, err = session.Where("provider=?", oldName).Update(resource)
if err != nil {
return err
}
return session.Commit()
}

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@ -94,6 +95,13 @@ func UpdateRole(id string, role *Role) bool {
return false return false
} }
if name != role.Name {
err := roleChangeTrigger(name, role.Name)
if err != nil {
return false
}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role) affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
if err != nil { if err != nil {
panic(err) panic(err)
@ -133,3 +141,54 @@ func GetRolesByUser(userId string) []*Role {
return roles return roles
} }
func roleChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
var roles []*Role
err = adapter.Engine.Find(&roles)
if err != nil {
return err
}
for _, role := range roles {
for j, u := range role.Roles {
split := strings.Split(u, "/")
if split[1] == oldName {
split[1] = newName
role.Roles[j] = split[0] + "/" + split[1]
}
}
_, err = session.Where("name=?", role.Name).Update(role)
if err != nil {
return err
}
}
var permissions []*Permission
err = adapter.Engine.Find(&permissions)
if err != nil {
return err
}
for _, permission := range permissions {
for j, u := range permission.Roles {
// u = organization/username
split := strings.Split(u, "/")
if split[1] == oldName {
split[1] = newName
permission.Roles[j] = split[0] + "/" + split[1]
}
}
_, err = session.Where("name=?", permission.Name).Update(permission)
if err != nil {
return err
}
}
return session.Commit()
}

View File

@ -55,7 +55,7 @@ func escapePath(path string) string {
} }
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) { func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
escapedPath := escapePath(fullFilePath) escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath))
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath) objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
host := "" host := ""
@ -70,7 +70,7 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = util.UrlJoin(provider.Domain, "/files") host = util.UrlJoin(provider.Domain, "/files")
} }
if provider.Type == "Azure Blob" { if provider.Type == "Azure Blob" {
host = fmt.Sprintf("%s/%s", host, provider.Bucket) host = util.UrlJoin(host, provider.Bucket)
} }
fileUrl := util.UrlJoin(host, escapePath(objectKey)) fileUrl := util.UrlJoin(host, escapePath(objectKey))

View File

@ -37,7 +37,16 @@ func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
return nil, err return nil, err
} }
return syncer.getOriginalUsersFromMap(results), nil // Memory leak problem handling
// https://github.com/casdoor/casdoor/issues/1256
users := syncer.getOriginalUsersFromMap(results)
for _, m := range results {
for k := range m {
delete(m, k)
}
}
return users, nil
} }
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) { func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {

View File

@ -37,7 +37,7 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
// Encode private key to PKCS#1 ASN.1 PEM. // Encode private key to PKCS#1 ASN.1 PEM.
privateKeyPem := pem.EncodeToMemory( privateKeyPem := pem.EncodeToMemory(
&pem.Block{ &pem.Block{
Type: "PRIVATE KEY", Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key), Bytes: x509.MarshalPKCS1PrivateKey(key),
}, },
) )

View File

@ -380,6 +380,13 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
return false return false
} }
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false
}
}
if user.Password == "***" { if user.Password == "***" {
user.Password = oldUser.Password user.Password = oldUser.Password
} }
@ -416,6 +423,13 @@ func UpdateUserForAllFields(id string, user *User) bool {
return false return false
} }
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false
}
}
user.UpdateUserHash() user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" { if user.Avatar != oldUser.Avatar && user.Avatar != "" {
@ -567,3 +581,62 @@ func ExtendUserWithRolesAndPermissions(user *User) {
user.Roles = GetRolesByUser(user.GetId()) user.Roles = GetRolesByUser(user.GetId())
user.Permissions = GetPermissionsByUser(user.GetId()) user.Permissions = GetPermissionsByUser(user.GetId())
} }
func userChangeTrigger(oldName string, newName string) error {
session := adapter.Engine.NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
var roles []*Role
err = adapter.Engine.Find(&roles)
if err != nil {
return err
}
for _, role := range roles {
for j, u := range role.Users {
// u = organization/username
split := strings.Split(u, "/")
if split[1] == oldName {
split[1] = newName
role.Users[j] = split[0] + "/" + split[1]
}
}
_, err = session.Where("name=?", role.Name).Update(role)
if err != nil {
return err
}
}
var permissions []*Permission
err = adapter.Engine.Find(&permissions)
if err != nil {
return err
}
for _, permission := range permissions {
for j, u := range permission.Users {
// u = organization/username
split := strings.Split(u, "/")
if split[1] == oldName {
split[1] = newName
permission.Users[j] = split[0] + "/" + split[1]
}
}
_, err = session.Where("name=?", permission.Name).Update(permission)
if err != nil {
return err
}
}
resource := new(Resource)
resource.User = newName
_, err = session.Where("user=?", oldName).Update(resource)
if err != nil {
return err
}
return session.Commit()
}

View File

@ -123,6 +123,7 @@ func initAPI() {
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders") beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider") beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider") beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider") beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider") beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")

View File

@ -120,7 +120,7 @@ class AccountTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("organization:Visible"),
dataIndex: "visible", dataIndex: "visible",
key: "visible", key: "visible",
width: "120px", width: "120px",
@ -133,7 +133,7 @@ class AccountTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("organization:viewRule"), title: i18next.t("organization:View rule"),
dataIndex: "viewRule", dataIndex: "viewRule",
key: "viewRule", key: "viewRule",
width: "155px", width: "155px",
@ -160,7 +160,7 @@ class AccountTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("organization:modifyRule"), title: i18next.t("organization:Modify rule"),
dataIndex: "modifyRule", dataIndex: "modifyRule",
key: "modifyRule", key: "modifyRule",
width: "155px", width: "155px",

View File

@ -59,7 +59,7 @@ class AdapterEditPage extends React.Component {
} }
getOrganizations() { getOrganizations() {
OrganizationBackend.getOrganizations(this.state.organizationName) OrganizationBackend.getOrganizations("admin")
.then((res) => { .then((res) => {
this.setState({ this.setState({
organizations: (res.msg === undefined) ? res : [], organizations: (res.msg === undefined) ? res : [],
@ -195,7 +195,7 @@ class AdapterEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} : {Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateadapterField("organization", value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateAdapterField("organization", value);})}>
{ {
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>) this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
} }

View File

@ -70,21 +70,6 @@ class AdapterListPage extends BaseListPage {
renderTable(adapters) { renderTable(adapters) {
const columns = [ const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -101,6 +86,21 @@ class AdapterListPage extends BaseListPage {
); );
}, },
}, },
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -420,13 +420,6 @@ class App extends Component {
</Link> </Link>
</Menu.Item> </Menu.Item>
); );
res.push(
<Menu.Item key="/providers">
<Link to="/providers">
{i18next.t("general:Providers")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/applications"> <Menu.Item key="/applications">
<Link to="/applications"> <Link to="/applications">
@ -437,6 +430,13 @@ class App extends Component {
} }
if (Setting.isLocalAdminUser(this.state.account)) { if (Setting.isLocalAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/providers">
<Link to="/providers">
{i18next.t("general:Providers")}
</Link>
</Menu.Item>
);
res.push( res.push(
<Menu.Item key="/resources"> <Menu.Item key="/resources">
<Link to="/resources"> <Link to="/resources">
@ -566,6 +566,7 @@ class App extends Component {
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} /> <Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> <Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} /> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} /> <Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} /> <Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />

View File

@ -123,7 +123,7 @@
.login-form { .login-form {
text-align: center; text-align: center;
padding: 10px; padding: 30px;
} }
.login-content { .login-content {

View File

@ -91,6 +91,7 @@ class ApplicationEditPage extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
owner: props.account.owner,
applicationName: props.match.params.applicationName, applicationName: props.match.params.applicationName,
application: null, application: null,
organizations: [], organizations: [],
@ -141,12 +142,21 @@ class ApplicationEditPage extends React.Component {
} }
getProviders() { getProviders() {
ProviderBackend.getProviders("admin") if (Setting.isAdminUser(this.props.account)) {
ProviderBackend.getGlobalProviders()
.then((res) => { .then((res) => {
this.setState({ this.setState({
providers: res, providers: res,
}); });
}); });
} else {
ProviderBackend.getProviders(this.state.owner)
.then((res) => {
this.setState({
providers: res,
});
});
}
} }
getSamlMetadata() { getSamlMetadata() {

View File

@ -63,21 +63,6 @@ class ModelListPage extends BaseListPage {
renderTable(models) { renderTable(models) {
const columns = [ const columns = [
{
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:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -94,6 +79,21 @@ class ModelListPage extends BaseListPage {
); );
}, },
}, },
{
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"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -76,6 +76,38 @@ class PaymentListPage extends BaseListPage {
renderTable(payments) { renderTable(payments) {
const columns = [ const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "180px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/payments/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Provider"),
dataIndex: "provider",
key: "provider",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("provider"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Organization"), title: i18next.t("general:Organization"),
dataIndex: "organization", dataIndex: "organization",
@ -106,22 +138,7 @@ class PaymentListPage extends BaseListPage {
); );
}, },
}, },
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "180px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/payments/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",
@ -140,22 +157,6 @@ class PaymentListPage extends BaseListPage {
// sorter: true, // sorter: true,
// ...this.getColumnSearchProps('displayName'), // ...this.getColumnSearchProps('displayName'),
// }, // },
{
title: i18next.t("general:Provider"),
dataIndex: "provider",
key: "provider",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("provider"),
render: (text, record, index) => {
return (
<Link to={`/providers/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("payment:Type"), title: i18next.t("payment:Type"),
dataIndex: "type", dataIndex: "type",

View File

@ -77,21 +77,7 @@ class PermissionListPage extends BaseListPage {
renderTable(permissions) { renderTable(permissions) {
const columns = [ const columns = [
{ // https://github.com/ant-design/ant-design/issues/22184
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:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -108,6 +94,21 @@ class PermissionListPage extends BaseListPage {
); );
}, },
}, },
{
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"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -32,6 +32,7 @@ class ProviderEditPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
providerName: props.match.params.providerName, providerName: props.match.params.providerName,
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
provider: null, provider: null,
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
}; };
@ -42,7 +43,7 @@ class ProviderEditPage extends React.Component {
} }
getProvider() { getProvider() {
ProviderBackend.getProvider("admin", this.state.providerName) ProviderBackend.getProvider(this.state.owner, this.state.providerName)
.then((provider) => { .then((provider) => {
this.setState({ this.setState({
provider: provider, provider: provider,
@ -469,6 +470,16 @@ class ProviderEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:The prefix path of the file - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.pathPrefix} onChange={e => {
this.updateProviderField("pathPrefix", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :

View File

@ -23,10 +23,25 @@ import i18next from "i18next";
import BaseListPage from "./BaseListPage"; import BaseListPage from "./BaseListPage";
class ProviderListPage extends BaseListPage { class ProviderListPage extends BaseListPage {
constructor(props) {
super(props);
this.state = {
classes: props,
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.organization.name,
data: [],
pagination: {
current: 1,
pageSize: 10,
},
loading: false,
searchText: "",
searchedColumn: "",
};
}
newProvider() { newProvider() {
const randomName = Setting.getRandomName(); const randomName = Setting.getRandomName();
return { return {
owner: "admin", // this.props.account.providername, owner: this.state.owner,
name: `provider_${randomName}`, name: `provider_${randomName}`,
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Provider - ${randomName}`, displayName: `New Provider - ${randomName}`,
@ -46,7 +61,7 @@ class ProviderListPage extends BaseListPage {
const newProvider = this.newProvider(); const newProvider = this.newProvider();
ProviderBackend.addProvider(newProvider) ProviderBackend.addProvider(newProvider)
.then((res) => { .then((res) => {
this.props.history.push({pathname: `/providers/${newProvider.name}`, mode: "add"}); this.props.history.push({pathname: `/providers/${newProvider.owner}/${newProvider.name}`, mode: "add"});
} }
) )
.catch(error => { .catch(error => {
@ -177,7 +192,7 @@ class ProviderListPage extends BaseListPage {
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete provider: ${record.name} ?`} title={`Sure to delete provider: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)} onConfirm={() => this.deleteProvider(index)}
@ -224,7 +239,8 @@ class ProviderListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
ProviderBackend.getProviders("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) (Setting.isAdminUser(this.props.account) ? ProviderBackend.getGlobalProviders(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: ProviderBackend.getProviders(this.state.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.setState({ this.setState({

View File

@ -105,7 +105,7 @@ class ProviderTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:canSignUp"), title: i18next.t("provider:Can signup"),
dataIndex: "canSignUp", dataIndex: "canSignUp",
key: "canSignUp", key: "canSignUp",
width: "120px", width: "120px",
@ -122,7 +122,7 @@ class ProviderTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:canSignIn"), title: i18next.t("provider:Can signin"),
dataIndex: "canSignIn", dataIndex: "canSignIn",
key: "canSignIn", key: "canSignIn",
width: "120px", width: "120px",
@ -139,7 +139,7 @@ class ProviderTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:canUnlink"), title: i18next.t("provider:Can unlink"),
dataIndex: "canUnlink", dataIndex: "canUnlink",
key: "canUnlink", key: "canUnlink",
width: "120px", width: "120px",
@ -156,7 +156,7 @@ class ProviderTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:Prompted"),
dataIndex: "prompted", dataIndex: "prompted",
key: "prompted", key: "prompted",
width: "120px", width: "120px",

View File

@ -65,21 +65,6 @@ class RoleListPage extends BaseListPage {
renderTable(roles) { renderTable(roles) {
const columns = [ const columns = [
{
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:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -96,6 +81,21 @@ class RoleListPage extends BaseListPage {
); );
}, },
}, },
{
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"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Link, useHistory} from "react-router-dom"; import {Link} from "react-router-dom";
import {Tag, Tooltip, message} from "antd"; import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
@ -752,7 +752,7 @@ export function getLoginLink(application) {
} else if (authConfig.appName === application.name) { } else if (authConfig.appName === application.name) {
url = "/login"; url = "/login";
} else if (application.signinUrl === "") { } else if (application.signinUrl === "") {
url = path.join(application.homepageUrl, "login"); url = path.join(application.homepageUrl, "/login");
} else { } else {
url = application.signinUrl; url = application.signinUrl;
} }
@ -764,9 +764,8 @@ export function renderLoginLink(application, text) {
return renderLink(url, text, null); return renderLink(url, text, null);
} }
export function redirectToLoginPage(application) { export function redirectToLoginPage(application, history) {
const loginLink = getLoginLink(application); const loginLink = getLoginLink(application);
const history = useHistory();
history.push(loginLink); history.push(loginLink);
} }

View File

@ -104,7 +104,7 @@ class SignupTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:visible"), title: i18next.t("provider:Visible"),
dataIndex: "visible", dataIndex: "visible",
key: "visible", key: "visible",
width: "120px", width: "120px",
@ -126,7 +126,7 @@ class SignupTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:required"), title: i18next.t("provider:Required"),
dataIndex: "required", dataIndex: "required",
key: "required", key: "required",
width: "120px", width: "120px",
@ -143,7 +143,7 @@ class SignupTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("provider:prompted"), title: i18next.t("provider:Prompted"),
dataIndex: "prompted", dataIndex: "prompted",
key: "prompted", key: "prompted",
width: "120px", width: "120px",

View File

@ -88,21 +88,6 @@ class SyncerListPage extends BaseListPage {
renderTable(syncers) { renderTable(syncers) {
const columns = [ const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -119,6 +104,21 @@ class SyncerListPage extends BaseListPage {
); );
}, },
}, },
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "120px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -90,18 +90,19 @@ class SystemInfo extends React.Component {
</div> : i18next.t("system:Get Memory Usage Failed") </div> : i18next.t("system:Get Memory Usage Failed")
); );
if (!Setting.isMobile()) {
return ( return (
<Row> <Row>
<Col span={6}></Col> <Col span={6}></Col>
<Col span={12}> <Col span={12}>
<Row gutter={[10, 10]}> <Row gutter={[10, 10]}>
<Col span={12}> <Col span={12}>
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center"}}> <Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
{this.state.loading ? <Spin size="large" /> : CPUInfo} {this.state.loading ? <Spin size="large" /> : CPUInfo}
</Card> </Card>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center"}}> <Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
{this.state.loading ? <Spin size="large" /> : MemInfo} {this.state.loading ? <Spin size="large" /> : MemInfo}
</Card> </Card>
</Col> </Col>
@ -121,6 +122,34 @@ class SystemInfo extends React.Component {
<Col span={6}></Col> <Col span={6}></Col>
</Row> </Row>
); );
} else {
return (
<Row gutter={[16, 0]}>
<Col span={24}>
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
{this.state.loading ? <Spin size="large" /> : CPUInfo}
</Card>
</Col>
<Col span={24}>
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
{this.state.loading ? <Spin size="large" /> : MemInfo}
</Card>
</Col>
<Col span={24}>
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
<br />
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
<br />
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
<br />
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
</Card>
</Col>
</Row>
);
}
} }
} }

View File

@ -17,6 +17,7 @@ import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table, Upload} from "antd"; import {Button, Popconfirm, Switch, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import i18next from "i18next"; import i18next from "i18next";
@ -28,6 +29,7 @@ class UserListPage extends BaseListPage {
this.state = { this.state = {
classes: props, classes: props,
organizationName: props.match.params.organizationName, organizationName: props.match.params.organizationName,
organization: null,
data: [], data: [],
pagination: { pagination: {
current: 1, current: 1,
@ -271,6 +273,15 @@ class UserListPage extends BaseListPage {
width: "110px", width: "110px",
sorter: true, sorter: true,
...this.getColumnSearchProps("tag"), ...this.getColumnSearchProps("tag"),
render: (text, record, index) => {
const tagMap = {};
this.state.organization?.tags?.map((tag, index) => {
const tokens = tag.split("|");
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
tagMap[tokens[0]] = displayValue;
});
return tagMap[text];
},
}, },
{ {
title: i18next.t("user:Is admin"), title: i18next.t("user:Is admin"),
@ -387,6 +398,11 @@ class UserListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
const users = res.data;
if (users.length > 0) {
this.getOrganization(users[0].owner);
}
} }
}); });
} else { } else {
@ -403,10 +419,24 @@ class UserListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
const users = res.data;
if (users.length > 0) {
this.getOrganization(users[0].owner);
}
} }
}); });
} }
}; };
getOrganization(organizationName) {
OrganizationBackend.getOrganization("admin", organizationName)
.then((organization) => {
this.setState({
organization: organization,
});
});
}
} }
export default UserListPage; export default UserListPage;

View File

@ -67,21 +67,6 @@ class WebhookListPage extends BaseListPage {
renderTable(webhooks) { renderTable(webhooks) {
const columns = [ const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "110px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: "name", dataIndex: "name",
@ -98,6 +83,21 @@ class WebhookListPage extends BaseListPage {
); );
}, },
}, },
{
title: i18next.t("general:Organization"),
dataIndex: "organization",
key: "organization",
width: "110px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: "createdTime", dataIndex: "createdTime",

View File

@ -23,6 +23,7 @@ import {CountDownInput} from "../common/CountDownInput";
import * as UserBackend from "../backend/UserBackend"; import * as UserBackend from "../backend/UserBackend";
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons"; import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import {withRouter} from "react-router-dom";
const {Step} = Steps; const {Step} = Steps;
const {Option} = Select; const {Option} = Select;
@ -166,7 +167,7 @@ class ForgetPage extends React.Component {
values.userOwner = this.state.application?.organizationObj.name; values.userOwner = this.state.application?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.redirectToLoginPage(this.state.application); Setting.redirectToLoginPage(this.state.application, this.props.history);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }
@ -489,7 +490,7 @@ class ForgetPage extends React.Component {
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="login-content forget-content"> <div className="login-content forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
<Row> <Row>
<Col span={24} style={{justifyContent: "center"}}> <Col span={24} style={{justifyContent: "center"}}>
<Row> <Row>
@ -550,4 +551,4 @@ class ForgetPage extends React.Component {
} }
} }
export default ForgetPage; export default withRouter(ForgetPage);

View File

@ -30,6 +30,7 @@ import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import {CaptchaModal} from "../common/CaptchaModal"; import {CaptchaModal} from "../common/CaptchaModal";
import {withRouter} from "react-router-dom";
const {TabPane} = Tabs; const {TabPane} = Tabs;
@ -339,7 +340,7 @@ class LoginPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"} subTitle={"The application does not allow to sign up new account"}
extra={[ extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}> <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{ {
i18next.t("login:Sign In") i18next.t("login:Sign In")
} }
@ -784,7 +785,6 @@ class LoginPage extends React.Component {
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel"> <div className="login-panel">
<SelectLanguageBox id="language-box-corner" style={{top: "50px"}} />
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} /> <div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
</div> </div>
@ -800,6 +800,7 @@ class LoginPage extends React.Component {
{/* {*/} {/* {*/}
{/* this.state.clientId !== null ? "Redirect" : null*/} {/* this.state.clientId !== null ? "Redirect" : null*/}
{/* }*/} {/* }*/}
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
{ {
this.renderSignedInBox() this.renderSignedInBox()
} }
@ -816,4 +817,4 @@ class LoginPage extends React.Component {
} }
} }
export default withTranslation()(LoginPage); export default withTranslation()(withRouter(LoginPage));

View File

@ -22,6 +22,7 @@ import i18next from "i18next";
import AffiliationSelect from "../common/AffiliationSelect"; import AffiliationSelect from "../common/AffiliationSelect";
import OAuthWidget from "../common/OAuthWidget"; import OAuthWidget from "../common/OAuthWidget";
import SelectRegionBox from "../SelectRegionBox"; import SelectRegionBox from "../SelectRegionBox";
import {withRouter} from "react-router-dom";
class PromptPage extends React.Component { class PromptPage extends React.Component {
constructor(props) { constructor(props) {
@ -190,7 +191,7 @@ class PromptPage extends React.Component {
if (redirectUrl !== "" && redirectUrl !== null) { if (redirectUrl !== "" && redirectUrl !== null) {
Setting.goToLink(redirectUrl); Setting.goToLink(redirectUrl);
} else { } else {
Setting.redirectToLoginPage(this.getApplicationObj()); Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
} }
} else { } else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
@ -234,7 +235,7 @@ class PromptPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"You are unexpected to see this prompt page"} subTitle={"You are unexpected to see this prompt page"}
extra={[ extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}> <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{ {
i18next.t("login:Sign In") i18next.t("login:Sign In")
} }
@ -272,4 +273,4 @@ class PromptPage extends React.Component {
} }
} }
export default PromptPage; export default withRouter(PromptPage);

View File

@ -26,6 +26,7 @@ import {CountDownInput} from "../common/CountDownInput";
import SelectRegionBox from "../SelectRegionBox"; import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {withRouter} from "react-router-dom";
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
@ -541,7 +542,7 @@ class SignupPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"} subTitle={"The application does not allow to sign up new account"}
extra={[ extra={[
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}> <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
{ {
i18next.t("login:Sign In") i18next.t("login:Sign In")
} }
@ -562,7 +563,7 @@ class SignupPage extends React.Component {
application: application.name, application: application.name,
organization: application.organization, organization: application.organization,
}} }}
style={{width: !Setting.isMobile() ? "400px" : "250px"}} style={{width: !Setting.isMobile() ? "400px" : "300px"}}
size="large" size="large"
> >
<Form.Item <Form.Item
@ -600,7 +601,7 @@ class SignupPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") { if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage); Setting.goToLink(linkInStorage);
} else { } else {
Setting.redirectToLoginPage(application); Setting.redirectToLoginPage(application, this.props.history);
} }
}}> }}>
{i18next.t("signup:sign in now")} {i18next.t("signup:sign in now")}
@ -633,7 +634,6 @@ class SignupPage extends React.Component {
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
<div className="login-panel" > <div className="login-panel" >
<SelectLanguageBox id="language-box-corner" style={{top: "50px"}} />
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}> <div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} /> <div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
</div> </div>
@ -645,6 +645,7 @@ class SignupPage extends React.Component {
{ {
Setting.renderLogo(application) Setting.renderLogo(application)
} }
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
{ {
this.renderForm(application) this.renderForm(application)
} }
@ -660,4 +661,4 @@ class SignupPage extends React.Component {
} }
} }
export default SignupPage; export default withRouter(SignupPage);

View File

@ -24,6 +24,16 @@ export function getProviders(owner, page = "", pageSize = "", field = "", value
}).then(res => res.json()); }).then(res => res.json());
} }
export function getGlobalProviders(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-global-providers?p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function getProvider(owner, name) { export function getProvider(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-provider?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-provider?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"New Application": "New Application", "New Application": "New Application",
"None": "None",
"Password ON": "Passwort AN", "Password ON": "Passwort AN",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei", "Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
@ -53,6 +55,7 @@
"Refresh token expire": "Aktualisierungs-Token läuft ab", "Refresh token expire": "Aktualisierungs-Token läuft ab",
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip", "Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -67,10 +70,7 @@
"Token expire": "Token läuft ab", "Token expire": "Token läuft ab",
"Token expire - Tooltip": "Token läuft ab - Tooltip", "Token expire - Tooltip": "Token läuft ab - Tooltip",
"Token format": "Token-Format", "Token format": "Token-Format",
"Token format - Tooltip": "Token-Format - Tooltip", "Token format - Tooltip": "Token-Format - Tooltip"
"Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bitgröße", "Bit size": "Bitgröße",
@ -311,15 +311,16 @@
"Favicon": "Févicon", "Favicon": "Févicon",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "Weiche Löschung", "Soft deletion": "Weiche Löschung",
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip", "Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "Website-URL", "Website URL": "Website-URL",
"Website URL - Tooltip": "Unique string-style identifier", "Website URL - Tooltip": "Unique string-style identifier"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "Eimer", "Bucket": "Eimer",
"Bucket - Tooltip": "Storage bucket name", "Bucket - Tooltip": "Storage bucket name",
"Can not parse Metadata": "Metadaten können nicht analysiert werden", "Can not parse Metadata": "Metadaten können nicht analysiert werden",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Kategorie", "Category": "Kategorie",
"Category - Tooltip": "Unique string-style identifier", "Category - Tooltip": "Unique string-style identifier",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "Metadaten erfolgreich analysieren", "Parse Metadata successfully": "Metadaten erfolgreich analysieren",
"Path prefix": "Path prefix",
"Port": "Port", "Port": "Port",
"Port - Tooltip": "Unique string-style identifier", "Port - Tooltip": "Unique string-style identifier",
"Prompted": "Prompted",
"Provider URL": "Provider-URL", "Provider URL": "Provider-URL",
"Provider URL - Tooltip": "Unique string-style identifier", "Provider URL - Tooltip": "Unique string-style identifier",
"Region ID": "Region ID", "Region ID": "Region ID",
"Region ID - Tooltip": "Region ID - Tooltip", "Region ID - Tooltip": "Region ID - Tooltip",
"Region endpoint for Internet": "Region Endpunkt für Internet", "Region endpoint for Internet": "Region Endpunkt für Internet",
"Region endpoint for Intranet": "Region Endpunkt für Intranet", "Region endpoint for Intranet": "Region Endpunkt für Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Smtp Connection",
"Test Email": "Test email config", "Test Email": "Test email config",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Email Address",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Typ", "Type": "Typ",
"Type - Tooltip": "Unique string-style identifier", "Type - Tooltip": "Unique string-style identifier",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "alarmtyp", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "alarmtyp"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "gefragt",
"required": "benötigt",
"visible": "sichtbar"
}, },
"record": { "record": {
"Is Triggered": "Wird ausgelöst" "Is Triggered": "Wird ausgelöst"

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"New Application": "New Application", "New Application": "New Application",
"None": "None",
"Password ON": "Password ON", "Password ON": "Password ON",
"Password ON - Tooltip": "Password ON - Tooltip", "Password ON - Tooltip": "Password ON - Tooltip",
"Please select a HTML file": "Please select a HTML file", "Please select a HTML file": "Please select a HTML file",
@ -53,6 +55,7 @@
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -67,10 +70,7 @@
"Token expire": "Token expire", "Token expire": "Token expire",
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip", "Token format - Tooltip": "Token format - Tooltip"
"Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bit size", "Bit size": "Bit size",
@ -311,15 +311,16 @@
"Favicon": "Favicon", "Favicon": "Favicon",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
"Soft deletion - Tooltip": "Soft deletion - Tooltip", "Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
"Website URL - Tooltip": "Website URL - Tooltip", "Website URL - Tooltip": "Website URL - Tooltip"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "Bucket", "Bucket": "Bucket",
"Bucket - Tooltip": "Bucket - Tooltip", "Bucket - Tooltip": "Bucket - Tooltip",
"Can not parse Metadata": "Can not parse Metadata", "Can not parse Metadata": "Can not parse Metadata",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Category", "Category": "Category",
"Category - Tooltip": "Category - Tooltip", "Category - Tooltip": "Category - Tooltip",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "Parse Metadata successfully", "Parse Metadata successfully": "Parse Metadata successfully",
"Path prefix": "Path prefix",
"Port": "Port", "Port": "Port",
"Port - Tooltip": "Port - Tooltip", "Port - Tooltip": "Port - Tooltip",
"Prompted": "Prompted",
"Provider URL": "Provider URL", "Provider URL": "Provider URL",
"Provider URL - Tooltip": "Provider URL - Tooltip", "Provider URL - Tooltip": "Provider URL - Tooltip",
"Region ID": "Region ID", "Region ID": "Region ID",
"Region ID - Tooltip": "Region ID - Tooltip", "Region ID - Tooltip": "Region ID - Tooltip",
"Region endpoint for Internet": "Region endpoint for Internet", "Region endpoint for Internet": "Region endpoint for Internet",
"Region endpoint for Intranet": "Region endpoint for Intranet", "Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Connection", "Test Connection": "Test Connection",
"Test Email": "Test Email", "Test Email": "Test Email",
"Test Email - Tooltip": "Test Email - Tooltip", "Test Email - Tooltip": "Test Email - Tooltip",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Type", "Type": "Type",
"Type - Tooltip": "Type - Tooltip", "Type - Tooltip": "Type - Tooltip",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "alertType", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "alertType"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "prompted",
"required": "required",
"visible": "visible"
}, },
"record": { "record": {
"Is Triggered": "Is Triggered" "Is Triggered": "Is Triggered"

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"New Application": "New Application", "New Application": "New Application",
"None": "None",
"Password ON": "Mot de passe activé", "Password ON": "Mot de passe activé",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Veuillez sélectionner un fichier HTML", "Please select a HTML file": "Veuillez sélectionner un fichier HTML",
@ -53,6 +55,7 @@
"Refresh token expire": "Expiration du jeton d'actualisation", "Refresh token expire": "Expiration du jeton d'actualisation",
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle", "Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
"Right": "Right", "Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -67,10 +70,7 @@
"Token expire": "Expiration du jeton", "Token expire": "Expiration du jeton",
"Token expire - Tooltip": "Expiration du jeton - Info-bulle", "Token expire - Tooltip": "Expiration du jeton - Info-bulle",
"Token format": "Format du jeton", "Token format": "Format du jeton",
"Token format - Tooltip": "Format du jeton - infobulle", "Token format - Tooltip": "Format du jeton - infobulle"
"Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Taille du bit", "Bit size": "Taille du bit",
@ -311,15 +311,16 @@
"Favicon": "Favicon", "Favicon": "Favicon",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "Suppression du logiciel", "Soft deletion": "Suppression du logiciel",
"Soft deletion - Tooltip": "Suppression de soft - infobulle", "Soft deletion - Tooltip": "Suppression de soft - infobulle",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "URL du site web", "Website URL": "URL du site web",
"Website URL - Tooltip": "Unique string-style identifier", "Website URL - Tooltip": "Unique string-style identifier"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "Seau", "Bucket": "Seau",
"Bucket - Tooltip": "Storage bucket name", "Bucket - Tooltip": "Storage bucket name",
"Can not parse Metadata": "Impossible d'analyser les métadonnées", "Can not parse Metadata": "Impossible d'analyser les métadonnées",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Catégorie", "Category": "Catégorie",
"Category - Tooltip": "Unique string-style identifier", "Category - Tooltip": "Unique string-style identifier",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "Analyse des métadonnées réussie", "Parse Metadata successfully": "Analyse des métadonnées réussie",
"Path prefix": "Path prefix",
"Port": "Port", "Port": "Port",
"Port - Tooltip": "Unique string-style identifier", "Port - Tooltip": "Unique string-style identifier",
"Prompted": "Prompted",
"Provider URL": "URL du fournisseur", "Provider URL": "URL du fournisseur",
"Provider URL - Tooltip": "Unique string-style identifier", "Provider URL - Tooltip": "Unique string-style identifier",
"Region ID": "ID de la région", "Region ID": "ID de la région",
"Region ID - Tooltip": "ID de région - infobulle", "Region ID - Tooltip": "ID de région - infobulle",
"Region endpoint for Internet": "Point de terminaison de la région pour Internet", "Region endpoint for Internet": "Point de terminaison de la région pour Internet",
"Region endpoint for Intranet": "Point de terminaison de la région pour Intranet", "Region endpoint for Intranet": "Point de terminaison de la région pour Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Smtp Connection",
"Test Email": "Test email config", "Test Email": "Test email config",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Email Address",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Type de texte", "Type": "Type de texte",
"Type - Tooltip": "Unique string-style identifier", "Type - Tooltip": "Unique string-style identifier",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "Type d'alerte", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "Type d'alerte"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "invitée",
"required": "Obligatoire",
"visible": "Visible"
}, },
"record": { "record": {
"Is Triggered": "Est déclenché" "Is Triggered": "Est déclenché"

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"New Application": "New Application", "New Application": "New Application",
"None": "None",
"Password ON": "パスワードON", "Password ON": "パスワードON",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "HTMLファイルを選択してください", "Please select a HTML file": "HTMLファイルを選択してください",
@ -53,6 +55,7 @@
"Refresh token expire": "トークンの更新の期限が切れます", "Refresh token expire": "トークンの更新の期限が切れます",
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ", "Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
"Right": "Right", "Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -67,10 +70,7 @@
"Token expire": "トークンの有効期限", "Token expire": "トークンの有効期限",
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ", "Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
"Token format": "トークンのフォーマット", "Token format": "トークンのフォーマット",
"Token format - Tooltip": "トークンフォーマット - ツールチップ", "Token format - Tooltip": "トークンフォーマット - ツールチップ"
"Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "ビットサイズ", "Bit size": "ビットサイズ",
@ -311,15 +311,16 @@
"Favicon": "ファビコン", "Favicon": "ファビコン",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "ソフト削除", "Soft deletion": "ソフト削除",
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ", "Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
"Website URL - Tooltip": "Unique string-style identifier", "Website URL - Tooltip": "Unique string-style identifier"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "バケツ入りバケツ", "Bucket": "バケツ入りバケツ",
"Bucket - Tooltip": "Storage bucket name", "Bucket - Tooltip": "Storage bucket name",
"Can not parse Metadata": "メタデータをパースできません", "Can not parse Metadata": "メタデータをパースできません",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "カテゴリ", "Category": "カテゴリ",
"Category - Tooltip": "Unique string-style identifier", "Category - Tooltip": "Unique string-style identifier",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "メタデータの解析に成功", "Parse Metadata successfully": "メタデータの解析に成功",
"Path prefix": "Path prefix",
"Port": "ポート", "Port": "ポート",
"Port - Tooltip": "Unique string-style identifier", "Port - Tooltip": "Unique string-style identifier",
"Prompted": "Prompted",
"Provider URL": "プロバイダー URL", "Provider URL": "プロバイダー URL",
"Provider URL - Tooltip": "Unique string-style identifier", "Provider URL - Tooltip": "Unique string-style identifier",
"Region ID": "地域ID", "Region ID": "地域ID",
"Region ID - Tooltip": "リージョンID - ツールチップ", "Region ID - Tooltip": "リージョンID - ツールチップ",
"Region endpoint for Internet": "インターネットのリージョンエンドポイント", "Region endpoint for Internet": "インターネットのリージョンエンドポイント",
"Region endpoint for Intranet": "イントラネットのリージョンエンドポイント", "Region endpoint for Intranet": "イントラネットのリージョンエンドポイント",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Smtp Connection",
"Test Email": "Test email config", "Test Email": "Test email config",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Email Address",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "タイプ", "Type": "タイプ",
"Type - Tooltip": "Unique string-style identifier", "Type - Tooltip": "Unique string-style identifier",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "alertType", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "alertType"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "プロンプトされた",
"required": "必須",
"visible": "表示"
}, },
"record": { "record": {
"Is Triggered": "トリガーされます" "Is Triggered": "トリガーされます"

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Grant types - Tooltip", "Grant types - Tooltip": "Grant types - Tooltip",
"Left": "Left", "Left": "Left",
"New Application": "New Application", "New Application": "New Application",
"None": "None",
"Password ON": "Password ON", "Password ON": "Password ON",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Please select a HTML file", "Please select a HTML file": "Please select a HTML file",
@ -53,6 +55,7 @@
"Refresh token expire": "Refresh token expire", "Refresh token expire": "Refresh token expire",
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"Right": "Right", "Right": "Right",
"Rule": "Rule",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully", "SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
@ -67,10 +70,7 @@
"Token expire": "Token expire", "Token expire": "Token expire",
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip", "Token format - Tooltip": "Token format - Tooltip"
"Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bit size", "Bit size": "Bit size",
@ -311,15 +311,16 @@
"Favicon": "Favicon", "Favicon": "Favicon",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "Soft deletion", "Soft deletion": "Soft deletion",
"Soft deletion - Tooltip": "Soft deletion - Tooltip", "Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "Website URL", "Website URL": "Website URL",
"Website URL - Tooltip": "Unique string-style identifier", "Website URL - Tooltip": "Unique string-style identifier"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "Bucket", "Bucket": "Bucket",
"Bucket - Tooltip": "Storage bucket name", "Bucket - Tooltip": "Storage bucket name",
"Can not parse Metadata": "Can not parse Metadata", "Can not parse Metadata": "Can not parse Metadata",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Category", "Category": "Category",
"Category - Tooltip": "Unique string-style identifier", "Category - Tooltip": "Unique string-style identifier",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "Parse Metadata successfully", "Parse Metadata successfully": "Parse Metadata successfully",
"Path prefix": "Path prefix",
"Port": "Port", "Port": "Port",
"Port - Tooltip": "Unique string-style identifier", "Port - Tooltip": "Unique string-style identifier",
"Prompted": "Prompted",
"Provider URL": "Provider URL", "Provider URL": "Provider URL",
"Provider URL - Tooltip": "Unique string-style identifier", "Provider URL - Tooltip": "Unique string-style identifier",
"Region ID": "Region ID", "Region ID": "Region ID",
"Region ID - Tooltip": "Region ID - Tooltip", "Region ID - Tooltip": "Region ID - Tooltip",
"Region endpoint for Internet": "Region endpoint for Internet", "Region endpoint for Internet": "Region endpoint for Internet",
"Region endpoint for Intranet": "Region endpoint for Intranet", "Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Smtp Connection",
"Test Email": "Test email config", "Test Email": "Test email config",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Email Address",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Type", "Type": "Type",
"Type - Tooltip": "Unique string-style identifier", "Type - Tooltip": "Unique string-style identifier",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "alertType", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "alertType"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "prompted",
"required": "required",
"visible": "visible"
}, },
"record": { "record": {
"Is Triggered": "Is Triggered" "Is Triggered": "Is Triggered"

View File

@ -13,6 +13,7 @@
"Sync": "Sync" "Sync": "Sync"
}, },
"application": { "application": {
"Always": "Always",
"Auto signin": "Auto signin", "Auto signin": "Auto signin",
"Auto signin - Tooltip": "Auto signin - Tooltip", "Auto signin - Tooltip": "Auto signin - Tooltip",
"Background URL": "Background URL", "Background URL": "Background URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "Виды грантов - Подсказка", "Grant types - Tooltip": "Виды грантов - Подсказка",
"Left": "Left", "Left": "Left",
"New Application": "Новое приложение", "New Application": "Новое приложение",
"None": "None",
"Password ON": "Пароль ВКЛ", "Password ON": "Пароль ВКЛ",
"Password ON - Tooltip": "Whether to allow password login", "Password ON - Tooltip": "Whether to allow password login",
"Please select a HTML file": "Пожалуйста, выберите HTML-файл", "Please select a HTML file": "Пожалуйста, выберите HTML-файл",
@ -53,6 +55,7 @@
"Refresh token expire": "Срок действия обновления токена истекает", "Refresh token expire": "Срок действия обновления токена истекает",
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка", "Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
"Right": "Right", "Right": "Right",
"Rule": "правило",
"SAML metadata": "Метаданные SAML", "SAML metadata": "Метаданные SAML",
"SAML metadata - Tooltip": "Метаданные SAML - Подсказка", "SAML metadata - Tooltip": "Метаданные SAML - Подсказка",
"SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена", "SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена",
@ -67,10 +70,7 @@
"Token expire": "Токен истекает", "Token expire": "Токен истекает",
"Token expire - Tooltip": "Истек токен - Подсказка", "Token expire - Tooltip": "Истек токен - Подсказка",
"Token format": "Формат токена", "Token format": "Формат токена",
"Token format - Tooltip": "Формат токена - Подсказка", "Token format - Tooltip": "Формат токена - Подсказка"
"Rule": "правило",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Размер бита", "Bit size": "Размер бита",
@ -311,15 +311,16 @@
"Favicon": "Иконка", "Favicon": "Иконка",
"Is profile public": "Is profile public", "Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip", "Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization", "New Organization": "New Organization",
"Soft deletion": "Мягкое удаление", "Soft deletion": "Мягкое удаление",
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка", "Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
"Tags": "Tags", "Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip", "Tags - Tooltip": "Tags - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "URL сайта", "Website URL": "URL сайта",
"Website URL - Tooltip": "Unique string-style identifier", "Website URL - Tooltip": "Unique string-style identifier"
"modifyRule": "modifyRule",
"viewRule": "viewRule"
}, },
"payment": { "payment": {
"Confirm your invoice information": "Confirm your invoice information", "Confirm your invoice information": "Confirm your invoice information",
@ -452,6 +453,9 @@
"Bucket": "Ведро", "Bucket": "Ведро",
"Bucket - Tooltip": "Storage bucket name", "Bucket - Tooltip": "Storage bucket name",
"Can not parse Metadata": "Невозможно разобрать метаданные", "Can not parse Metadata": "Невозможно разобрать метаданные",
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Категория", "Category": "Категория",
"Category - Tooltip": "Unique string-style identifier", "Category - Tooltip": "Unique string-style identifier",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "New Provider", "New Provider": "New Provider",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "Анализ метаданных успешно завершен", "Parse Metadata successfully": "Анализ метаданных успешно завершен",
"Path prefix": "Path prefix",
"Port": "Порт", "Port": "Порт",
"Port - Tooltip": "Unique string-style identifier", "Port - Tooltip": "Unique string-style identifier",
"Prompted": "Prompted",
"Provider URL": "URL провайдера", "Provider URL": "URL провайдера",
"Provider URL - Tooltip": "Unique string-style identifier", "Provider URL - Tooltip": "Unique string-style identifier",
"Region ID": "ID региона", "Region ID": "ID региона",
"Region ID - Tooltip": "Идентификатор региона - Подсказка", "Region ID - Tooltip": "Идентификатор региона - Подсказка",
"Region endpoint for Internet": "Конечная точка региона для Интернета", "Region endpoint for Internet": "Конечная точка региона для Интернета",
"Region endpoint for Intranet": "Конечная точка региона Интранета", "Region endpoint for Intranet": "Конечная точка региона Интранета",
"Required": "Required",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Smtp Connection",
"Test Email": "Test email config", "Test Email": "Test email config",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Email Address",
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Тип", "Type": "Тип",
"Type - Tooltip": "Unique string-style identifier", "Type - Tooltip": "Unique string-style identifier",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip", "UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
"alertType": "тип оповещения", "Visible": "Visible",
"canSignIn": "canSignIn", "alertType": "тип оповещения"
"canSignUp": "canSignUp",
"canUnlink": "canUnlink",
"prompted": "запрошено",
"required": "обязательный",
"visible": "видимый"
}, },
"record": { "record": {
"Is Triggered": "Срабатывает" "Is Triggered": "Срабатывает"

View File

@ -13,6 +13,7 @@
"Sync": "同步" "Sync": "同步"
}, },
"application": { "application": {
"Always": "始终开启",
"Auto signin": "启用自动登录", "Auto signin": "启用自动登录",
"Auto signin - Tooltip": "当Casdoor存在已登录会话时自动采用该会话进行应用端的登录", "Auto signin - Tooltip": "当Casdoor存在已登录会话时自动采用该会话进行应用端的登录",
"Background URL": "背景图URL", "Background URL": "背景图URL",
@ -43,6 +44,7 @@
"Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types", "Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types",
"Left": "居左", "Left": "居左",
"New Application": "添加应用", "New Application": "添加应用",
"None": "关闭",
"Password ON": "开启密码", "Password ON": "开启密码",
"Password ON - Tooltip": "是否允许密码登录", "Password ON - Tooltip": "是否允许密码登录",
"Please select a HTML file": "请选择一个HTML文件", "Please select a HTML file": "请选择一个HTML文件",
@ -53,6 +55,7 @@
"Refresh token expire": "Refresh Token过期", "Refresh token expire": "Refresh Token过期",
"Refresh token expire - Tooltip": "Refresh Token过期时间", "Refresh token expire - Tooltip": "Refresh Token过期时间",
"Right": "居右", "Right": "居右",
"Rule": "规则",
"SAML metadata": "SAML元数据", "SAML metadata": "SAML元数据",
"SAML metadata - Tooltip": "SAML协议的元数据Metadata信息", "SAML metadata - Tooltip": "SAML协议的元数据Metadata信息",
"SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板", "SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
@ -67,10 +70,7 @@
"Token expire": "Access Token过期", "Token expire": "Access Token过期",
"Token expire - Tooltip": "Access Token过期时间", "Token expire - Tooltip": "Access Token过期时间",
"Token format": "Access Token格式", "Token format": "Access Token格式",
"Token format - Tooltip": "Access Token格式", "Token format - Tooltip": "Access Token格式"
"Rule": "规则",
"None": "关闭",
"Always": "始终开启"
}, },
"cert": { "cert": {
"Bit size": "位大小", "Bit size": "位大小",
@ -311,15 +311,16 @@
"Favicon": "图标", "Favicon": "图标",
"Is profile public": "用户个人页公开", "Is profile public": "用户个人页公开",
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页", "Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
"Modify rule": "修改规则",
"New Organization": "添加组织", "New Organization": "添加组织",
"Soft deletion": "软删除", "Soft deletion": "软删除",
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态", "Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
"Tags": "标签集合", "Tags": "标签集合",
"Tags - Tooltip": "可供用户选择的标签的集合", "Tags - Tooltip": "可供用户选择的标签的集合",
"View rule": "查看规则",
"Visible": "是否可见",
"Website URL": "网页地址", "Website URL": "网页地址",
"Website URL - Tooltip": "网页地址", "Website URL - Tooltip": "网页地址"
"modifyRule": "修改规则",
"viewRule": "查看规则"
}, },
"payment": { "payment": {
"Confirm your invoice information": "确认您的发票信息", "Confirm your invoice information": "确认您的发票信息",
@ -452,6 +453,9 @@
"Bucket": "存储桶", "Bucket": "存储桶",
"Bucket - Tooltip": "Bucket名称", "Bucket - Tooltip": "Bucket名称",
"Can not parse Metadata": "无法解析元数据", "Can not parse Metadata": "无法解析元数据",
"Can signin": "可用于登录",
"Can signup": "可用于注册",
"Can unlink": "可解绑定",
"Category": "分类", "Category": "分类",
"Category - Tooltip": "分类", "Category - Tooltip": "分类",
"Channel No.": "Channel No.", "Channel No.": "Channel No.",
@ -491,14 +495,17 @@
"New Provider": "添加提供商", "New Provider": "添加提供商",
"Parse": "Parse", "Parse": "Parse",
"Parse Metadata successfully": "解析元数据成功", "Parse Metadata successfully": "解析元数据成功",
"Path prefix": "路径前缀",
"Port": "端口", "Port": "端口",
"Port - Tooltip": "端口号", "Port - Tooltip": "端口号",
"Prompted": "注册后提醒绑定",
"Provider URL": "提供商URL", "Provider URL": "提供商URL",
"Provider URL - Tooltip": "提供商URL", "Provider URL - Tooltip": "提供商URL",
"Region ID": "地域ID", "Region ID": "地域ID",
"Region ID - Tooltip": "地域ID", "Region ID - Tooltip": "地域ID",
"Region endpoint for Internet": "地域节点 (外网)", "Region endpoint for Internet": "地域节点 (外网)",
"Region endpoint for Intranet": "地域节点 (内网)", "Region endpoint for Intranet": "地域节点 (内网)",
"Required": "是否必填项",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)", "SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS account": "SMS account", "SMS account": "SMS account",
"SMS account - Tooltip": "SMS account - Tooltip", "SMS account - Tooltip": "SMS account - Tooltip",
@ -535,19 +542,15 @@
"Test Connection": "测试SMTP连接", "Test Connection": "测试SMTP连接",
"Test Email": "测试Email配置", "Test Email": "测试Email配置",
"Test Email - Tooltip": "邮箱地址", "Test Email - Tooltip": "邮箱地址",
"The prefix path of the file - Tooltip": "文件的路径前缀 - 工具提示",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - 工具提示", "Token URL - Tooltip": "Token URL - 工具提示",
"Type": "类型", "Type": "类型",
"Type - Tooltip": "类型", "Type - Tooltip": "类型",
"UserInfo URL": "UserInfo URL", "UserInfo URL": "UserInfo URL",
"UserInfo URL - Tooltip": "UserInfo URL - 工具提示", "UserInfo URL - Tooltip": "UserInfo URL - 工具提示",
"alertType": "警报类型", "Visible": "是否可见",
"canSignIn": "可用于登录", "alertType": "警报类型"
"canSignUp": "可用于注册",
"canUnlink": "可解绑定",
"prompted": "注册后提醒绑定",
"required": "是否必填项",
"visible": "是否可见"
}, },
"record": { "record": {
"Is Triggered": "已触发" "Is Triggered": "已触发"