Compare commits

...

6 Commits

Author SHA1 Message Date
dc3131c683 feat: use i18next-resources-to-backend to lazy load i18n (#2738)
* feat: use i18next-resources-to-backend to lazy load i18n file

* feat: change source in yarn.lock
2024-02-23 22:35:59 +08:00
042a8d0ad6 feat: add rule for SMS and Email provider (#2733)
* add phonecoderule

* feat:add phone code rule

* feat: add email rule

* fix: merge
2024-02-23 00:09:37 +08:00
44abfb3430 feat: support custom header HTML in entry pages (#2731) 2024-02-22 17:56:47 +08:00
53b8424a1f feat: fix JSON typo in init_data.json template 2024-02-21 17:33:08 +08:00
23c2ba3a2b feat: support ssh key/pem file in DB syncer (#2727)
* feat: support connect database with ssh tunnel in syncer

* feat: improve i18n translate

* feat: improve code format and i18n
2024-02-21 17:27:37 +08:00
3a9ffedce4 feat: support phone and Email in /api/login/oauth/access_token API (#2725)
Phone Number supports for /api/login/oauth/access_token as username

 Closes: #2724
2024-02-21 17:27:24 +08:00
45 changed files with 968 additions and 192 deletions

View File

@ -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()
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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": ""
}
]
}

View File

@ -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"`

View File

@ -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" {
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) {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

116
object/viaSSHDialer.go Normal file
View 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)
}

View File

@ -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")

View File

@ -32,6 +32,7 @@
"file-saver": "^2.0.5",
"i18n-iso-countries": "^7.0.0",
"i18next": "^19.8.9",
"i18next-resources-to-backend": "^1.2.0",
"jwt-decode": "^4.0.0",
"libphonenumber-js": "^1.10.19",
"moment": "^2.29.1",

View File

@ -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"}} >

View File

@ -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,32 +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="/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>
);
}
}

View File

@ -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"))} :

View File

@ -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`, {

View File

@ -0,0 +1,40 @@
// 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 => {
el.setAttribute("app-custom-head" + suffix, "");
document.head.appendChild(el);
});
return () => {
for (const el of document.head.children) {
if (el.getAttribute("app-custom-head" + suffix) !== null) {
document.head.removeChild(el);
}
}
};
});
}
export default CustomHead;

View File

@ -14,56 +14,9 @@
import i18n from "i18next";
import en from "./locales/en/data.json";
import zh from "./locales/zh/data.json";
import es from "./locales/es/data.json";
import fr from "./locales/fr/data.json";
import de from "./locales/de/data.json";
import id from "./locales/id/data.json";
import ja from "./locales/ja/data.json";
import ko from "./locales/ko/data.json";
import ru from "./locales/ru/data.json";
import vi from "./locales/vi/data.json";
import pt from "./locales/pt/data.json";
import it from "./locales/it/data.json";
import ms from "./locales/ms/data.json";
import tr from "./locales/tr/data.json";
import ar from "./locales/ar/data.json";
import he from "./locales/he/data.json";
import nl from "./locales/nl/data.json";
import pl from "./locales/pl/data.json";
import fi from "./locales/fi/data.json";
import sv from "./locales/sv/data.json";
import uk from "./locales/uk/data.json";
import kk from "./locales/kk/data.json";
import fa from "./locales/fa/data.json";
import * as Conf from "./Conf";
import {initReactI18next} from "react-i18next";
const resources = {
en: en,
zh: zh,
es: es,
fr: fr,
de: de,
id: id,
ja: ja,
ko: ko,
ru: ru,
vi: vi,
pt: pt,
it: it,
ms: ms,
tr: tr,
ar: ar,
he: he,
nl: nl,
pl: pl,
fi: fi,
sv: sv,
uk: uk,
kk: kk,
fa: fa,
};
import resourcesToBackend from "i18next-resources-to-backend";
function initLanguage() {
let language = localStorage.getItem("language");
@ -157,18 +110,24 @@ function initLanguage() {
return language;
}
i18n.use(initReactI18next).init({
lng: initLanguage(),
await i18n.use(resourcesToBackend(async(language, namespace) => {
const res = await import(`./locales/${language}/data.json`);
return res.default[namespace];
}
))
.use(initReactI18next)
.init({
lng: initLanguage(),
ns: Object.keys(en),
fallbackLng: Conf.DefaultLanguage,
resources: resources,
keySeparator: false,
keySeparator: false,
interpolation: {
escapeValue: true,
},
// debug: true,
saveMissing: true,
});
interpolation: {
escapeValue: true,
},
// debug: true,
saveMissing: true,
});
export default i18n;

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "同期の間隔",

View File

@ -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",

View File

@ -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": "동기화 간격",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "Интервал синхронизации",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "同步间隔",

View File

@ -223,6 +223,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;
}

View File

@ -1416,6 +1416,13 @@
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.23.2":
version "7.23.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.5", "@babel/template@^7.3.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
@ -8569,6 +8576,13 @@ i18n-iso-countries@^7.0.0:
dependencies:
diacritics "1.3.0"
i18next-resources-to-backend@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.0.tgz#4e0ea0c093bf1acb3eb47972a3002b84a1fec3b2"
integrity sha512-8f1l03s+QxDmCfpSXCh9V+AFcxAwIp0UaroWuyOx+hmmv8484GcELHs+lnu54FrNij8cDBEXvEwhzZoXsKcVpg==
dependencies:
"@babel/runtime" "^7.23.2"
i18next@^19.8.9:
version "19.9.2"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.9.2.tgz#ea5a124416e3c5ab85fddca2c8e3c3669a8da397"
@ -12721,6 +12735,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regenerator-transform@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"