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
This commit is contained in:
DacongDA 2024-02-21 17:27:37 +08:00 committed by GitHub
parent 3a9ffedce4
commit 23c2ba3a2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 570 additions and 12 deletions

View File

@ -168,3 +168,20 @@ func (c *ApiController) RunSyncer() {
c.ResponseOk() 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()
}

2
go.mod
View File

@ -59,7 +59,7 @@ require (
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect 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/net v0.17.0
golang.org/x/oauth2 v0.13.0 golang.org/x/oauth2 v0.13.0
google.golang.org/api v0.150.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.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.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.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.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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.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.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.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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.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.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 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.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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.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.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.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.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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -32,8 +32,9 @@ import (
_ "github.com/denisenkom/go-mssqldb" // db = mssql _ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql _ "github.com/go-sql-driver/mysql" // db = mysql
_ "github.com/lib/pq" // db = postgres _ "github.com/lib/pq" // db = postgres
"github.com/xorm-io/core"
"github.com/xorm-io/xorm" "github.com/xorm-io/xorm"
"github.com/xorm-io/xorm/core"
"github.com/xorm-io/xorm/names"
_ "modernc.org/sqlite" // db = sqlite _ "modernc.org/sqlite" // db = sqlite
) )
@ -98,7 +99,7 @@ func InitAdapter() {
} }
tableNamePrefix := conf.GetConfigString("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix) tbMapper := names.NewPrefixMapper(names.SnakeMapper{}, tableNamePrefix)
ormer.Engine.SetTableMapper(tbMapper) ormer.Engine.SetTableMapper(tbMapper)
} }
@ -118,6 +119,7 @@ type Ormer struct {
driverName string driverName string
dataSourceName string dataSourceName string
dbName string dbName string
Db *sql.DB
Engine *xorm.Engine Engine *xorm.Engine
} }
@ -127,6 +129,13 @@ func finalizer(a *Ormer) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
if a.Db != nil {
err = a.Db.Close()
if err != nil {
panic(err)
}
}
} }
// NewAdapter is the constructor for Ormer. // NewAdapter is the constructor for Ormer.
@ -148,6 +157,26 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) (*Ormer
return a, nil 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 { func refineDataSourceNameForPostgres(dataSourceName string) string {
reg := regexp.MustCompile(`dbname=[^ ]+\s*`) reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
return reg.ReplaceAllString(dataSourceName, "") return reg.ReplaceAllString(dataSourceName, "")
@ -226,6 +255,30 @@ func (a *Ormer) open() error {
return nil 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() { func (a *Ormer) close() {
_ = a.Engine.Close() _ = a.Engine.Close()
a.Engine = nil a.Engine = nil

View File

@ -39,11 +39,17 @@ type Syncer struct {
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"` DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
SslMode string `xorm:"varchar(100)" json:"sslMode"` SslMode string `xorm:"varchar(100)" json:"sslMode"`
SshType string `xorm:"varchar(100)" json:"sshType"`
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"` User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(150)" json:"password"` 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"` Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"` Table string `xorm:"varchar(100)" json:"table"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"` TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
@ -279,3 +285,25 @@ func RunSyncer(syncer *Syncer) error {
return syncer.syncUsers() 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 package object
import ( import (
"context"
"database/sql" "database/sql"
"database/sql/driver"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"golang.org/x/crypto/ssh"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-sql-driver/mysql"
) )
type OriginalUser = User type OriginalUser = User
@ -124,6 +129,19 @@ func (syncer *Syncer) calculateHash(user *OriginalUser) string {
return util.GetMd5Hash(s) 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 { func (syncer *Syncer) initAdapter() error {
if syncer.Ormer != nil { if syncer.Ormer != nil {
return 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) 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 { if !isCloudIntranet {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.") dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
} }
var err error if db != nil {
syncer.Ormer, err = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database) syncer.Ormer, err = NewAdapterFromDb(syncer.DatabaseType, dataSourceName, syncer.Database, db)
} else {
syncer.Ormer, err = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
}
return err return 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/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer") beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer") 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-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook") beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; 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 {LinkOutlined} from "@ant-design/icons";
import * as SyncerBackend from "./backend/SyncerBackend"; import * as SyncerBackend from "./backend/SyncerBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@ -23,6 +23,7 @@ import SyncerTableColumnTable from "./table/SyncerTableColumnTable";
import {Controlled as CodeMirror} from "react-codemirror2"; import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
import * as CertBackend from "./backend/CertBackend";
require("codemirror/theme/material-darker.css"); require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
@ -32,11 +33,13 @@ class SyncerEditPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
certs: [],
classes: props, classes: props,
syncerName: props.match.params.syncerName, syncerName: props.match.params.syncerName,
syncer: null, syncer: null,
organizations: [], organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit", 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() { getOrganizations() {
OrganizationBackend.getOrganizations("admin") OrganizationBackend.getOrganizations("admin")
.then((res) => { .then((res) => {
this.setState({ this.setState({
organizations: res.data || [], 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>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
@ -317,7 +332,7 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} : {Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
</Col> </Col>
<Col span={22} > <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); this.updateSyncerField("password", e.target.value);
}} /> }} />
</Col> </Col>
@ -332,6 +347,88 @@ class SyncerEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
@ -343,6 +440,31 @@ class SyncerEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </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"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Table columns"), i18next.t("syncer:Table columns - Tooltip"))} : {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()); }).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) { export function deleteSyncer(syncer) {
const newSyncer = Setting.deepCopy(syncer); const newSyncer = Setting.deepCopy(syncer);
return fetch(`${Setting.ServerUrl}/api/delete-syncer`, { return fetch(`${Setting.ServerUrl}/api/delete-syncer`, {

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Speichern",
"Save & Exit": "Speichern und verlassen", "Save & Exit": "Speichern und verlassen",
"Session ID": "Session-ID", "Session ID": "Session-ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "Neuer Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Synchronisierungsintervall", "Sync interval": "Synchronisierungsintervall",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "The SSL mode used when connecting to the database", "SSL mode - Tooltip": "The SSL mode used when connecting to the database",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Guardar",
"Save & Exit": "Guardar y salir", "Save & Exit": "Guardar y salir",
"Session ID": "ID de sesión", "Session ID": "ID de sesión",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "Nuevo Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Intervalo de sincronización", "Sync interval": "Intervalo de sincronización",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Enregistrer",
"Save & Exit": "Enregistrer et quitter", "Save & Exit": "Enregistrer et quitter",
"Session ID": "Identifiant de session", "Session ID": "Identifiant de session",
@ -964,6 +967,10 @@
"Is read-only": "Est en lecture seule", "Is read-only": "Est en lecture seule",
"Is read-only - Tooltip": "En lecture seule - Infobulle", "Is read-only - Tooltip": "En lecture seule - Infobulle",
"New Syncer": "Nouveau synchroniseur", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Intervalle de synchronisation", "Sync interval": "Intervalle de synchronisation",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Menyimpan",
"Save & Exit": "Simpan & Keluar", "Save & Exit": "Simpan & Keluar",
"Session ID": "ID sesi", "Session ID": "ID sesi",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "Sinkronisasi Baru", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Interval sinkronisasi", "Sync interval": "Interval sinkronisasi",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "セッションID", "Session ID": "セッションID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "同期の間隔", "Sync interval": "同期の間隔",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "세션 ID", "Session ID": "세션 ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "동기화 간격", "Sync interval": "동기화 간격",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Salvar",
"Save & Exit": "Salvar e Sair", "Save & Exit": "Salvar e Sair",
"Session ID": "ID da sessão", "Session ID": "ID da sessão",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "Novo Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Intervalo de sincronização", "Sync interval": "Intervalo de sincronização",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Интервал синхронизации", "Sync interval": "Интервал синхронизации",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Kaydet",
"Save & Exit": "Kaydet ve Çık", "Save & Exit": "Kaydet ve Çık",
"Session ID": "Oturum ID", "Session ID": "Oturum ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID", "Session ID": "Session ID",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Sync interval", "Sync interval": "Sync interval",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "Root cert - Tooltip", "Root cert - Tooltip": "Root cert - Tooltip",
"SAML attributes": "SAML attributes", "SAML attributes": "SAML attributes",
"SAML attributes - Tooltip": "SAML attributes - Tooltip", "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": "Lưu",
"Save & Exit": "Lưu và Thoát", "Save & Exit": "Lưu và Thoát",
"Session ID": "ID phiên làm việc", "Session ID": "ID phiên làm việc",
@ -964,6 +967,10 @@
"Is read-only": "Is read-only", "Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip", "Is read-only - Tooltip": "Is read-only - Tooltip",
"New Syncer": "New Syncer: Đồng bộ mới", "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": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip", "SSL mode - Tooltip": "SSL mode - Tooltip",
"Sync interval": "Khoảng thời gian đồng bộ hóa", "Sync interval": "Khoảng thời gian đồng bộ hóa",

View File

@ -324,6 +324,9 @@
"Root cert - Tooltip": "根证书", "Root cert - Tooltip": "根证书",
"SAML attributes": "SAML属性", "SAML attributes": "SAML属性",
"SAML attributes - Tooltip": "Casdoor作为SAML IdP时所返回的SAML响应的属性", "SAML attributes - Tooltip": "Casdoor作为SAML IdP时所返回的SAML响应的属性",
"SSH cert": "SSH证书",
"SSH type": "SSH类型",
"SSH type - Tooltip": "SSH连接的认证类型",
"Save": "保存", "Save": "保存",
"Save & Exit": "保存 & 退出", "Save & Exit": "保存 & 退出",
"Session ID": "会话ID", "Session ID": "会话ID",
@ -964,6 +967,10 @@
"Is read-only": "是否只读", "Is read-only": "是否只读",
"Is read-only - Tooltip": "只读", "Is read-only - Tooltip": "只读",
"New Syncer": "添加同步器", "New Syncer": "添加同步器",
"SSH host": "SSH主机",
"SSH password": "SSH密码",
"SSH port": "SSH端口",
"SSH user": "SSH用户",
"SSL mode": "SSL模式", "SSL mode": "SSL模式",
"SSL mode - Tooltip": "连接数据库采用哪种SSL模式", "SSL mode - Tooltip": "连接数据库采用哪种SSL模式",
"Sync interval": "同步间隔", "Sync interval": "同步间隔",