mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-31 08:50:31 +08:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6c76913f71 | ||
![]() |
5a0d1bcb6e | ||
![]() |
37232faa07 | ||
![]() |
4d9c81ef96 | ||
![]() |
b0d87f60ae | ||
![]() |
a5499219d1 | ||
![]() |
6a813a1f8c | ||
![]() |
e4cf244cf8 | ||
![]() |
f5a6415e57 | ||
![]() |
13e871043c | ||
![]() |
a8699d0b87 | ||
![]() |
6621d693de | ||
![]() |
dc3131c683 | ||
![]() |
042a8d0ad6 | ||
![]() |
44abfb3430 | ||
![]() |
53b8424a1f | ||
![]() |
23c2ba3a2b | ||
![]() |
3a9ffedce4 | ||
![]() |
03f005389f | ||
![]() |
69a8346d05 | ||
![]() |
546512a0ea |
@@ -168,3 +168,20 @@ func (c *ApiController) RunSyncer() {
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
func (c *ApiController) TestSyncerDb() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = object.TestSyncerDb(syncer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@@ -164,7 +164,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.SetSession(MfaDestSession, vform.Dest)
|
||||
}
|
||||
|
||||
provider, err = application.GetEmailProvider()
|
||||
provider, err = application.GetEmailProvider(vform.Method)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -210,7 +210,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
vform.CountryCode = mfaProps.CountryCode
|
||||
}
|
||||
|
||||
provider, err = application.GetSmsProvider()
|
||||
provider, err = application.GetSmsProvider(vform.Method)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
2
go.mod
2
go.mod
@@ -59,7 +59,7 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
google.golang.org/api v0.150.0
|
||||
|
12
go.sum
12
go.sum
@@ -2117,8 +2117,9 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -2447,8 +2448,9 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -2465,8 +2467,9 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2486,8 +2489,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@@ -8,12 +8,62 @@
|
||||
"favicon": "",
|
||||
"passwordType": "plain",
|
||||
"passwordSalt": "",
|
||||
"passwordOptions": ["AtLeast6"],
|
||||
"countryCodes": ["US", "GB", "ES", "FR", "DE", "CN", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH", "NL", "PL", "FI", "SE", "UA", "KZ"],
|
||||
"passwordOptions": [
|
||||
"AtLeast6"
|
||||
],
|
||||
"countryCodes": [
|
||||
"US",
|
||||
"GB",
|
||||
"ES",
|
||||
"FR",
|
||||
"DE",
|
||||
"CN",
|
||||
"JP",
|
||||
"KR",
|
||||
"VN",
|
||||
"ID",
|
||||
"SG",
|
||||
"IN",
|
||||
"IT",
|
||||
"MY",
|
||||
"TR",
|
||||
"DZ",
|
||||
"IL",
|
||||
"PH",
|
||||
"NL",
|
||||
"PL",
|
||||
"FI",
|
||||
"SE",
|
||||
"UA",
|
||||
"KZ"
|
||||
],
|
||||
"defaultAvatar": "",
|
||||
"defaultApplication": "",
|
||||
"tags": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||
"languages": [
|
||||
"en",
|
||||
"zh",
|
||||
"es",
|
||||
"fr",
|
||||
"de",
|
||||
"id",
|
||||
"ja",
|
||||
"ko",
|
||||
"ru",
|
||||
"vi",
|
||||
"it",
|
||||
"ms",
|
||||
"tr",
|
||||
"ar",
|
||||
"he",
|
||||
"nl",
|
||||
"pl",
|
||||
"fi",
|
||||
"sv",
|
||||
"uk",
|
||||
"kk",
|
||||
"fa"
|
||||
],
|
||||
"masterPassword": "",
|
||||
"defaultPassword": "",
|
||||
"initScore": 2000,
|
||||
@@ -49,18 +99,18 @@
|
||||
{
|
||||
"name": "Password",
|
||||
"displayName": "Password",
|
||||
"rule": "All",
|
||||
"rule": "All"
|
||||
},
|
||||
{
|
||||
"name": "Verification code",
|
||||
"displayName": "Verification code",
|
||||
"rule": "All",
|
||||
"rule": "All"
|
||||
},
|
||||
{
|
||||
"name": "WebAuthn",
|
||||
"displayName": "WebAuthn",
|
||||
"rule": "None",
|
||||
},
|
||||
"rule": "None"
|
||||
}
|
||||
],
|
||||
"signupItems": [
|
||||
{
|
||||
@@ -72,56 +122,65 @@
|
||||
},
|
||||
{
|
||||
"name": "Username",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Display name",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Password",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Confirm password",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Email",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Phone",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
},
|
||||
{
|
||||
"name": "Agreement",
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
"visible": true,
|
||||
"required": true,
|
||||
"prompted": false,
|
||||
"rule": "None"
|
||||
}
|
||||
],
|
||||
"grantTypes": ["authorization_code", "password", "client_credentials", "token", "id_token", "refresh_token"],
|
||||
"redirectUris": [""],
|
||||
"grantTypes": [
|
||||
"authorization_code",
|
||||
"password",
|
||||
"client_credentials",
|
||||
"token",
|
||||
"id_token",
|
||||
"refresh_token"
|
||||
],
|
||||
"redirectUris": [
|
||||
""
|
||||
],
|
||||
"expireInHours": 168,
|
||||
"failedSigninLimit": 5,
|
||||
"failedSigninFrozenTime": 15
|
||||
@@ -354,71 +413,71 @@
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"owner": "",
|
||||
"name":"",
|
||||
"displayName": "",
|
||||
"manager": "",
|
||||
"contactEmail": "",
|
||||
"type": "",
|
||||
"parent_id": "",
|
||||
"isTopGroup": true,
|
||||
"title": "",
|
||||
"key": "",
|
||||
"children": "",
|
||||
"isEnabled": true
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"manager": "",
|
||||
"contactEmail": "",
|
||||
"type": "",
|
||||
"parent_id": "",
|
||||
"isTopGroup": true,
|
||||
"title": "",
|
||||
"key": "",
|
||||
"children": "",
|
||||
"isEnabled": true
|
||||
}
|
||||
],
|
||||
"adapters": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"table": "",
|
||||
"useSameDb": true,
|
||||
"type": "",
|
||||
"databaseType": "",
|
||||
"database": "",
|
||||
"host": "",
|
||||
"port": 0,
|
||||
"user": "",
|
||||
"password": "",
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"table": "",
|
||||
"useSameDb": true,
|
||||
"type": "",
|
||||
"databaseType": "",
|
||||
"database": "",
|
||||
"host": "",
|
||||
"port": 0,
|
||||
"user": "",
|
||||
"password": ""
|
||||
}
|
||||
],
|
||||
"enforcers": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"model": "",
|
||||
"adapter": "",
|
||||
"enforcer": ""
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"model": "",
|
||||
"adapter": "",
|
||||
"enforcer": ""
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"price": 0,
|
||||
"currency": "",
|
||||
"period": "",
|
||||
"product": "",
|
||||
"paymentProviders": [],
|
||||
"isEnabled": true,
|
||||
"role", ""
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"price": 0,
|
||||
"currency": "",
|
||||
"period": "",
|
||||
"product": "",
|
||||
"paymentProviders": [],
|
||||
"isEnabled": true,
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"pricings": [
|
||||
{
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"plans": [],
|
||||
"isEnabled": true,
|
||||
"trialDuration": 0,
|
||||
"application": "",
|
||||
"owner": "",
|
||||
"name": "",
|
||||
"displayName": "",
|
||||
"description": "",
|
||||
"plans": [],
|
||||
"isEnabled": true,
|
||||
"trialDuration": 0,
|
||||
"application": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -66,6 +66,7 @@ type Application struct {
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
HeaderHtml string `xorm:"mediumtext" json:"headerHtml"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
|
@@ -38,12 +38,38 @@ func (application *Application) GetProviderByCategory(category string) (*Provide
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (application *Application) GetEmailProvider() (*Provider, error) {
|
||||
return application.GetProviderByCategory("Email")
|
||||
func (application *Application) GetProviderByCategoryAndRule(category string, method string) (*Provider, error) {
|
||||
providers, err := GetProviders(application.Organization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
if provider.Category != category {
|
||||
continue
|
||||
}
|
||||
|
||||
m[provider.Name] = provider
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.Rule == method || (providerItem.Rule == "all" || providerItem.Rule == "" || providerItem.Rule == "None") {
|
||||
if provider, ok := m[providerItem.Name]; ok {
|
||||
return provider, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (application *Application) GetSmsProvider() (*Provider, error) {
|
||||
return application.GetProviderByCategory("SMS")
|
||||
func (application *Application) GetEmailProvider(method string) (*Provider, error) {
|
||||
return application.GetProviderByCategoryAndRule("Email", method)
|
||||
}
|
||||
|
||||
func (application *Application) GetSmsProvider(method string) (*Provider, error) {
|
||||
return application.GetProviderByCategoryAndRule("SMS", method)
|
||||
}
|
||||
|
||||
func (application *Application) GetStorageProvider() (*Provider, error) {
|
||||
|
@@ -464,7 +464,9 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
||||
record.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(record)
|
||||
if err != nil {
|
||||
return err
|
||||
if err.Error() != "no columns found to be updated" {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resource := new(Resource)
|
||||
|
@@ -32,8 +32,9 @@ import (
|
||||
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
_ "github.com/lib/pq" // db = postgres
|
||||
"github.com/xorm-io/core"
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/core"
|
||||
"github.com/xorm-io/xorm/names"
|
||||
_ "modernc.org/sqlite" // db = sqlite
|
||||
)
|
||||
|
||||
@@ -98,7 +99,7 @@ func InitAdapter() {
|
||||
}
|
||||
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
tbMapper := names.NewPrefixMapper(names.SnakeMapper{}, tableNamePrefix)
|
||||
ormer.Engine.SetTableMapper(tbMapper)
|
||||
}
|
||||
|
||||
@@ -118,6 +119,7 @@ type Ormer struct {
|
||||
driverName string
|
||||
dataSourceName string
|
||||
dbName string
|
||||
Db *sql.DB
|
||||
Engine *xorm.Engine
|
||||
}
|
||||
|
||||
@@ -127,6 +129,13 @@ func finalizer(a *Ormer) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if a.Db != nil {
|
||||
err = a.Db.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewAdapter is the constructor for Ormer.
|
||||
@@ -148,6 +157,26 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) (*Ormer
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// NewAdapterFromdb is the constructor for Ormer.
|
||||
func NewAdapterFromDb(driverName string, dataSourceName string, dbName string, db *sql.DB) (*Ormer, error) {
|
||||
a := &Ormer{}
|
||||
a.driverName = driverName
|
||||
a.dataSourceName = dataSourceName
|
||||
a.dbName = dbName
|
||||
a.Db = db
|
||||
|
||||
// Open the DB, create it if not existed.
|
||||
err := a.openFromDb(a.Db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call the destructor when the object is released.
|
||||
runtime.SetFinalizer(a, finalizer)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
||||
reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
|
||||
return reg.ReplaceAllString(dataSourceName, "")
|
||||
@@ -226,6 +255,30 @@ func (a *Ormer) open() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Ormer) openFromDb(db *sql.DB) error {
|
||||
dataSourceName := a.dataSourceName + a.dbName
|
||||
if a.driverName != "mysql" {
|
||||
dataSourceName = a.dataSourceName
|
||||
}
|
||||
|
||||
xormDb := core.FromDB(db)
|
||||
|
||||
engine, err := xorm.NewEngineWithDB(a.driverName, dataSourceName, xormDb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.driverName == "postgres" {
|
||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||
if schema != "" {
|
||||
engine.SetSchema(schema)
|
||||
}
|
||||
}
|
||||
|
||||
a.Engine = engine
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Ormer) close() {
|
||||
_ = a.Engine.Close()
|
||||
a.Engine = nil
|
||||
|
@@ -27,7 +27,7 @@ func getCasvisorApplication() *Application {
|
||||
}
|
||||
|
||||
for _, application := range applications {
|
||||
if strings.Contains(strings.ToLower(application.Name), "casvisor") {
|
||||
if strings.Contains(strings.ToLower(application.Name), "casvisor-my") {
|
||||
return application
|
||||
}
|
||||
}
|
||||
|
@@ -39,11 +39,17 @@ type Syncer struct {
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
SslMode string `xorm:"varchar(100)" json:"sslMode"`
|
||||
SshType string `xorm:"varchar(100)" json:"sshType"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(150)" json:"password"`
|
||||
SshHost string `xorm:"varchar(100)" json:"sshHost"`
|
||||
SshPort int `json:"sshPort"`
|
||||
SshUser string `xorm:"varchar(100)" json:"sshUser"`
|
||||
SshPassword string `xorm:"varchar(150)" json:"sshPassword"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||
@@ -279,3 +285,25 @@ func RunSyncer(syncer *Syncer) error {
|
||||
|
||||
return syncer.syncUsers()
|
||||
}
|
||||
|
||||
func TestSyncerDb(syncer Syncer) error {
|
||||
oldSyncer, err := getSyncer(syncer.Owner, syncer.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if syncer.Password == "***" {
|
||||
syncer.Password = oldSyncer.Password
|
||||
}
|
||||
|
||||
err = syncer.initAdapter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syncer.Ormer.Engine.Ping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -15,12 +15,17 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
type OriginalUser = User
|
||||
@@ -124,6 +129,19 @@ func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
||||
return util.GetMd5Hash(s)
|
||||
}
|
||||
|
||||
type dsnConnector struct {
|
||||
dsn string
|
||||
driver driver.Driver
|
||||
}
|
||||
|
||||
func (t dsnConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||
return t.driver.Open(t.dsn)
|
||||
}
|
||||
|
||||
func (t dsnConnector) Driver() driver.Driver {
|
||||
return t.driver
|
||||
}
|
||||
|
||||
func (syncer *Syncer) initAdapter() error {
|
||||
if syncer.Ormer != nil {
|
||||
return nil
|
||||
@@ -142,12 +160,38 @@ func (syncer *Syncer) initAdapter() error {
|
||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
var err error
|
||||
|
||||
if syncer.SshType != "" && (syncer.DatabaseType == "mysql" || syncer.DatabaseType == "postgres" || syncer.DatabaseType == "mssql") {
|
||||
var dial *ssh.Client
|
||||
if syncer.SshType == "password" {
|
||||
dial, err = DialWithPassword(syncer.SshUser, syncer.SshPassword, syncer.SshHost, syncer.SshPort)
|
||||
} else {
|
||||
dial, err = DialWithCert(syncer.SshUser, syncer.Owner+"/"+syncer.Cert, syncer.SshHost, syncer.SshPort)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if syncer.DatabaseType == "mysql" {
|
||||
dataSourceName = fmt.Sprintf("%s:%s@%s(%s:%d)/", syncer.User, syncer.Password, syncer.Owner+syncer.Name, syncer.Host, syncer.Port)
|
||||
mysql.RegisterDialContext(syncer.Owner+syncer.Name, (&ViaSSHDialer{Client: dial, Context: nil}).MysqlDial)
|
||||
} else if syncer.DatabaseType == "postgres" || syncer.DatabaseType == "mssql" {
|
||||
db = sql.OpenDB(dsnConnector{dsn: dataSourceName, driver: &ViaSSHDialer{Client: dial, Context: nil, DatabaseType: syncer.DatabaseType}})
|
||||
}
|
||||
}
|
||||
|
||||
if !isCloudIntranet {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||
}
|
||||
|
||||
var err error
|
||||
syncer.Ormer, err = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||
if db != nil {
|
||||
syncer.Ormer, err = NewAdapterFromDb(syncer.DatabaseType, dataSourceName, syncer.Database, db)
|
||||
} else {
|
||||
syncer.Ormer, err = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -681,7 +681,7 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
||||
// GetPasswordToken
|
||||
// Resource Owner Password Credentials flow
|
||||
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError, error) {
|
||||
user, err := getUser(application.Organization, username)
|
||||
user, err := GetUserByFields(application.Organization, username)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -1021,6 +1021,10 @@ func userChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Users {
|
||||
if u == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
// u = organization/username
|
||||
owner, name := util.GetOwnerAndNameFromId(u)
|
||||
if name == oldName {
|
||||
|
@@ -77,6 +77,12 @@ func GetUserByFields(organization string, field string) (*User, error) {
|
||||
return user, err
|
||||
}
|
||||
|
||||
// check user ID
|
||||
user, err = GetUserByField(organization, "id", field)
|
||||
if user != nil || err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
// check ID card
|
||||
user, err = GetUserByField(organization, "id_card", field)
|
||||
if user != nil || err != nil {
|
||||
|
116
object/viaSSHDialer.go
Normal file
116
object/viaSSHDialer.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
mssql "github.com/denisenkom/go-mssqldb"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type ViaSSHDialer struct {
|
||||
Client *ssh.Client
|
||||
Context *context.Context
|
||||
DatabaseType string
|
||||
}
|
||||
|
||||
func (v *ViaSSHDialer) MysqlDial(ctx context.Context, addr string) (net.Conn, error) {
|
||||
return v.Client.Dial("tcp", addr)
|
||||
}
|
||||
|
||||
func (v *ViaSSHDialer) Open(s string) (_ driver.Conn, err error) {
|
||||
if v.DatabaseType == "mssql" {
|
||||
c, err := mssql.NewConnector(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Dialer = v
|
||||
return c.Connect(context.Background())
|
||||
} else if v.DatabaseType == "postgres" {
|
||||
return pq.DialOpen(v, s)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (v *ViaSSHDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return v.Client.Dial(network, address)
|
||||
}
|
||||
|
||||
func (v *ViaSSHDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
return v.Client.DialContext(ctx, network, addr)
|
||||
}
|
||||
|
||||
func (v *ViaSSHDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
return v.Client.Dial(network, address)
|
||||
}
|
||||
|
||||
func DialWithPassword(SshUser string, SshPassword string, SshHost string, SshPort int) (*ssh.Client, error) {
|
||||
address := fmt.Sprintf("%s:%d", SshHost, SshPort)
|
||||
config := &ssh.ClientConfig{
|
||||
User: SshUser,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(SshPassword),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
return ssh.Dial("tcp", address, config)
|
||||
}
|
||||
|
||||
func DialWithCert(SshUser string, CertId string, SshHost string, SshPort int) (*ssh.Client, error) {
|
||||
address := fmt.Sprintf("%s:%d", SshHost, SshPort)
|
||||
config := &ssh.ClientConfig{
|
||||
User: SshUser,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
cert, err := GetCert(CertId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(cert.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
}
|
||||
return ssh.Dial("tcp", address, config)
|
||||
}
|
||||
|
||||
func DialWithPrivateKey(SshUser string, PrivateKey []byte, SshHost string, SshPort int) (*ssh.Client, error) {
|
||||
address := fmt.Sprintf("%s:%d", SshHost, SshPort)
|
||||
config := &ssh.ClientConfig{
|
||||
User: SshUser,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Auth = []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
}
|
||||
return ssh.Dial("tcp", address, config)
|
||||
}
|
@@ -233,6 +233,7 @@ func initAPI() {
|
||||
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
|
||||
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
|
||||
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||
beego.Router("/api/test-syncer-db", &controllers.ApiController{}, "POST:TestSyncerDb")
|
||||
|
||||
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
|
||||
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
|
||||
|
598
web/src/App.js
598
web/src/App.js
@@ -12,67 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React, {Component} from "react";
|
||||
import React, {Component, Suspense, lazy} from "react";
|
||||
import "./App.less";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Setting from "./Setting";
|
||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||
import {AppstoreTwoTone, BarsOutlined, DeploymentUnitOutlined, DollarTwoTone, DownOutlined, GithubOutlined, HomeTwoTone, InfoCircleFilled, LockTwoTone, LogoutOutlined, SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone, ShareAltOutlined, WalletTwoTone} from "@ant-design/icons";
|
||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, Tooltip} from "antd";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import Dashboard from "./basic/Dashboard";
|
||||
import ShortcutsPage from "./basic/ShortcutsPage";
|
||||
import AppListPage from "./basic/AppListPage";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
import GroupEditPage from "./GroupEdit";
|
||||
import GroupListPage from "./GroupList";
|
||||
import GroupTreePage from "./GroupTreePage";
|
||||
import UserListPage from "./UserListPage";
|
||||
import UserEditPage from "./UserEditPage";
|
||||
import InvitationListPage from "./InvitationListPage";
|
||||
import InvitationEditPage from "./InvitationEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import RoleListPage from "./RoleListPage";
|
||||
import RoleEditPage from "./RoleEditPage";
|
||||
import PermissionListPage from "./PermissionListPage";
|
||||
import PermissionEditPage from "./PermissionEditPage";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import EnforcerEditPage from "./EnforcerEditPage";
|
||||
import EnforcerListPage from "./EnforcerListPage";
|
||||
import SessionListPage from "./SessionListPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
import TokenEditPage from "./TokenEditPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import PricingListPage from "./PricingListPage";
|
||||
import PricingEditPage from "./PricingEditPage";
|
||||
import PlanListPage from "./PlanListPage";
|
||||
import PlanEditPage from "./PlanEditPage";
|
||||
import SubscriptionListPage from "./SubscriptionListPage";
|
||||
import SubscriptionEditPage from "./SubscriptionEditPage";
|
||||
import SystemInfo from "./SystemInfo";
|
||||
import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import MfaSetupPage from "./auth/MfaSetupPage";
|
||||
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
|
||||
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
|
||||
import {Route, Switch, withRouter} from "react-router-dom";
|
||||
import CustomGithubCorner from "./common/CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
|
||||
@@ -80,41 +27,36 @@ import * as Auth from "./auth/Auth";
|
||||
import EntryPage from "./EntryPage";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import AuthCallback from "./auth/AuthCallback";
|
||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||
import SamlCallback from "./auth/SamlCallback";
|
||||
import i18next from "i18next";
|
||||
import {withTranslation} from "react-i18next";
|
||||
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
|
||||
import LanguageSelect from "./common/select/LanguageSelect";
|
||||
import ThemeSelect from "./common/select/ThemeSelect";
|
||||
import OrganizationSelect from "./common/select/OrganizationSelect";
|
||||
import {clearWeb3AuthToken} from "./auth/Web3Auth";
|
||||
import AccountAvatar from "./account/AccountAvatar";
|
||||
import OpenTour from "./common/OpenTour";
|
||||
|
||||
const {Header, Footer, Content} = Layout;
|
||||
const ManagementPage = lazy(() => import("./ManagementPage"));
|
||||
const {Footer, Content} = Layout;
|
||||
|
||||
import {setTwoToneColor} from "@ant-design/icons";
|
||||
import RecordListPage from "./RecordListPage";
|
||||
|
||||
setTwoToneColor("rgb(87,52,211)");
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let storageThemeAlgorithm = [];
|
||||
try {
|
||||
storageThemeAlgorithm = localStorage.getItem("themeAlgorithm") ? JSON.parse(localStorage.getItem("themeAlgorithm")) : ["default"];
|
||||
} catch {
|
||||
storageThemeAlgorithm = ["default"];
|
||||
}
|
||||
this.state = {
|
||||
classes: props,
|
||||
selectedMenuKey: 0,
|
||||
account: undefined,
|
||||
uri: null,
|
||||
menuVisible: false,
|
||||
themeAlgorithm: ["default"],
|
||||
themeAlgorithm: storageThemeAlgorithm,
|
||||
themeData: Conf.ThemeDefault,
|
||||
logo: this.getLogo(Setting.getAlgorithmNames(Conf.ThemeDefault)),
|
||||
logo: this.getLogo(storageThemeAlgorithm),
|
||||
requiredEnableMfa: false,
|
||||
isAiAssistantOpen: false,
|
||||
};
|
||||
|
||||
Setting.initServerUrl();
|
||||
Auth.initAuthWithConfig({
|
||||
serverUrl: Setting.ServerUrl,
|
||||
@@ -228,6 +170,19 @@ class App extends Component {
|
||||
});
|
||||
|
||||
if (initThemeAlgorithm) {
|
||||
if (localStorage.getItem("themeAlgorithm")) {
|
||||
let storageThemeAlgorithm = [];
|
||||
try {
|
||||
storageThemeAlgorithm = JSON.parse(localStorage.getItem("themeAlgorithm"));
|
||||
} catch {
|
||||
storageThemeAlgorithm = ["default"];
|
||||
}
|
||||
this.setState({
|
||||
logo: this.getLogo(storageThemeAlgorithm),
|
||||
themeAlgorithm: storageThemeAlgorithm,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
logo: this.getLogo(Setting.getAlgorithmNames(theme)),
|
||||
themeAlgorithm: Setting.getAlgorithmNames(theme),
|
||||
@@ -274,387 +229,12 @@ class App extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.setState({
|
||||
expired: false,
|
||||
submitted: false,
|
||||
});
|
||||
|
||||
AuthBackend.logout()
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const owner = this.state.account.owner;
|
||||
this.setState({
|
||||
account: null,
|
||||
themeAlgorithm: ["default"],
|
||||
});
|
||||
clearWeb3AuthToken();
|
||||
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||
const redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
} else if (owner !== "built-in") {
|
||||
Setting.goToLink(`${window.location.origin}/login/${owner}`);
|
||||
} else {
|
||||
Setting.goToLinkSoft(this, "/");
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onUpdateAccount(account) {
|
||||
this.setState({
|
||||
account: account,
|
||||
});
|
||||
}
|
||||
|
||||
renderAvatar() {
|
||||
if (this.state.account.avatar === "") {
|
||||
return (
|
||||
<Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large">
|
||||
{Setting.getShortName(this.state.account.name)}
|
||||
</Avatar>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large"
|
||||
icon={<AccountAvatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size={40} />}
|
||||
>
|
||||
{Setting.getShortName(this.state.account.name)}
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderRightDropdown() {
|
||||
const items = [];
|
||||
if (this.state.requiredEnableMfa === false) {
|
||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||
"/account"
|
||||
));
|
||||
}
|
||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||
"/logout"));
|
||||
|
||||
const onClick = (e) => {
|
||||
if (e.key === "/account") {
|
||||
this.props.history.push("/account");
|
||||
} else if (e.key === "/subscription") {
|
||||
this.props.history.push("/subscription");
|
||||
} else if (e.key === "/logout") {
|
||||
this.logout();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown key="/rightDropDown" menu={{items, onClick}} >
|
||||
<div className="rightDropDown">
|
||||
{
|
||||
this.renderAvatar()
|
||||
}
|
||||
|
||||
|
||||
{Setting.isMobile() ? null : Setting.getShortText(Setting.getNameAtLeast(this.state.account.displayName), 30)} <DownOutlined />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccountMenu() {
|
||||
if (this.state.account === undefined) {
|
||||
return null;
|
||||
} else if (this.state.account === null) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LanguageSelect />
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.renderRightDropdown()}
|
||||
<ThemeSelect
|
||||
themeAlgorithm={this.state.themeAlgorithm}
|
||||
onChange={(nextThemeAlgorithm) => {
|
||||
this.setState({
|
||||
themeAlgorithm: nextThemeAlgorithm,
|
||||
logo: this.getLogo(nextThemeAlgorithm),
|
||||
});
|
||||
}} />
|
||||
<LanguageSelect languages={this.state.account.organization.languages} />
|
||||
<Tooltip title="Click to open AI assitant">
|
||||
<div className="select-box" onClick={() => {
|
||||
this.setState({
|
||||
isAiAssistantOpen: true,
|
||||
});
|
||||
}}>
|
||||
<DeploymentUnitOutlined style={{fontSize: "24px", color: "rgb(77,77,77)"}} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<OpenTour />
|
||||
{Setting.isAdminUser(this.state.account) && !Setting.isMobile() && (this.state.uri.indexOf("/trees") === -1) &&
|
||||
<OrganizationSelect
|
||||
initValue={Setting.getOrganization()}
|
||||
withAll={true}
|
||||
style={{marginRight: "20px", width: "180px", display: "flex"}}
|
||||
onChange={(value) => {
|
||||
Setting.setOrganization(value);
|
||||
}}
|
||||
className="select-box"
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getMenuItems() {
|
||||
const res = [];
|
||||
|
||||
if (this.state.account === null || this.state.account === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone />, [
|
||||
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
|
||||
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
|
||||
Setting.getItem(<Link to="/apps">{i18next.t("general:Apps")}</Link>, "/apps"),
|
||||
].filter(item => {
|
||||
return Setting.isLocalAdminUser(this.state.account);
|
||||
})));
|
||||
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
if (Conf.ShowGithubCorner) {
|
||||
res.push(Setting.getItem(<a href={"https://casdoor.com"}>
|
||||
<span style={{fontWeight: "bold", backgroundColor: "rgba(87,52,211,0.4)", marginTop: "12px", paddingLeft: "5px", paddingRight: "5px", display: "flex", alignItems: "center", height: "40px", borderRadius: "5px"}}>
|
||||
🚀 SaaS Hosting 🔥
|
||||
</span>
|
||||
</a>, "#"));
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone />, [
|
||||
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
|
||||
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
|
||||
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
|
||||
Setting.getItem(<Link to="/invitations">{i18next.t("general:Invitations")}</Link>, "/invitations"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
|
||||
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
|
||||
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
|
||||
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
|
||||
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone />, [
|
||||
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
|
||||
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
|
||||
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
|
||||
Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>, "/adapters"),
|
||||
Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>, "/enforcers"),
|
||||
].filter(item => {
|
||||
if (!Setting.isLocalAdminUser(this.state.account) && ["/models", "/adapters", "/enforcers"].includes(item.key)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone />, [
|
||||
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
|
||||
Conf.CasvisorUrl ? Setting.getItem(<a target="_blank" rel="noreferrer" href={Conf.CasvisorUrl}>{i18next.t("general:Records")}</a>, "/records")
|
||||
: Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
|
||||
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone />, [
|
||||
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
|
||||
Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>, "/payments"),
|
||||
Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>, "/plans"),
|
||||
Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>, "/pricings"),
|
||||
Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, "/subscriptions"),
|
||||
]));
|
||||
|
||||
if (Setting.isAdminUser(this.state.account)) {
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
|
||||
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
|
||||
} else {
|
||||
res.push(Setting.getItem(<Link style={{color: "black"}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone />, [
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks")]));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
renderLoginIfNotLoggedIn(component) {
|
||||
if (this.state.account === null) {
|
||||
sessionStorage.setItem("from", window.location.pathname);
|
||||
return <Redirect to="/login" />;
|
||||
} else if (this.state.account === undefined) {
|
||||
return null;
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
renderRouter() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<Dashboard account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/apps" render={(props) => this.renderLoginIfNotLoggedIn(<AppListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/shortcuts" render={(props) => this.renderLoginIfNotLoggedIn(<ShortcutsPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupTreePage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/groups" render={(props) => this.renderLoginIfNotLoggedIn(<GroupListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/groups/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||
<Route exact path="/invitations" render={(props) => this.renderLoginIfNotLoggedIn(<InvitationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/invitations/:organizationName/:invitationName" render={(props) => this.renderLoginIfNotLoggedIn(<InvitationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.setState({
|
||||
menuVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
showMenu = () => {
|
||||
this.setState({
|
||||
menuVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
isWithoutCard() {
|
||||
return Setting.isMobile() || window.location.pathname.startsWith("/trees");
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const onClick = ({key}) => {
|
||||
if (key !== "/swagger" && key !== "/records") {
|
||||
if (this.state.requiredEnableMfa) {
|
||||
Setting.showMessage("info", "Please enable MFA first!");
|
||||
} else {
|
||||
this.props.history.push(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
const menuStyleRight = Setting.isAdminUser(this.state.account) && !Setting.isMobile() ? "calc(180px + 280px)" : "280px";
|
||||
return (
|
||||
<Layout id="parent-area">
|
||||
<EnableMfaNotification account={this.state.account} />
|
||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}} >
|
||||
{Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||
</Link>
|
||||
)}
|
||||
{this.state.requiredEnableMfa || (Setting.isMobile() ?
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
|
||||
<Menu
|
||||
items={this.getMenuItems()}
|
||||
mode={"inline"}
|
||||
selectedKeys={[this.state.selectedMenuKey]}
|
||||
style={{lineHeight: "64px"}}
|
||||
onClick={this.onClose}
|
||||
>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment> :
|
||||
<Menu
|
||||
onClick={onClick}
|
||||
items={this.getMenuItems()}
|
||||
mode={"horizontal"}
|
||||
selectedKeys={[this.state.selectedMenuKey]}
|
||||
style={{position: "absolute", left: "145px", right: menuStyleRight}}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
this.renderAccountMenu()
|
||||
}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{this.isWithoutCard() ?
|
||||
this.renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{this.renderRouter()}
|
||||
</Card>
|
||||
}
|
||||
</Content>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
{
|
||||
this.renderAiAssistant()
|
||||
}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -721,49 +301,63 @@ class App extends Component {
|
||||
window.location.pathname.startsWith("/prompt") ||
|
||||
window.location.pathname.startsWith("/result") ||
|
||||
window.location.pathname.startsWith("/cas") ||
|
||||
window.location.pathname.startsWith("/auto-signup") ||
|
||||
window.location.pathname.startsWith("/select-plan") ||
|
||||
window.location.pathname.startsWith("/buy-plan") ||
|
||||
window.location.pathname.startsWith("/qrcode") ;
|
||||
}
|
||||
|
||||
onClick = ({key}) => {
|
||||
if (key !== "/swagger" && key !== "/records") {
|
||||
if (this.state.requiredEnableMfa) {
|
||||
Setting.showMessage("info", "Please enable MFA first!");
|
||||
} else {
|
||||
this.props.history.push(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderPage() {
|
||||
if (this.isDoorPages()) {
|
||||
return (
|
||||
<Layout id="parent-area">
|
||||
<Content style={{display: "flex", justifyContent: "center"}}>
|
||||
{
|
||||
this.isEntryPages() ?
|
||||
<EntryPage
|
||||
account={this.state.account}
|
||||
theme={this.state.themeData}
|
||||
onLoginSuccess={(redirectUrl) => {
|
||||
if (redirectUrl) {
|
||||
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
||||
}
|
||||
this.getAccount();
|
||||
}}
|
||||
onUpdateAccount={(account) => this.onUpdateAccount(account)}
|
||||
updataThemeData={this.setTheme}
|
||||
/> :
|
||||
<Switch>
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
}
|
||||
</Content>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
{
|
||||
this.renderAiAssistant()
|
||||
}
|
||||
</Layout>
|
||||
<ConfigProvider theme={{
|
||||
algorithm: Setting.getAlgorithm(["default"]),
|
||||
}}>
|
||||
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||
<Layout id="parent-area">
|
||||
<Content style={{display: "flex", justifyContent: "center"}}>
|
||||
{
|
||||
this.isEntryPages() ?
|
||||
<EntryPage
|
||||
account={this.state.account}
|
||||
theme={this.state.themeData}
|
||||
onLoginSuccess={(redirectUrl) => {
|
||||
if (redirectUrl) {
|
||||
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
||||
}
|
||||
this.getAccount();
|
||||
}}
|
||||
onUpdateAccount={(account) => this.onUpdateAccount(account)}
|
||||
updataThemeData={this.setTheme}
|
||||
/> :
|
||||
<Switch>
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
}
|
||||
</Content>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
{
|
||||
this.renderAiAssistant()
|
||||
}
|
||||
</Layout>
|
||||
</StyleProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* { */}
|
||||
@@ -772,7 +366,49 @@ class App extends Component {
|
||||
<FloatButton.BackTop />
|
||||
<CustomGithubCorner />
|
||||
{
|
||||
this.renderContent()
|
||||
<Suspense fallback={<div>loading</div>}>
|
||||
<Layout id="parent-area">
|
||||
<ManagementPage
|
||||
account={this.state.account}
|
||||
uri={this.state.uri}
|
||||
themeData={this.state.themeData}
|
||||
themeAlgorithm={this.state.themeAlgorithm}
|
||||
selectedMenuKey={this.state.selectedMenuKey}
|
||||
requiredEnableMfa={this.state.requiredEnableMfa}
|
||||
menuVisible={this.state.menuVisible}
|
||||
logo={this.state.logo}
|
||||
onChangeTheme={this.setTheme}
|
||||
onClick = {this.onClick}
|
||||
onfinish={() => {
|
||||
this.setState({requiredEnableMfa: false});
|
||||
}}
|
||||
openAiAssistant={() => {
|
||||
this.setState({
|
||||
isAiAssistantOpen: true,
|
||||
});
|
||||
}}
|
||||
setLogoAndThemeAlgorithm={(nextThemeAlgorithm) => {
|
||||
this.setState({
|
||||
themeAlgorithm: nextThemeAlgorithm,
|
||||
logo: this.getLogo(nextThemeAlgorithm),
|
||||
});
|
||||
localStorage.setItem("themeAlgorithm", JSON.stringify(nextThemeAlgorithm));
|
||||
}}
|
||||
setLogoutState={() => {
|
||||
this.setState({
|
||||
account: null,
|
||||
themeAlgorithm: ["default"],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{
|
||||
this.renderFooter()
|
||||
}
|
||||
{
|
||||
this.renderAiAssistant()
|
||||
}
|
||||
</Layout>
|
||||
</Suspense>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@@ -54,7 +54,7 @@ img {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5 !important;
|
||||
background-color: #f5f5f5a5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ img {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: black;
|
||||
background-color: #f5f5f5a5;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -865,6 +865,28 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Header HTML"), i18next.t("application:Header HTML - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Popover placement="right" content={
|
||||
<div style={{width: "900px", height: "300px"}} >
|
||||
<CodeMirror
|
||||
value={this.state.application.headerHtml}
|
||||
options={{mode: "htmlmixed", theme: "material-darker"}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
this.updateApplicationField("headerHtml", value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
} title={i18next.t("application:Header HTML - Edit")} trigger="click">
|
||||
<Input value={this.state.application.headerHtml} style={{marginBottom: "10px"}} onChange={e => {
|
||||
this.updateApplicationField("headerHtml", e.target.value);
|
||||
}} />
|
||||
</Popover>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@@ -32,6 +32,7 @@ import {authConfig} from "./auth/Auth";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import QrCodePage from "./QrCodePage";
|
||||
import CustomHead from "./basic/CustomHead";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -66,7 +67,6 @@ class EntryPage extends React.Component {
|
||||
this.setState({
|
||||
application: application,
|
||||
});
|
||||
|
||||
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
|
||||
this.props.updataThemeData(themeData);
|
||||
};
|
||||
@@ -82,7 +82,6 @@ class EntryPage extends React.Component {
|
||||
Setting.showMessage("error", res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const application = res.data;
|
||||
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
|
||||
this.props.updataThemeData(themeData);
|
||||
@@ -90,33 +89,35 @@ class EntryPage extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="loginBackground"
|
||||
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||
style={{margin: "0 auto"}} />
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
|
||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
||||
<div className="loginBackground"
|
||||
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||
style={{margin: "0 auto"}} />
|
||||
<Switch>
|
||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />);}} />
|
||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
443
web/src/ManagementPage.js
Normal file
443
web/src/ManagementPage.js
Normal file
@@ -0,0 +1,443 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "./Setting";
|
||||
import {Avatar, Button, Card, Drawer, Dropdown, Menu, Result, Tooltip} from "antd";
|
||||
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import React, {useState} from "react";
|
||||
import i18next from "i18next";
|
||||
import {
|
||||
AppstoreTwoTone,
|
||||
BarsOutlined, DeploymentUnitOutlined, DollarTwoTone, DownOutlined,
|
||||
HomeTwoTone,
|
||||
LockTwoTone, LogoutOutlined,
|
||||
SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone,
|
||||
WalletTwoTone
|
||||
} from "@ant-design/icons";
|
||||
import Dashboard from "./basic/Dashboard";
|
||||
import AppListPage from "./basic/AppListPage";
|
||||
import ShortcutsPage from "./basic/ShortcutsPage";
|
||||
import AccountPage from "./account/AccountPage";
|
||||
import OrganizationListPage from "./OrganizationListPage";
|
||||
import OrganizationEditPage from "./OrganizationEditPage";
|
||||
import UserListPage from "./UserListPage";
|
||||
import GroupTreePage from "./GroupTreePage";
|
||||
import GroupListPage from "./GroupList";
|
||||
import GroupEditPage from "./GroupEdit";
|
||||
import UserEditPage from "./UserEditPage";
|
||||
import InvitationListPage from "./InvitationListPage";
|
||||
import InvitationEditPage from "./InvitationEditPage";
|
||||
import ApplicationListPage from "./ApplicationListPage";
|
||||
import ApplicationEditPage from "./ApplicationEditPage";
|
||||
import ProviderListPage from "./ProviderListPage";
|
||||
import ProviderEditPage from "./ProviderEditPage";
|
||||
import RecordListPage from "./RecordListPage";
|
||||
import ResourceListPage from "./ResourceListPage";
|
||||
import CertListPage from "./CertListPage";
|
||||
import CertEditPage from "./CertEditPage";
|
||||
import RoleListPage from "./RoleListPage";
|
||||
import RoleEditPage from "./RoleEditPage";
|
||||
import PermissionListPage from "./PermissionListPage";
|
||||
import PermissionEditPage from "./PermissionEditPage";
|
||||
import ModelListPage from "./ModelListPage";
|
||||
import ModelEditPage from "./ModelEditPage";
|
||||
import AdapterListPage from "./AdapterListPage";
|
||||
import AdapterEditPage from "./AdapterEditPage";
|
||||
import EnforcerListPage from "./EnforcerListPage";
|
||||
import EnforcerEditPage from "./EnforcerEditPage";
|
||||
import SessionListPage from "./SessionListPage";
|
||||
import TokenListPage from "./TokenListPage";
|
||||
import TokenEditPage from "./TokenEditPage";
|
||||
import ProductListPage from "./ProductListPage";
|
||||
import ProductEditPage from "./ProductEditPage";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
import PaymentEditPage from "./PaymentEditPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import PlanListPage from "./PlanListPage";
|
||||
import PlanEditPage from "./PlanEditPage";
|
||||
import PricingListPage from "./PricingListPage";
|
||||
import PricingEditPage from "./PricingEditPage";
|
||||
import SubscriptionListPage from "./SubscriptionListPage";
|
||||
import SubscriptionEditPage from "./SubscriptionEditPage";
|
||||
import SystemInfo from "./SystemInfo";
|
||||
import SyncerListPage from "./SyncerListPage";
|
||||
import SyncerEditPage from "./SyncerEditPage";
|
||||
import WebhookListPage from "./WebhookListPage";
|
||||
import WebhookEditPage from "./WebhookEditPage";
|
||||
import LdapEditPage from "./LdapEditPage";
|
||||
import LdapSyncPage from "./LdapSyncPage";
|
||||
import MfaSetupPage from "./auth/MfaSetupPage";
|
||||
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
|
||||
import * as Conf from "./Conf";
|
||||
import LanguageSelect from "./common/select/LanguageSelect";
|
||||
import ThemeSelect from "./common/select/ThemeSelect";
|
||||
import OpenTour from "./common/OpenTour";
|
||||
import OrganizationSelect from "./common/select/OrganizationSelect";
|
||||
import AccountAvatar from "./account/AccountAvatar";
|
||||
import {Content, Header} from "antd/es/layout/layout";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import {clearWeb3AuthToken} from "./auth/Web3Auth";
|
||||
|
||||
function ManagementPage(props) {
|
||||
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
|
||||
function logout() {
|
||||
AuthBackend.logout()
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const owner = props.account.owner;
|
||||
props.setLogoutState();
|
||||
clearWeb3AuthToken();
|
||||
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||
const redirectUri = res.data2;
|
||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||
Setting.goToLink(redirectUri);
|
||||
} else if (owner !== "built-in") {
|
||||
Setting.goToLink(`${window.location.origin}/login/${owner}`);
|
||||
} else {
|
||||
Setting.goToLinkSoft({props}, "/");
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvatar() {
|
||||
if (props.account.avatar === "") {
|
||||
return (
|
||||
<Avatar style={{backgroundColor: Setting.getAvatarColor(props.account.name), verticalAlign: "middle"}} size="large">
|
||||
{Setting.getShortName(props.account.name)}
|
||||
</Avatar>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Avatar src={props.account.avatar} style={{verticalAlign: "middle"}} size="large"
|
||||
icon={<AccountAvatar src={props.account.avatar} style={{verticalAlign: "middle"}} size={40} />}
|
||||
>
|
||||
{Setting.getShortName(props.account.name)}
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderRightDropdown() {
|
||||
const items = [];
|
||||
if (props.requiredEnableMfa === false) {
|
||||
items.push(Setting.getItem(<><SettingOutlined /> {i18next.t("account:My Account")}</>,
|
||||
"/account"
|
||||
));
|
||||
}
|
||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||
"/logout"));
|
||||
|
||||
const onClick = (e) => {
|
||||
if (e.key === "/account") {
|
||||
props.history.push("/account");
|
||||
} else if (e.key === "/subscription") {
|
||||
props.history.push("/subscription");
|
||||
} else if (e.key === "/logout") {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown key="/rightDropDown" menu={{items, onClick}} >
|
||||
<div className="rightDropDown">
|
||||
{
|
||||
renderAvatar()
|
||||
}
|
||||
|
||||
|
||||
{Setting.isMobile() ? null : Setting.getShortText(Setting.getNameAtLeast(props.account.displayName), 30)} <DownOutlined />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function renderAccountMenu() {
|
||||
if (props.account === undefined) {
|
||||
return null;
|
||||
} else if (props.account === null) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LanguageSelect />
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{renderRightDropdown()}
|
||||
<ThemeSelect
|
||||
themeAlgorithm={props.themeAlgorithm}
|
||||
onChange={props.setLogoAndThemeAlgorithm} />
|
||||
<LanguageSelect languages={props.account.organization.languages} />
|
||||
<Tooltip title="Click to open AI assitant">
|
||||
<div className="select-box" onClick={props.openAiAssistant}>
|
||||
<DeploymentUnitOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<OpenTour />
|
||||
{Setting.isAdminUser(props.account) && !Setting.isMobile() && (props.uri.indexOf("/trees") === -1) &&
|
||||
<OrganizationSelect
|
||||
initValue={Setting.getOrganization()}
|
||||
withAll={true}
|
||||
style={{marginRight: "20px", width: "180px", display: "flex"}}
|
||||
onChange={(value) => {
|
||||
Setting.setOrganization(value);
|
||||
}}
|
||||
className="select-box"
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getMenuItems() {
|
||||
const res = [];
|
||||
|
||||
if (props.account === null || props.account === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const textColor = props.themeAlgorithm.includes("dark") ? "white" : "black";
|
||||
const twoToneColor = props.themeData.colorPrimary;
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
|
||||
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
|
||||
Setting.getItem(<Link to="/apps">{i18next.t("general:Apps")}</Link>, "/apps"),
|
||||
].filter(item => {
|
||||
return Setting.isLocalAdminUser(props.account);
|
||||
})));
|
||||
|
||||
if (Setting.isLocalAdminUser(props.account)) {
|
||||
if (Conf.ShowGithubCorner) {
|
||||
res.push(Setting.getItem(<a href={"https://casdoor.com"}>
|
||||
<span style={{fontWeight: "bold", backgroundColor: "rgba(87,52,211,0.4)", marginTop: "12px", paddingLeft: "5px", paddingRight: "5px", display: "flex", alignItems: "center", height: "40px", borderRadius: "5px"}}>
|
||||
🚀 SaaS Hosting 🔥
|
||||
</span>
|
||||
</a>, "#"));
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
|
||||
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
|
||||
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
|
||||
Setting.getItem(<Link to="/invitations">{i18next.t("general:Invitations")}</Link>, "/invitations"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
|
||||
Setting.getItem(<Link to="/providers">{i18next.t("general:Providers")}</Link>, "/providers"),
|
||||
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
|
||||
Setting.getItem(<Link to="/certs">{i18next.t("general:Certs")}</Link>, "/certs"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
|
||||
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
|
||||
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
|
||||
Setting.getItem(<Link to="/adapters">{i18next.t("general:Adapters")}</Link>, "/adapters"),
|
||||
Setting.getItem(<Link to="/enforcers">{i18next.t("general:Enforcers")}</Link>, "/enforcers"),
|
||||
].filter(item => {
|
||||
if (!Setting.isLocalAdminUser(props.account) && ["/models", "/adapters", "/enforcers"].includes(item.key)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
|
||||
Conf.CasvisorUrl ? Setting.getItem(<a target="_blank" rel="noreferrer" href={Conf.CasvisorUrl}>{i18next.t("general:Records")}</a>, "/records")
|
||||
: Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
|
||||
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
|
||||
Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>, "/payments"),
|
||||
Setting.getItem(<Link to="/plans">{i18next.t("general:Plans")}</Link>, "/plans"),
|
||||
Setting.getItem(<Link to="/pricings">{i18next.t("general:Pricings")}</Link>, "/pricings"),
|
||||
Setting.getItem(<Link to="/subscriptions">{i18next.t("general:Subscriptions")}</Link>, "/subscriptions"),
|
||||
]));
|
||||
|
||||
if (Setting.isAdminUser(props.account)) {
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
|
||||
} else {
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks")]));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function renderLoginIfNotLoggedIn(component) {
|
||||
if (props.account === null) {
|
||||
sessionStorage.setItem("from", window.location.pathname);
|
||||
return <Redirect to="/login" />;
|
||||
} else if (props.account === undefined) {
|
||||
return null;
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
function renderRouter() {
|
||||
const account = props.account;
|
||||
const onChangeTheme = props.onChangeTheme;
|
||||
const onfinish = props.onfinish;
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/" render={(props) => renderLoginIfNotLoggedIn(<Dashboard account={account} {...props} />)} />
|
||||
<Route exact path="/apps" render={(props) => renderLoginIfNotLoggedIn(<AppListPage account={account} {...props} />)} />
|
||||
<Route exact path="/shortcuts" render={(props) => renderLoginIfNotLoggedIn(<ShortcutsPage account={account} {...props} />)} />
|
||||
<Route exact path="/account" render={(props) => renderLoginIfNotLoggedIn(<AccountPage account={account} {...props} />)} />
|
||||
<Route exact path="/organizations" render={(props) => renderLoginIfNotLoggedIn(<OrganizationListPage account={account} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName" render={(props) => renderLoginIfNotLoggedIn(<OrganizationEditPage account={account} onChangeTheme={onChangeTheme} {...props} />)} />
|
||||
<Route exact path="/organizations/:organizationName/users" render={(props) => renderLoginIfNotLoggedIn(<UserListPage account={account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName" render={(props) => renderLoginIfNotLoggedIn(<GroupTreePage account={account} {...props} />)} />
|
||||
<Route exact path="/trees/:organizationName/:groupName" render={(props) => renderLoginIfNotLoggedIn(<GroupTreePage account={account} {...props} />)} />
|
||||
<Route exact path="/groups" render={(props) => renderLoginIfNotLoggedIn(<GroupListPage account={account} {...props} />)} />
|
||||
<Route exact path="/groups/:organizationName/:groupName" render={(props) => renderLoginIfNotLoggedIn(<GroupEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/users" render={(props) => renderLoginIfNotLoggedIn(<UserListPage account={account} {...props} />)} />
|
||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={account} {...props} />} />
|
||||
<Route exact path="/invitations" render={(props) => renderLoginIfNotLoggedIn(<InvitationListPage account={account} {...props} />)} />
|
||||
<Route exact path="/invitations/:organizationName/:invitationName" render={(props) => renderLoginIfNotLoggedIn(<InvitationEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => renderLoginIfNotLoggedIn(<ApplicationListPage account={account} {...props} />)} />
|
||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => renderLoginIfNotLoggedIn(<ApplicationEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => renderLoginIfNotLoggedIn(<ProviderListPage account={account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => renderLoginIfNotLoggedIn(<ProviderEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/records" render={(props) => renderLoginIfNotLoggedIn(<RecordListPage account={account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => renderLoginIfNotLoggedIn(<ResourceListPage account={account} {...props} />)} />
|
||||
<Route exact path="/certs" render={(props) => renderLoginIfNotLoggedIn(<CertListPage account={account} {...props} />)} />
|
||||
<Route exact path="/certs/:organizationName/:certName" render={(props) => renderLoginIfNotLoggedIn(<CertEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/roles" render={(props) => renderLoginIfNotLoggedIn(<RoleListPage account={account} {...props} />)} />
|
||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => renderLoginIfNotLoggedIn(<RoleEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/permissions" render={(props) => renderLoginIfNotLoggedIn(<PermissionListPage account={account} {...props} />)} />
|
||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => renderLoginIfNotLoggedIn(<PermissionEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/models" render={(props) => renderLoginIfNotLoggedIn(<ModelListPage account={account} {...props} />)} />
|
||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => renderLoginIfNotLoggedIn(<ModelEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/adapters" render={(props) => renderLoginIfNotLoggedIn(<AdapterListPage account={account} {...props} />)} />
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => renderLoginIfNotLoggedIn(<AdapterEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/enforcers" render={(props) => renderLoginIfNotLoggedIn(<EnforcerListPage account={account} {...props} />)} />
|
||||
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => renderLoginIfNotLoggedIn(<EnforcerEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/sessions" render={(props) => renderLoginIfNotLoggedIn(<SessionListPage account={account} {...props} />)} />
|
||||
<Route exact path="/tokens" render={(props) => renderLoginIfNotLoggedIn(<TokenListPage account={account} {...props} />)} />
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => renderLoginIfNotLoggedIn(<TokenEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/products" render={(props) => renderLoginIfNotLoggedIn(<ProductListPage account={account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName" render={(props) => renderLoginIfNotLoggedIn(<ProductEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/products/:organizationName/:productName/buy" render={(props) => renderLoginIfNotLoggedIn(<ProductBuyPage account={account} {...props} />)} />
|
||||
<Route exact path="/payments" render={(props) => renderLoginIfNotLoggedIn(<PaymentListPage account={account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName" render={(props) => renderLoginIfNotLoggedIn(<PaymentEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => renderLoginIfNotLoggedIn(<PaymentResultPage account={account} {...props} />)} />
|
||||
<Route exact path="/plans" render={(props) => renderLoginIfNotLoggedIn(<PlanListPage account={account} {...props} />)} />
|
||||
<Route exact path="/plans/:organizationName/:planName" render={(props) => renderLoginIfNotLoggedIn(<PlanEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/pricings" render={(props) => renderLoginIfNotLoggedIn(<PricingListPage account={account} {...props} />)} />
|
||||
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => renderLoginIfNotLoggedIn(<PricingEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/subscriptions" render={(props) => renderLoginIfNotLoggedIn(<SubscriptionListPage account={account} {...props} />)} />
|
||||
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => renderLoginIfNotLoggedIn(<SubscriptionEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/sysinfo" render={(props) => renderLoginIfNotLoggedIn(<SystemInfo account={account} {...props} />)} />
|
||||
<Route exact path="/syncers" render={(props) => renderLoginIfNotLoggedIn(<SyncerListPage account={account} {...props} />)} />
|
||||
<Route exact path="/syncers/:syncerName" render={(props) => renderLoginIfNotLoggedIn(<SyncerEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/webhooks" render={(props) => renderLoginIfNotLoggedIn(<WebhookListPage account={account} {...props} />)} />
|
||||
<Route exact path="/webhooks/:webhookName" render={(props) => renderLoginIfNotLoggedIn(<WebhookEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => renderLoginIfNotLoggedIn(<LdapEditPage account={account} {...props} />)} />
|
||||
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => renderLoginIfNotLoggedIn(<LdapSyncPage account={account} {...props} />)} />
|
||||
<Route exact path="/mfa/setup" render={(props) => renderLoginIfNotLoggedIn(<MfaSetupPage account={account} onfinish={onfinish} {...props} />)} />
|
||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
function isWithoutCard() {
|
||||
return Setting.isMobile() || window.location.pathname.startsWith("/trees");
|
||||
}
|
||||
|
||||
const menuStyleRight = Setting.isAdminUser(props.account) && !Setting.isMobile() ? "calc(180px + 280px)" : "280px";
|
||||
|
||||
const onClose = () => {
|
||||
setMenuVisible(false);
|
||||
};
|
||||
|
||||
const showMenu = () => {
|
||||
setMenuVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EnableMfaNotification account={props.account} />
|
||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}} >
|
||||
{Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" style={{background: `url(${props.logo})`}} />
|
||||
</Link>
|
||||
)}
|
||||
{props.requiredEnableMfa || (Setting.isMobile() ?
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" visible={menuVisible} onClose={onClose}>
|
||||
<Menu
|
||||
items={getMenuItems()}
|
||||
mode={"inline"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{lineHeight: "64px"}}
|
||||
onClick={onClose}
|
||||
>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment> :
|
||||
<Menu
|
||||
onClick={onClose}
|
||||
items={getMenuItems()}
|
||||
mode={"horizontal"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{position: "absolute", left: "145px", right: menuStyleRight, backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
renderAccountMenu()
|
||||
}
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{isWithoutCard() ?
|
||||
renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{renderRouter()}
|
||||
</Card>
|
||||
}
|
||||
</Content>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(ManagementPage);
|
@@ -91,7 +91,7 @@ class PermissionListPage extends BaseListPage {
|
||||
const {pagination} = this.state;
|
||||
this.fetch({pagination});
|
||||
} else {
|
||||
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to sync")}: ${res.msg}`);
|
||||
}
|
||||
} else if (status === "error") {
|
||||
Setting.showMessage("error", "File failed to upload");
|
||||
|
@@ -83,7 +83,7 @@ class RoleListPage extends BaseListPage {
|
||||
const {pagination} = this.state;
|
||||
this.fetch({pagination});
|
||||
} else {
|
||||
Setting.showMessage("error", `Users failed to upload: ${res.msg}`);
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to sync")}: ${res.msg}`);
|
||||
}
|
||||
} else if (status === "error") {
|
||||
Setting.showMessage("error", "File failed to upload");
|
||||
|
@@ -69,7 +69,7 @@ export function getThemeData(organization, application) {
|
||||
}
|
||||
|
||||
export function getAlgorithm(themeAlgorithmNames) {
|
||||
return themeAlgorithmNames.map((algorithmName) => {
|
||||
return themeAlgorithmNames.sort().reverse().map((algorithmName) => {
|
||||
if (algorithmName === "dark") {
|
||||
return theme.darkAlgorithm;
|
||||
}
|
||||
@@ -1164,8 +1164,8 @@ export function getLoginLink(application) {
|
||||
let url;
|
||||
if (application === null) {
|
||||
url = null;
|
||||
} else if (window.location.pathname.includes("/auto-signup/oauth/authorize")) {
|
||||
url = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
|
||||
} else if (window.location.pathname.includes("/signup/oauth/authorize")) {
|
||||
url = window.location.pathname.replace("/signup/oauth/authorize", "/login/oauth/authorize");
|
||||
} else if (authConfig.appName === application.name) {
|
||||
url = "/login";
|
||||
} else if (application.signinUrl === "") {
|
||||
@@ -1173,7 +1173,7 @@ export function getLoginLink(application) {
|
||||
} else {
|
||||
url = application.signinUrl;
|
||||
}
|
||||
return url;
|
||||
return url + window.location.search;
|
||||
}
|
||||
|
||||
export function redirectToLoginPage(application, history) {
|
||||
@@ -1216,7 +1216,7 @@ export function renderSignupLink(application, text) {
|
||||
if (application === null) {
|
||||
url = null;
|
||||
} else if (window.location.pathname.includes("/login/oauth/authorize")) {
|
||||
url = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
|
||||
url = window.location.pathname.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||
} else if (authConfig.appName === application.name) {
|
||||
url = "/signup";
|
||||
} else {
|
||||
@@ -1228,10 +1228,10 @@ export function renderSignupLink(application, text) {
|
||||
}
|
||||
|
||||
const storeSigninUrl = () => {
|
||||
sessionStorage.setItem("signinUrl", window.location.href);
|
||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||
};
|
||||
|
||||
return renderLink(url, text, storeSigninUrl);
|
||||
return renderLink(url + window.location.search, text, storeSigninUrl);
|
||||
}
|
||||
|
||||
export function renderForgetLink(application, text) {
|
||||
@@ -1249,7 +1249,7 @@ export function renderForgetLink(application, text) {
|
||||
}
|
||||
|
||||
const storeSigninUrl = () => {
|
||||
sessionStorage.setItem("signinUrl", window.location.href);
|
||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||
};
|
||||
|
||||
return renderLink(url, text, storeSigninUrl);
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Radio, Row, Select, Switch} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as SyncerBackend from "./backend/SyncerBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
@@ -23,6 +23,7 @@ import SyncerTableColumnTable from "./table/SyncerTableColumnTable";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
@@ -32,11 +33,13 @@ class SyncerEditPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
certs: [],
|
||||
classes: props,
|
||||
syncerName: props.match.params.syncerName,
|
||||
syncer: null,
|
||||
organizations: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
testDbLoading: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,12 +67,24 @@ class SyncerEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
getCerts(owner) {
|
||||
CertBackend.getCerts(owner)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
certs: res.data || [],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: res.data || [],
|
||||
});
|
||||
if (res.data) {
|
||||
this.getCerts(`${res.data.owner}/${res.data.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,7 +243,7 @@ class SyncerEditPage extends React.Component {
|
||||
});
|
||||
})}>
|
||||
{
|
||||
["Database", "LDAP", "Keycloak"]
|
||||
["Database", "Keycloak"]
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
@@ -317,7 +332,7 @@ class SyncerEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.syncer.password} onChange={e => {
|
||||
<Input.Password value={this.state.syncer.password} onChange={e => {
|
||||
this.updateSyncerField("password", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@@ -332,6 +347,88 @@ class SyncerEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.syncer.databaseType === "mysql" || this.state.syncer.databaseType === "mssql" || this.state.syncer.databaseType === "postgres" ? (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:SSH type"), i18next.t("general:SSH type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Radio.Group value={this.state.syncer.sshType} buttonStyle="solid" onChange={e => {
|
||||
this.updateSyncerField("sshType", e.target.value);
|
||||
}}>
|
||||
<Radio.Button value="">{i18next.t("general:None")}</Radio.Button>
|
||||
<Radio.Button value="password">{i18next.t("general:Password")}</Radio.Button>
|
||||
<Radio.Button value="cert">{i18next.t("general:Cert")}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
this.state.syncer.sshType && this.state.syncer.databaseType === "mysql" || this.state.syncer.databaseType === "mssql" || this.state.syncer.databaseType === "postgres" ? (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:SSH host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input prefix={<LinkOutlined />} value={this.state.syncer.sshHost} onChange={e => {
|
||||
this.updateSyncerField("sshHost", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:SSH port"), i18next.t("provider:Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.syncer.sshPort} onChange={value => {
|
||||
this.updateSyncerField("sshPort", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:SSH user"), i18next.t("general:User - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.syncer.sshUser} onChange={e => {
|
||||
this.updateSyncerField("sshUser", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
this.state.syncer.sshType === "password" && (this.state.syncer.databaseType === "mysql" || this.state.syncer.databaseType === "mssql" || this.state.syncer.databaseType === "postgres") ?
|
||||
(
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:SSH password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input.Password value={this.state.syncer.sshPassword} onChange={e => {
|
||||
this.updateSyncerField("ssh " + "sshPassword", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:SSH cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.cert} onChange={(value => {this.updateSyncerField("cert", value);})}>
|
||||
{
|
||||
this.state?.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
</React.Fragment>
|
||||
) : null
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||
@@ -343,6 +440,31 @@ class SyncerEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button type={"primary"} loading={this.state.testDbLoading} onClick={() => {
|
||||
this.setState({testDbLoading: true});
|
||||
SyncerBackend.testSyncerDb(this.state.syncer)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({testDbLoading: false});
|
||||
Setting.showMessage("success", i18next.t("syncer:Connect successfully"));
|
||||
} else {
|
||||
this.setState({testDbLoading: false});
|
||||
Setting.showMessage("error", `${i18next.t("syncer:Failed to connect")}: ${res.msg}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({testDbLoading: false});
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
|
||||
});
|
||||
}
|
||||
}>{i18next.t("syncer:Test DB Connection")}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Table columns"), i18next.t("syncer:Table columns - Tooltip"))} :
|
||||
|
@@ -156,7 +156,7 @@ class ForgetPage extends React.Component {
|
||||
if (res.status === "ok") {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
Setting.goToLinkSoft(linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||
}
|
||||
|
@@ -92,7 +92,7 @@ class ResultPage extends React.Component {
|
||||
<Button type="primary" key="login" onClick={() => {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
Setting.goToLinkSoft(this, linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
|
@@ -17,6 +17,10 @@ import LoginPage from "./LoginPage";
|
||||
import {authConfig} from "./Auth";
|
||||
|
||||
class SelfLoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
import("../ManagementPage");
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} {...this.props} />
|
||||
|
@@ -87,8 +87,8 @@ class SignupPage extends React.Component {
|
||||
componentDidMount() {
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
if (oAuthParams !== null) {
|
||||
const signinUrl = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
const signinUrl = window.location.pathname.replace("/signup/oauth/authorize", "/login/oauth/authorize");
|
||||
sessionStorage.setItem("signinUrl", signinUrl + window.location.search);
|
||||
}
|
||||
|
||||
if (this.getApplicationObj() === undefined) {
|
||||
@@ -223,7 +223,7 @@ class SignupPage extends React.Component {
|
||||
Setting.goToLinkSoft(this, this.getResultPath(application, values));
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
Setting.showMessage("error", res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -639,7 +639,7 @@ class SignupPage extends React.Component {
|
||||
<a onClick={() => {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
Setting.goToLinkSoft(this, linkInStorage);
|
||||
} else {
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
|
@@ -58,6 +58,18 @@ export function addSyncer(syncer) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function testSyncerDb(syncer) {
|
||||
const newSyncer = Setting.deepCopy(syncer);
|
||||
return fetch(`${Setting.ServerUrl}/api/test-syncer-db`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newSyncer),
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deleteSyncer(syncer) {
|
||||
const newSyncer = Setting.deepCopy(syncer);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-syncer`, {
|
||||
|
@@ -147,7 +147,7 @@ export function sendCode(captchaType, captchaToken, clientSecret, method, countr
|
||||
Setting.showMessage("success", i18next.t("user:Verification code sent"));
|
||||
return true;
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t("user:" + res.msg));
|
||||
Setting.showMessage("error", res.msg);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
53
web/src/basic/CustomHead.js
Normal file
53
web/src/basic/CustomHead.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {useEffect} from "react";
|
||||
|
||||
function CustomHead(props) {
|
||||
useEffect(() => {
|
||||
const suffix = new Date().getTime().toString();
|
||||
|
||||
if (!props.headerHtml) {return;}
|
||||
const node = document.createElement("div");
|
||||
node.innerHTML = props.headerHtml;
|
||||
|
||||
node.childNodes.forEach(el => {
|
||||
if (el.nodeName === "#text") {
|
||||
return;
|
||||
}
|
||||
let innerNode = el;
|
||||
innerNode.setAttribute("app-custom-head" + suffix, "");
|
||||
|
||||
if (innerNode.localName === "script") {
|
||||
const scriptNode = document.createElement("script");
|
||||
Array.from(innerNode.attributes).forEach(attr => {
|
||||
scriptNode.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
scriptNode.text = innerNode.textContent;
|
||||
innerNode = scriptNode;
|
||||
}
|
||||
document.head.appendChild(innerNode);
|
||||
});
|
||||
|
||||
return () => {
|
||||
for (const el of document.head.children) {
|
||||
if (el.getAttribute("app-custom-head" + suffix) !== null) {
|
||||
document.head.removeChild(el);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default CustomHead;
|
@@ -36,7 +36,7 @@ class OpenTour extends React.Component {
|
||||
this.canTour() ?
|
||||
<Tooltip title="Click to open tour">
|
||||
<div className="select-box" style={{display: Setting.isMobile() ? "none" : null, ...this.props.style}} onClick={() => TourConfig.setIsTourVisible(true)} >
|
||||
<QuestionCircleOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
|
||||
<QuestionCircleOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
:
|
||||
|
@@ -56,7 +56,7 @@ class LanguageSelect extends React.Component {
|
||||
return (
|
||||
<Dropdown menu={{items: languageItems, onClick}} >
|
||||
<div className="select-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} >
|
||||
<GlobalOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
|
||||
<GlobalOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
|
@@ -21,9 +21,9 @@ import {CheckOutlined} from "@ant-design/icons";
|
||||
import {CompactTheme, DarkTheme, Light} from "antd-token-previewer/es/icons";
|
||||
|
||||
export const Themes = [
|
||||
{label: "Default", key: "default", icon: <Light style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Default")
|
||||
{label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Dark")
|
||||
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Compact")
|
||||
{label: "Default", key: "default", icon: <Light style={{fontSize: "24px"}} />}, // i18next.t("theme:Default")
|
||||
{label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px"}} />}, // i18next.t("theme:Dark")
|
||||
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px"}} />}, // i18next.t("theme:Compact")
|
||||
];
|
||||
|
||||
function getIcon(themeKey) {
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Position der Anmelde-, Registrierungs- und Passwort-vergessen-Formulare",
|
||||
"Grant types": "Grant-Typen",
|
||||
"Grant types - Tooltip": "Wählen Sie aus, welche Grant-Typen im OAuth-Protokoll zulässig sind",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Speichern",
|
||||
"Save & Exit": "Speichern und verlassen",
|
||||
"Session ID": "Session-ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Neuer Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Synchronisierungsintervall",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "The SSL mode used when connecting to the database",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Ubicación de los formularios de registro, inicio de sesión y olvido de contraseña",
|
||||
"Grant types": "Tipos de subvenciones",
|
||||
"Grant types - Tooltip": "Selecciona cuáles tipos de subvenciones están permitidas en el protocolo OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Guardar",
|
||||
"Save & Exit": "Guardar y salir",
|
||||
"Session ID": "ID de sesión",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Nuevo Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Intervalo de sincronización",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Emplacement des formulaires d'inscription, de connexion et de récupération de mot de passe",
|
||||
"Grant types": "Types d'autorisation",
|
||||
"Grant types - Tooltip": "Sélectionnez les types d'autorisations autorisés dans le protocole OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incrémentale",
|
||||
"Input": "Saisie",
|
||||
"Invitation code": "Code d'invitation",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Enregistrer",
|
||||
"Save & Exit": "Enregistrer et quitter",
|
||||
"Session ID": "Identifiant de session",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Est en lecture seule",
|
||||
"Is read-only - Tooltip": "En lecture seule - Infobulle",
|
||||
"New Syncer": "Nouveau synchroniseur",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Intervalle de synchronisation",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Tempat pendaftaran, masuk, dan lupa kata sandi",
|
||||
"Grant types": "Jenis-jenis hibah",
|
||||
"Grant types - Tooltip": "Pilih jenis hibah apa yang diperbolehkan dalam protokol OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Menyimpan",
|
||||
"Save & Exit": "Simpan & Keluar",
|
||||
"Session ID": "ID sesi",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Sinkronisasi Baru",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Interval sinkronisasi",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "登録、ログイン、パスワード忘れフォームの位置",
|
||||
"Grant types": "グラント種類",
|
||||
"Grant types - Tooltip": "OAuthプロトコルで許可されているグラントタイプを選択してください",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "保存",
|
||||
"Save & Exit": "保存して終了",
|
||||
"Session ID": "セッションID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "新しいシンクロナイザー",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "同期の間隔",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "가입, 로그인 및 비밀번호 재설정 양식의 위치",
|
||||
"Grant types": "Grant types: 부여 유형",
|
||||
"Grant types - Tooltip": "OAuth 프로토콜에서 허용되는 그란트 유형을 선택하십시오",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "저장하다",
|
||||
"Save & Exit": "저장하고 종료하기",
|
||||
"Session ID": "세션 ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "신규 싱크어",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "동기화 간격",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Localização dos formulários de registro, login e recuperação de senha",
|
||||
"Grant types": "Tipos de concessão",
|
||||
"Grant types - Tooltip": "Selecione quais tipos de concessão são permitidos no protocolo OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Código de convite",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Salvar",
|
||||
"Save & Exit": "Salvar e Sair",
|
||||
"Session ID": "ID da sessão",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Novo Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Intervalo de sincronização",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Местоположение форм регистрации, входа и восстановления пароля",
|
||||
"Grant types": "Типы грантов",
|
||||
"Grant types - Tooltip": "Выберите, какие типы грантов разрешены в протоколе OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Последовательный",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Код приглашения",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Сохранить",
|
||||
"Save & Exit": "Сохранить и выйти",
|
||||
"Session ID": "Идентификатор сессии",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "Новый синхронизатор",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Интервал синхронизации",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Davet Kodu",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Kaydet",
|
||||
"Save & Exit": "Kaydet ve Çık",
|
||||
"Session ID": "Oturum ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Location of the signup, signin and forget password forms",
|
||||
"Grant types": "Grant types",
|
||||
"Grant types - Tooltip": "Select which grant types are allowed in the OAuth protocol",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Incremental",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Save",
|
||||
"Save & Exit": "Save & Exit",
|
||||
"Session ID": "Session ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Sync interval",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "Vị trí của các biểu mẫu đăng ký, đăng nhập và quên mật khẩu",
|
||||
"Grant types": "Loại hỗ trợ",
|
||||
"Grant types - Tooltip": "Chọn loại hỗ trợ được cho phép trong giao thức OAuth",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - Edit",
|
||||
"Header HTML - Tooltip": "Custom the head tag of your application entry page",
|
||||
"Incremental": "Tăng",
|
||||
"Input": "Input",
|
||||
"Invitation code": "Invitation code",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "Root cert - Tooltip",
|
||||
"SAML attributes": "SAML attributes",
|
||||
"SAML attributes - Tooltip": "SAML attributes - Tooltip",
|
||||
"SSH cert": "SSH cert",
|
||||
"SSH type": "SSH type",
|
||||
"SSH type - Tooltip": "The auth type of SSH connection",
|
||||
"Save": "Lưu",
|
||||
"Save & Exit": "Lưu và Thoát",
|
||||
"Session ID": "ID phiên làm việc",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "Is read-only",
|
||||
"Is read-only - Tooltip": "Is read-only - Tooltip",
|
||||
"New Syncer": "New Syncer: Đồng bộ mới",
|
||||
"SSH host": "SSH host",
|
||||
"SSH password": "SSH password",
|
||||
"SSH port": "SSH port",
|
||||
"SSH user": "SSH user",
|
||||
"SSL mode": "SSL mode",
|
||||
"SSL mode - Tooltip": "SSL mode - Tooltip",
|
||||
"Sync interval": "Khoảng thời gian đồng bộ hóa",
|
||||
|
@@ -62,6 +62,9 @@
|
||||
"Form position - Tooltip": "注册、登录、忘记密码等表单的位置",
|
||||
"Grant types": "OAuth授权类型",
|
||||
"Grant types - Tooltip": "选择允许哪些OAuth协议中的grant types",
|
||||
"Header HTML": "Header HTML",
|
||||
"Header HTML - Edit": "Header HTML - 编辑",
|
||||
"Header HTML - Tooltip": "自定义应用页面的head标签",
|
||||
"Incremental": "递增",
|
||||
"Input": "输入",
|
||||
"Invitation code": "邀请码",
|
||||
@@ -324,6 +327,9 @@
|
||||
"Root cert - Tooltip": "根证书",
|
||||
"SAML attributes": "SAML属性",
|
||||
"SAML attributes - Tooltip": "Casdoor作为SAML IdP时所返回的SAML响应的属性",
|
||||
"SSH cert": "SSH证书",
|
||||
"SSH type": "SSH类型",
|
||||
"SSH type - Tooltip": "SSH连接的认证类型",
|
||||
"Save": "保存",
|
||||
"Save & Exit": "保存 & 退出",
|
||||
"Session ID": "会话ID",
|
||||
@@ -964,6 +970,10 @@
|
||||
"Is read-only": "是否只读",
|
||||
"Is read-only - Tooltip": "只读",
|
||||
"New Syncer": "添加同步器",
|
||||
"SSH host": "SSH主机",
|
||||
"SSH password": "SSH密码",
|
||||
"SSH port": "SSH端口",
|
||||
"SSH user": "SSH用户",
|
||||
"SSL mode": "SSL模式",
|
||||
"SSL mode - Tooltip": "连接数据库采用哪种SSL模式",
|
||||
"Sync interval": "同步间隔",
|
||||
|
@@ -76,6 +76,11 @@ class ProviderTable extends React.Component {
|
||||
this.updateField(table, index, "name", value);
|
||||
const provider = Setting.getArrayItem(this.props.providers, "name", value);
|
||||
this.updateField(table, index, "provider", provider);
|
||||
|
||||
// If the provider is email or SMS, set the rule to "all" instead of the default "None"
|
||||
if (provider.category === "Email" || provider.category === "SMS") {
|
||||
this.updateField(table, index, "rule", "all");
|
||||
}
|
||||
}} >
|
||||
{
|
||||
Setting.getDeduplicatedArray(this.props.providers, table, "name").map((provider, index) => <Option key={index} value={provider.name}>{provider.name}</Option>)
|
||||
@@ -223,6 +228,26 @@ class ProviderTable extends React.Component {
|
||||
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
|
||||
</Select>
|
||||
);
|
||||
} else if (record.provider?.category === "SMS" || record.provider?.category === "Email") {
|
||||
if (text === "None") {
|
||||
text = "all";
|
||||
}
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
defaultValue="all"
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "rule", value);
|
||||
}}>
|
||||
<Option key="all" value="all">{"All"}</Option>
|
||||
<Option key="signup" value="signup">{"Signup"}</Option>
|
||||
<Option key="login" value="login">{"Login"}</Option>
|
||||
<Option key="forget" value="forget">{"Forget Password"}</Option>
|
||||
<Option key="reset" value="reset">{"Reset Password"}</Option>
|
||||
<Option key="mfaSetup" value="mfaSetup">{"Set MFA"}</Option>
|
||||
<Option key="mfaAuth" value="mfaAuth">{"MFA Auth"}</Option>
|
||||
</Select>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user