mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-23 13:42:39 +08:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
295a69c5f7 | ||
![]() |
a8a8f39963 | ||
![]() |
90f8eba02d | ||
![]() |
2cca1c9136 | ||
![]() |
c2eebd61a1 | ||
![]() |
59566f61d7 | ||
![]() |
7e4c9c91cd | ||
![]() |
430ee616db | ||
![]() |
2e3a323528 | ||
![]() |
09e8408a3d | ||
![]() |
2998bbf4b9 | ||
![]() |
404382f2e0 | ||
![]() |
71db1f62a9 | ||
![]() |
07dc6bf7cd |
@@ -451,7 +451,7 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
|
||||||
initScore, err := getInitScore(organization)
|
initScore, err := getInitScore(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||||
|
@@ -21,14 +21,6 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapServer struct {
|
|
||||||
Host string `json:"host"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Admin string `json:"admin"`
|
|
||||||
Passwd string `json:"passwd"`
|
|
||||||
BaseDn string `json:"baseDn"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LdapResp struct {
|
type LdapResp struct {
|
||||||
// Groups []LdapRespGroup `json:"groups"`
|
// Groups []LdapRespGroup `json:"groups"`
|
||||||
Users []object.LdapRespUser `json:"users"`
|
Users []object.LdapRespUser `json:"users"`
|
||||||
@@ -44,21 +36,17 @@ type LdapSyncResp struct {
|
|||||||
Failed []object.LdapRespUser `json:"failed"`
|
Failed []object.LdapRespUser `json:"failed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdapUser
|
// GetLdapUsers
|
||||||
// @Tag Account API
|
// @Tag Account API
|
||||||
// @Title GetLdapser
|
// @Title GetLdapser
|
||||||
// @router /get-ldap-user [post]
|
// @router /get-ldap-users [get]
|
||||||
func (c *ApiController) GetLdapUser() {
|
func (c *ApiController) GetLdapUsers() {
|
||||||
ldapServer := LdapServer{}
|
id := c.Input().Get("id")
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
|
||||||
if err != nil || util.IsStringsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp LdapResp
|
_, ldapId := util.GetOwnerAndNameFromId(id)
|
||||||
|
ldapServer := object.GetLdap(ldapId)
|
||||||
|
|
||||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
conn, err := ldapServer.GetLdapConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -83,6 +71,8 @@ func (c *ApiController) GetLdapUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resp LdapResp
|
||||||
|
uuids := make([]string, len(users))
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||||
UidNumber: user.UidNumber,
|
UidNumber: user.UidNumber,
|
||||||
@@ -95,9 +85,12 @@ func (c *ApiController) GetLdapUser() {
|
|||||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||||
})
|
})
|
||||||
|
uuids = append(uuids, user.Uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(resp)
|
existUuids := object.CheckLdapUuidExist(ldapServer.Owner, uuids)
|
||||||
|
|
||||||
|
c.ResponseOk(resp, existUuids)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdaps
|
// GetLdaps
|
||||||
@@ -134,7 +127,7 @@ func (c *ApiController) AddLdap() {
|
|||||||
var ldap object.Ldap
|
var ldap object.Ldap
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,14 +143,14 @@ func (c *ApiController) AddLdap() {
|
|||||||
|
|
||||||
affected := object.AddLdap(&ldap)
|
affected := object.AddLdap(&ldap)
|
||||||
resp := wrapActionResponse(affected)
|
resp := wrapActionResponse(affected)
|
||||||
if affected {
|
resp.Data2 = ldap
|
||||||
resp.Data2 = ldap
|
|
||||||
}
|
|
||||||
if ldap.AutoSync != 0 {
|
if ldap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(resp)
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLdap
|
// UpdateLdap
|
||||||
@@ -174,17 +167,15 @@ func (c *ApiController) UpdateLdap() {
|
|||||||
|
|
||||||
prevLdap := object.GetLdap(ldap.Id)
|
prevLdap := object.GetLdap(ldap.Id)
|
||||||
affected := object.UpdateLdap(&ldap)
|
affected := object.UpdateLdap(&ldap)
|
||||||
resp := wrapActionResponse(affected)
|
|
||||||
if affected {
|
|
||||||
resp.Data2 = ldap
|
|
||||||
}
|
|
||||||
if ldap.AutoSync != 0 {
|
if ldap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(resp)
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLdap
|
// DeleteLdap
|
||||||
@@ -199,8 +190,12 @@ func (c *ApiController) DeleteLdap() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
affected := object.DeleteLdap(&ldap)
|
||||||
|
|
||||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
c.ResponseOk(wrapActionResponse(object.DeleteLdap(&ldap)))
|
|
||||||
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncLdapUsers
|
// SyncLdapUsers
|
||||||
@@ -226,20 +221,3 @@ func (c *ApiController) SyncLdapUsers() {
|
|||||||
Failed: *failed,
|
Failed: *failed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckLdapUsersExist
|
|
||||||
// @Tag Account API
|
|
||||||
// @Title CheckLdapUserExist
|
|
||||||
// @router /check-ldap-users-exist [post]
|
|
||||||
func (c *ApiController) CheckLdapUsersExist() {
|
|
||||||
owner := c.Input().Get("owner")
|
|
||||||
var uuids []string
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
|
||||||
c.ResponseOk(exist)
|
|
||||||
}
|
|
||||||
|
@@ -15,22 +15,9 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/casdoor/casdoor/object"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SystemInfo struct {
|
|
||||||
MemoryUsed uint64 `json:"memory_used"`
|
|
||||||
MemoryTotal uint64 `json:"memory_total"`
|
|
||||||
CpuUsage []float64 `json:"cpu_usage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GitRepoInfo struct {
|
|
||||||
AheadCnt int `json:"ahead_cnt"`
|
|
||||||
Commit string `json:"commit"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSystemInfo
|
// GetSystemInfo
|
||||||
// @Title GetSystemInfo
|
// @Title GetSystemInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
@@ -39,54 +26,32 @@ type GitRepoInfo struct {
|
|||||||
// @Success 200 {object} object.SystemInfo The Response object
|
// @Success 200 {object} object.SystemInfo The Response object
|
||||||
// @router /get-system-info [get]
|
// @router /get-system-info [get]
|
||||||
func (c *ApiController) GetSystemInfo() {
|
func (c *ApiController) GetSystemInfo() {
|
||||||
id := c.GetString("id")
|
_, ok := c.RequireAdmin()
|
||||||
if id == "" {
|
if !ok {
|
||||||
id = c.GetSessionUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
user := object.GetUser(id)
|
|
||||||
if user == nil || !user.IsGlobalAdmin {
|
|
||||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cpuUsage, err := util.GetCpuUsage()
|
systemInfo, err := util.GetSystemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryUsed, memoryTotal, err := util.GetMemoryUsage()
|
c.ResponseOk(systemInfo)
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = SystemInfo{
|
|
||||||
CpuUsage: cpuUsage,
|
|
||||||
MemoryUsed: memoryUsed,
|
|
||||||
MemoryTotal: memoryTotal,
|
|
||||||
}
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitRepoVersion
|
// GetVersionInfo
|
||||||
// @Title GitRepoVersion
|
// @Title GetVersionInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
// @Description get local github repo's latest release version info
|
// @Description get local git repo's latest release version info
|
||||||
// @Success 200 {string} local latest version hash of casdoor
|
// @Success 200 {string} local latest version hash of Casdoor
|
||||||
// @router /get-release [get]
|
// @router /get-version-info [get]
|
||||||
func (c *ApiController) GitRepoVersion() {
|
func (c *ApiController) GetVersionInfo() {
|
||||||
aheadCnt, commit, version, err := util.GetGitRepoVersion()
|
versionInfo, err := util.GetVersionInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = GitRepoInfo{
|
c.ResponseOk(versionInfo)
|
||||||
AheadCnt: aheadCnt,
|
|
||||||
Commit: commit,
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
package i18n
|
package i18n
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@@ -15,11 +15,9 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -170,10 +168,18 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
Email: dtUserInfo.Email,
|
Email: dtUserInfo.Email,
|
||||||
AvatarUrl: dtUserInfo.AvatarUrl,
|
AvatarUrl: dtUserInfo.AvatarUrl,
|
||||||
}
|
}
|
||||||
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
|
|
||||||
if !isUserInOrg {
|
corpAccessToken := idp.getInnerAppAccessToken()
|
||||||
|
userId, err := idp.getUserId(userInfo.UnionId, corpAccessToken)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
corpEmail, err := idp.getUserCorpEmail(userId, corpAccessToken)
|
||||||
|
if err == nil && corpEmail != "" {
|
||||||
|
userInfo.Email = corpEmail
|
||||||
|
}
|
||||||
|
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,23 +208,14 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
||||||
appKey := idp.Config.ClientID
|
|
||||||
appSecret := idp.Config.ClientSecret
|
|
||||||
body := make(map[string]string)
|
body := make(map[string]string)
|
||||||
body["appKey"] = appKey
|
body["appKey"] = idp.Config.ClientID
|
||||||
body["appSecret"] = appSecret
|
body["appSecret"] = idp.Config.ClientSecret
|
||||||
bodyData, err := json.Marshal(body)
|
respBytes, err := idp.postWithBody(body, "https://api.dingtalk.com/v1.0/oauth2/accessToken")
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
}
|
|
||||||
reader := bytes.NewReader(bodyData)
|
|
||||||
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
|
|
||||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
|
||||||
resp, err := idp.Client.Do(request)
|
|
||||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
ExpireIn int `json:"expireIn"`
|
ExpireIn int `json:"expireIn"`
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
@@ -230,34 +227,53 @@ func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
|||||||
return data.AccessToken
|
return data.AccessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
|
func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (string, error) {
|
||||||
body := make(map[string]string)
|
body := make(map[string]string)
|
||||||
body["unionid"] = unionId
|
body["unionid"] = unionId
|
||||||
bodyData, err := json.Marshal(body)
|
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
return "", err
|
||||||
}
|
|
||||||
reader := bytes.NewReader(bodyData)
|
|
||||||
accessToken := idp.getInnerAppAccessToken()
|
|
||||||
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
|
|
||||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
|
||||||
resp, err := idp.Client.Do(request)
|
|
||||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
ErrCode int `json:"errcode"`
|
ErrCode int `json:"errcode"`
|
||||||
ErrMessage string `json:"errmsg"`
|
ErrMessage string `json:"errmsg"`
|
||||||
|
Result struct {
|
||||||
|
UserId string `json:"userid"`
|
||||||
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(respBytes, &data)
|
err = json.Unmarshal(respBytes, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
return "", err
|
||||||
}
|
}
|
||||||
if data.ErrCode == 60121 {
|
if data.ErrCode == 60121 {
|
||||||
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
return "", fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||||
} else if data.ErrCode != 0 {
|
} else if data.ErrCode != 0 {
|
||||||
return false, fmt.Errorf(data.ErrMessage)
|
return "", fmt.Errorf(data.ErrMessage)
|
||||||
}
|
}
|
||||||
return true, nil
|
return data.Result.UserId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, error) {
|
||||||
|
body := make(map[string]string)
|
||||||
|
body["userid"] = userId
|
||||||
|
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ErrMessage string `json:"errmsg"`
|
||||||
|
Result struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(respBytes, &data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if data.ErrMessage != "ok" {
|
||||||
|
return "", fmt.Errorf(data.ErrMessage)
|
||||||
|
}
|
||||||
|
return data.Result.Email, nil
|
||||||
}
|
}
|
||||||
|
@@ -196,7 +196,7 @@ func checkLdapUserPassword(user *User, password string, lang string) (*User, str
|
|||||||
ldaps := GetLdaps(user.Owner)
|
ldaps := GetLdaps(user.Owner)
|
||||||
ldapLoginSuccess := false
|
ldapLoginSuccess := false
|
||||||
for _, ldapServer := range ldaps {
|
for _, ldapServer := range ldaps {
|
||||||
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
conn, err := ldapServer.GetLdapConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,7 @@ type Ldap struct {
|
|||||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
|
EnableSsl bool `xorm:"bool" json:"enableSsl"`
|
||||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||||
@@ -152,20 +153,26 @@ func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
|||||||
return isMicrosoft, err
|
return isMicrosoft, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
func (ldap *Ldap) GetLdapConn() (c *ldapConn, err error) {
|
||||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
var conn *goldap.Conn
|
||||||
|
if ldap.EnableSsl {
|
||||||
|
conn, err = goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port), nil)
|
||||||
|
} else {
|
||||||
|
conn, err = goldap.Dial("tcp", fmt.Sprintf("%s:%d", ldap.Host, ldap.Port))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = conn.Bind(adminUser, adminPasswd)
|
err = conn.Bind(ldap.Admin, ldap.Passwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
isAD, err := isMicrosoftAD(conn)
|
isAD, err := isMicrosoftAD(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fail to get Ldap server type [%s]", adminUser)
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
return &ldapConn{Conn: conn, IsAD: isAD}, nil
|
||||||
}
|
}
|
||||||
@@ -352,7 +359,7 @@ func UpdateLdap(ldap *Ldap) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||||
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
"port", "enable_ssl", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -76,7 +76,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
|
|
||||||
UpdateLdapSyncTime(ldap.Id)
|
UpdateLdapSyncTime(ldap.Id)
|
||||||
// fetch all users
|
// fetch all users
|
||||||
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
|
conn, err := ldap.GetLdapConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
|
@@ -44,7 +44,7 @@ func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
|||||||
|
|
||||||
if provider.Type == sender.Aliyun {
|
if provider.Type == sender.Aliyun {
|
||||||
for i, number := range phoneNumbers {
|
for i, number := range phoneNumbers {
|
||||||
phoneNumbers[i] = strings.TrimPrefix(number, "+")
|
phoneNumbers[i] = strings.TrimPrefix(number, "+86")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -118,13 +118,12 @@ func initAPI() {
|
|||||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||||
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
|
||||||
|
|
||||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
|
||||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
|
||||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
|
||||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||||
beego.Router("/api/check-ldap-users-exist", &controllers.ApiController{}, "POST:CheckLdapUsersExist")
|
|
||||||
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
||||||
|
|
||||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||||
@@ -225,5 +224,5 @@ func initAPI() {
|
|||||||
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
|
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish")
|
||||||
|
|
||||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||||
beego.Router("/api/get-release", &controllers.ApiController{}, "GET:GitRepoVersion")
|
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||||
}
|
}
|
||||||
|
@@ -636,14 +636,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/check-ldap-users-exist": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"Account API"
|
|
||||||
],
|
|
||||||
"operationId": "ApiController.CheckLdapUserExist"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/check-user-password": {
|
"/api/check-user-password": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1349,8 +1341,8 @@
|
|||||||
"operationId": "ApiController.GetLdap"
|
"operationId": "ApiController.GetLdap"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/get-ldap-user": {
|
"/api/get-ldap-users": {
|
||||||
"post": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Account API"
|
"Account API"
|
||||||
],
|
],
|
||||||
@@ -1835,20 +1827,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/get-release": {
|
|
||||||
"get": {
|
|
||||||
"tags": [
|
|
||||||
"System API"
|
|
||||||
],
|
|
||||||
"description": "get local github repo's latest release version info",
|
|
||||||
"operationId": "ApiController.GitRepoVersion",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "{string} local latest version hash of casdoor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/get-resource": {
|
"/api/get-resource": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -2340,6 +2318,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/get-version-info": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"System API"
|
||||||
|
],
|
||||||
|
"description": "get local git repo's latest release version info",
|
||||||
|
"operationId": "ApiController.GetVersionInfo",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "{string} local latest version hash of Casdoor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/get-webhook": {
|
"/api/get-webhook": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -3635,11 +3627,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"2346.0xc000278ab0.false": {
|
"2268.0xc000528cf0.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"2381.0xc000278ae0.false": {
|
"2302.0xc000528d20.false": {
|
||||||
"title": "false",
|
"title": "false",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@@ -3766,10 +3758,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/2346.0xc000278ab0.false"
|
"$ref": "#/definitions/2268.0xc000528cf0.false"
|
||||||
},
|
},
|
||||||
"data2": {
|
"data2": {
|
||||||
"$ref": "#/definitions/2381.0xc000278ae0.false"
|
"$ref": "#/definitions/2302.0xc000528d20.false"
|
||||||
},
|
},
|
||||||
"msg": {
|
"msg": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@@ -412,11 +412,6 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.Response'
|
$ref: '#/definitions/controllers.Response'
|
||||||
/api/check-ldap-users-exist:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Account API
|
|
||||||
operationId: ApiController.CheckLdapUserExist
|
|
||||||
/api/check-user-password:
|
/api/check-user-password:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@@ -875,8 +870,8 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.GetLdap
|
operationId: ApiController.GetLdap
|
||||||
/api/get-ldap-user:
|
/api/get-ldap-users:
|
||||||
post:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Account API
|
- Account API
|
||||||
operationId: ApiController.GetLdapser
|
operationId: ApiController.GetLdapser
|
||||||
@@ -1193,15 +1188,6 @@ paths:
|
|||||||
description: The Response object
|
description: The Response object
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/object.Record'
|
$ref: '#/definitions/object.Record'
|
||||||
/api/get-release:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- System API
|
|
||||||
description: get local github repo's latest release version info
|
|
||||||
operationId: ApiController.GitRepoVersion
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: '{string} local latest version hash of casdoor'
|
|
||||||
/api/get-resource:
|
/api/get-resource:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -1525,6 +1511,15 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.User'
|
$ref: '#/definitions/object.User'
|
||||||
|
/api/get-version-info:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- System API
|
||||||
|
description: get local git repo's latest release version info
|
||||||
|
operationId: ApiController.GetVersionInfo
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: '{string} local latest version hash of Casdoor'
|
||||||
/api/get-webhook:
|
/api/get-webhook:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -2379,10 +2374,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
definitions:
|
definitions:
|
||||||
2346.0xc000278ab0.false:
|
2268.0xc000528cf0.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
2381.0xc000278ae0.false:
|
2302.0xc000528d20.false:
|
||||||
title: "false"
|
title: "false"
|
||||||
type: object
|
type: object
|
||||||
Response:
|
Response:
|
||||||
@@ -2469,9 +2464,9 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/2346.0xc000278ab0.false'
|
$ref: '#/definitions/2268.0xc000528cf0.false'
|
||||||
data2:
|
data2:
|
||||||
$ref: '#/definitions/2381.0xc000278ae0.false'
|
$ref: '#/definitions/2302.0xc000528d20.false'
|
||||||
msg:
|
msg:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
|
17
sync/conf.go
17
sync/conf.go
@@ -1,17 +0,0 @@
|
|||||||
package sync
|
|
||||||
|
|
||||||
var (
|
|
||||||
host1 = "127.0.0.1"
|
|
||||||
port1 = 3306
|
|
||||||
username1 = "root"
|
|
||||||
password1 = "123456"
|
|
||||||
database1 = "db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
host2 = "127.0.0.1"
|
|
||||||
port2 = 3306
|
|
||||||
username2 = "root"
|
|
||||||
password2 = "123456"
|
|
||||||
database2 = "db"
|
|
||||||
)
|
|
100
sync/database.go
Normal file
100
sync/database.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-mysql-org/go-mysql/canal"
|
||||||
|
"github.com/xorm-io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
database string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
|
||||||
|
engine *xorm.Engine
|
||||||
|
serverId uint32
|
||||||
|
serverUuid string
|
||||||
|
Gtid string
|
||||||
|
canal.DummyEventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDatabase(host string, port int, database string, username string, password string) *Database {
|
||||||
|
db := &Database{
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
database: database,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
|
||||||
|
engine, err := createEngine(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.engine = engine
|
||||||
|
|
||||||
|
db.serverId, err = getServerId(engine)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.serverUuid, err = getServerUuid(engine)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) getCanalConfig() *canal.Config {
|
||||||
|
// config canal
|
||||||
|
cfg := canal.NewDefaultConfig()
|
||||||
|
cfg.Addr = fmt.Sprintf("%s:%d", db.host, db.port)
|
||||||
|
cfg.Password = db.password
|
||||||
|
cfg.User = db.username
|
||||||
|
// We only care table in database1
|
||||||
|
cfg.Dump.TableDB = db.database
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) startCanal(targetDb *Database) error {
|
||||||
|
canalConfig := db.getCanalConfig()
|
||||||
|
c, err := canal.NewCanal(canalConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gtidSet, err := c.GetMasterGTIDSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a handler to handle RowsEvent
|
||||||
|
c.SetEventHandler(targetDb)
|
||||||
|
|
||||||
|
// Start replication
|
||||||
|
err = c.StartFromGTID(gtidSet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -17,84 +17,32 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-mysql-org/go-mysql/canal"
|
"github.com/go-mysql-org/go-mysql/canal"
|
||||||
"github.com/go-mysql-org/go-mysql/mysql"
|
"github.com/go-mysql-org/go-mysql/mysql"
|
||||||
"github.com/go-mysql-org/go-mysql/replication"
|
"github.com/go-mysql-org/go-mysql/replication"
|
||||||
"github.com/siddontang/go-log/log"
|
"github.com/siddontang/go-log/log"
|
||||||
"github.com/xorm-io/xorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyEventHandler struct {
|
func (db *Database) OnGTID(header *replication.EventHeader, gtid mysql.GTIDSet) error {
|
||||||
dataSourceName string
|
|
||||||
engine *xorm.Engine
|
|
||||||
serverId uint32
|
|
||||||
serverUUID string
|
|
||||||
GTID string
|
|
||||||
canal.DummyEventHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartCanal(cfg *canal.Config, username string, password string, host string, port int, database string) error {
|
|
||||||
c, err := canal.NewCanal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
GTIDSet, err := c.GetMasterGTIDSet()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHandler := GetMyEventHandler(username, password, host, port, database)
|
|
||||||
// Register a handler to handle RowsEvent
|
|
||||||
c.SetEventHandler(&eventHandler)
|
|
||||||
|
|
||||||
// Start replication
|
|
||||||
err = c.StartFromGTID(GTIDSet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartBinlogSync() error {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
// init config
|
|
||||||
cfg1 := GetCanalConfig(username1, password1, host1, port1, database1)
|
|
||||||
cfg2 := GetCanalConfig(username2, password2, host2, port2, database2)
|
|
||||||
|
|
||||||
// start canal1 replication
|
|
||||||
go StartCanal(cfg1, username2, password2, host2, port2, database2)
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
// start canal2 replication
|
|
||||||
go StartCanal(cfg2, username1, password1, host1, port1, database1)
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MyEventHandler) OnGTID(header *replication.EventHeader, gtid mysql.GTIDSet) error {
|
|
||||||
log.Info("OnGTID: ", gtid.String())
|
log.Info("OnGTID: ", gtid.String())
|
||||||
h.GTID = gtid.String()
|
db.Gtid = gtid.String()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MyEventHandler) onDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
func (db *Database) onDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
||||||
log.Info("into DDL event")
|
log.Info("into DDL event")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
func (db *Database) OnRow(e *canal.RowsEvent) error {
|
||||||
log.Info("serverId: ", e.Header.ServerID)
|
log.Info("serverId: ", e.Header.ServerID)
|
||||||
if strings.Contains(h.GTID, h.serverUUID) {
|
if strings.Contains(db.Gtid, db.serverUuid) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the next gtid of the target library to the gtid of the current target library to avoid loopbacks
|
// Set the next gtid of the target library to the gtid of the current target library to avoid loopbacks
|
||||||
h.engine.Exec(fmt.Sprintf("SET GTID_NEXT= '%s'", h.GTID))
|
db.engine.Exec(fmt.Sprintf("SET GTID_NEXT= '%s'", db.Gtid))
|
||||||
length := len(e.Table.Columns)
|
length := len(e.Table.Columns)
|
||||||
columnNames := make([]string, length)
|
columnNames := make([]string, length)
|
||||||
oldColumnValue := make([]interface{}, length)
|
oldColumnValue := make([]interface{}, length)
|
||||||
@@ -110,11 +58,11 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// get pk column name
|
// get pk column name
|
||||||
pkColumnNames := GetPKColumnNames(columnNames, e.Table.PKColumns)
|
pkColumnNames := getPkColumnNames(columnNames, e.Table.PKColumns)
|
||||||
|
|
||||||
switch e.Action {
|
switch e.Action {
|
||||||
case canal.UpdateAction:
|
case canal.UpdateAction:
|
||||||
h.engine.Exec("BEGIN")
|
db.engine.Exec("BEGIN")
|
||||||
for i, row := range e.Rows {
|
for i, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
@@ -136,23 +84,23 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i%2 == 1 {
|
if i%2 == 1 {
|
||||||
pkColumnValue := GetPKColumnValues(oldColumnValue, e.Table.PKColumns)
|
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||||
updateSql, args, err := GetUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.engine.DB().Exec(updateSql, args...)
|
res, err := db.engine.DB().Exec(updateSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(updateSql, args, res)
|
log.Info(updateSql, args, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.engine.Exec("COMMIT")
|
db.engine.Exec("COMMIT")
|
||||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||||
case canal.DeleteAction:
|
case canal.DeleteAction:
|
||||||
h.engine.Exec("BEGIN")
|
db.engine.Exec("BEGIN")
|
||||||
for _, row := range e.Rows {
|
for _, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if isChar[j] == true {
|
if isChar[j] == true {
|
||||||
@@ -162,22 +110,22 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pkColumnValue := GetPKColumnValues(oldColumnValue, e.Table.PKColumns)
|
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||||
deleteSql, args, err := GetDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.engine.DB().Exec(deleteSql, args...)
|
res, err := db.engine.DB().Exec(deleteSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(deleteSql, args, res)
|
log.Info(deleteSql, args, res)
|
||||||
}
|
}
|
||||||
h.engine.Exec("COMMIT")
|
db.engine.Exec("COMMIT")
|
||||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||||
case canal.InsertAction:
|
case canal.InsertAction:
|
||||||
h.engine.Exec("BEGIN")
|
db.engine.Exec("BEGIN")
|
||||||
for _, row := range e.Rows {
|
for _, row := range e.Rows {
|
||||||
for j, item := range row {
|
for j, item := range row {
|
||||||
if isChar[j] == true {
|
if isChar[j] == true {
|
||||||
@@ -191,25 +139,25 @@ func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertSql, args, err := GetInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
insertSql, args, err := getInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.engine.DB().Exec(insertSql, args...)
|
res, err := db.engine.DB().Exec(insertSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(insertSql, args, res)
|
log.Info(insertSql, args, res)
|
||||||
}
|
}
|
||||||
h.engine.Exec("COMMIT")
|
db.engine.Exec("COMMIT")
|
||||||
h.engine.Exec("SET GTID_NEXT='automatic'")
|
db.engine.Exec("SET GTID_NEXT='automatic'")
|
||||||
default:
|
default:
|
||||||
log.Infof("%v", e.String())
|
log.Infof("%v", e.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MyEventHandler) String() string {
|
func (db *Database) String() string {
|
||||||
return "MyEventHandler"
|
return "Database"
|
||||||
}
|
}
|
32
sync/sync.go
Normal file
32
sync/sync.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
func startSyncJob(db1 *Database, db2 *Database) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// start canal1 replication
|
||||||
|
go db1.startCanal(db2)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
// start canal2 replication
|
||||||
|
go db2.startCanal(db1)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
30
sync/sync_test.go
Normal file
30
sync/sync_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStartSyncJob(t *testing.T) {
|
||||||
|
db1 := newDatabase("127.0.0.1", 3306, "casdoor", "root", "123456")
|
||||||
|
db2 := newDatabase("127.0.0.1", 3306, "casdoor2", "root", "123456")
|
||||||
|
startSyncJob(db1, db2)
|
||||||
|
}
|
@@ -19,13 +19,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-mysql-org/go-mysql/canal"
|
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/xorm-io/xorm"
|
"github.com/xorm-io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetUpdateSql(schemaName string, tableName string, columnNames []string, newColumnVal []interface{}, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
func getUpdateSql(schemaName string, tableName string, columnNames []string, newColumnVal []interface{}, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||||
updateSql := squirrel.Update(schemaName + "." + tableName)
|
updateSql := squirrel.Update(schemaName + "." + tableName)
|
||||||
for i, columnName := range columnNames {
|
for i, columnName := range columnNames {
|
||||||
updateSql = updateSql.Set(columnName, newColumnVal[i])
|
updateSql = updateSql.Set(columnName, newColumnVal[i])
|
||||||
@@ -43,13 +41,13 @@ func GetUpdateSql(schemaName string, tableName string, columnNames []string, new
|
|||||||
return sql, args, nil
|
return sql, args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInsertSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
func getInsertSql(schemaName string, tableName string, columnNames []string, columnValue []interface{}) (string, []interface{}, error) {
|
||||||
insertSql := squirrel.Insert(schemaName + "." + tableName).Columns(columnNames...).Values(columnValue...)
|
insertSql := squirrel.Insert(schemaName + "." + tableName).Columns(columnNames...).Values(columnValue...)
|
||||||
|
|
||||||
return insertSql.ToSql()
|
return insertSql.ToSql()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDeleteSql(schemaName string, tableName string, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
func getDeleteSql(schemaName string, tableName string, pkColumnNames []string, pkColumnValue []interface{}) (string, []interface{}, error) {
|
||||||
deleteSql := squirrel.Delete(schemaName + "." + tableName)
|
deleteSql := squirrel.Delete(schemaName + "." + tableName)
|
||||||
|
|
||||||
for i, columnName := range pkColumnNames {
|
for i, columnName := range pkColumnNames {
|
||||||
@@ -59,7 +57,7 @@ func GetDeleteSql(schemaName string, tableName string, pkColumnNames []string, p
|
|||||||
return deleteSql.ToSql()
|
return deleteSql.ToSql()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateEngine(dataSourceName string) (*xorm.Engine, error) {
|
func createEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||||
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -75,7 +73,7 @@ func CreateEngine(dataSourceName string) (*xorm.Engine, error) {
|
|||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServerId(engin *xorm.Engine) (uint32, error) {
|
func getServerId(engin *xorm.Engine) (uint32, error) {
|
||||||
res, err := engin.QueryInterface("SELECT @@server_id")
|
res, err := engin.QueryInterface("SELECT @@server_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -84,16 +82,16 @@ func GetServerId(engin *xorm.Engine) (uint32, error) {
|
|||||||
return uint32(serverId), nil
|
return uint32(serverId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetServerUUID(engin *xorm.Engine) (string, error) {
|
func getServerUuid(engin *xorm.Engine) (string, error) {
|
||||||
res, err := engin.QueryString("show variables like 'server_uuid'")
|
res, err := engin.QueryString("show variables like 'server_uuid'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
serverUUID := fmt.Sprintf("%s", res[0]["Value"])
|
serverUuid := fmt.Sprintf("%s", res[0]["Value"])
|
||||||
return serverUUID, err
|
return serverUuid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPKColumnNames(columnNames []string, PKColumns []int) []string {
|
func getPkColumnNames(columnNames []string, PKColumns []int) []string {
|
||||||
pkColumnNames := make([]string, len(PKColumns))
|
pkColumnNames := make([]string, len(PKColumns))
|
||||||
for i, index := range PKColumns {
|
for i, index := range PKColumns {
|
||||||
pkColumnNames[i] = columnNames[index]
|
pkColumnNames[i] = columnNames[index]
|
||||||
@@ -101,30 +99,10 @@ func GetPKColumnNames(columnNames []string, PKColumns []int) []string {
|
|||||||
return pkColumnNames
|
return pkColumnNames
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPKColumnValues(columnValues []interface{}, PKColumns []int) []interface{} {
|
func getPkColumnValues(columnValues []interface{}, PKColumns []int) []interface{} {
|
||||||
pkColumnNames := make([]interface{}, len(PKColumns))
|
pkColumnNames := make([]interface{}, len(PKColumns))
|
||||||
for i, index := range PKColumns {
|
for i, index := range PKColumns {
|
||||||
pkColumnNames[i] = columnValues[index]
|
pkColumnNames[i] = columnValues[index]
|
||||||
}
|
}
|
||||||
return pkColumnNames
|
return pkColumnNames
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCanalConfig(username string, password string, host string, port int, database string) *canal.Config {
|
|
||||||
// config canal
|
|
||||||
cfg := canal.NewDefaultConfig()
|
|
||||||
cfg.Addr = fmt.Sprintf("%s:%d", host, port)
|
|
||||||
cfg.Password = password
|
|
||||||
cfg.User = username
|
|
||||||
// We only care table in database1
|
|
||||||
cfg.Dump.TableDB = database
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMyEventHandler(username string, password string, host string, port int, database string) MyEventHandler {
|
|
||||||
var eventHandler MyEventHandler
|
|
||||||
eventHandler.dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
|
|
||||||
eventHandler.engine, _ = CreateEngine(eventHandler.dataSourceName)
|
|
||||||
eventHandler.serverId, _ = GetServerId(eventHandler.engine)
|
|
||||||
eventHandler.serverUUID, _ = GetServerUUID(eventHandler.engine)
|
|
||||||
return eventHandler
|
|
||||||
}
|
|
@@ -26,16 +26,26 @@ import (
|
|||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// get cpu usage
|
type SystemInfo struct {
|
||||||
func GetCpuUsage() ([]float64, error) {
|
CpuUsage []float64 `json:"cpuUsage"`
|
||||||
|
MemoryUsed uint64 `json:"memoryUsed"`
|
||||||
|
MemoryTotal uint64 `json:"memoryTotal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VersionInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
CommitId string `json:"commitId"`
|
||||||
|
CommitOffset int `json:"commitOffset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCpuUsage get cpu usage
|
||||||
|
func getCpuUsage() ([]float64, error) {
|
||||||
usage, err := cpu.Percent(time.Second, true)
|
usage, err := cpu.Percent(time.Second, true)
|
||||||
return usage, err
|
return usage, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDate, version string
|
// getMemoryUsage get memory usage
|
||||||
|
func getMemoryUsage() (uint64, uint64, error) {
|
||||||
// get memory usage
|
|
||||||
func GetMemoryUsage() (uint64, uint64, error) {
|
|
||||||
virtualMem, err := mem.VirtualMemory()
|
virtualMem, err := mem.VirtualMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
@@ -47,21 +57,46 @@ func GetMemoryUsage() (uint64, uint64, error) {
|
|||||||
return m.TotalAlloc, virtualMem.Total, nil
|
return m.TotalAlloc, virtualMem.Total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get github current commit and repo release version
|
func GetSystemInfo() (*SystemInfo, error) {
|
||||||
func GetGitRepoVersion() (int, string, string, error) {
|
cpuUsage, err := getCpuUsage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryUsed, memoryTotal, err := getMemoryUsage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &SystemInfo{
|
||||||
|
CpuUsage: cpuUsage,
|
||||||
|
MemoryUsed: memoryUsed,
|
||||||
|
MemoryTotal: memoryTotal,
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersionInfo get git current commit and repo release version
|
||||||
|
func GetVersionInfo() (*VersionInfo, error) {
|
||||||
|
res := &VersionInfo{
|
||||||
|
Version: "",
|
||||||
|
CommitId: "",
|
||||||
|
CommitOffset: -1,
|
||||||
|
}
|
||||||
|
|
||||||
_, filename, _, _ := runtime.Caller(0)
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
rootPath := path.Dir(path.Dir(filename))
|
rootPath := path.Dir(path.Dir(filename))
|
||||||
r, err := git.PlainOpen(rootPath)
|
r, err := git.PlainOpen(rootPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, "", "", err
|
return res, err
|
||||||
}
|
}
|
||||||
ref, err := r.Head()
|
ref, err := r.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, "", "", err
|
return res, err
|
||||||
}
|
}
|
||||||
tags, err := r.Tags()
|
tags, err := r.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, "", "", err
|
return res, err
|
||||||
}
|
}
|
||||||
tagMap := make(map[plumbing.Hash]string)
|
tagMap := make(map[plumbing.Hash]string)
|
||||||
err = tags.ForEach(func(t *plumbing.Reference) error {
|
err = tags.ForEach(func(t *plumbing.Reference) error {
|
||||||
@@ -74,29 +109,34 @@ func GetGitRepoVersion() (int, string, string, error) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, "", "", err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
|
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
|
||||||
|
|
||||||
aheadCnt := 0
|
commitOffset := 0
|
||||||
releaseVersion := ""
|
version := ""
|
||||||
// iterates over the commits
|
// iterates over the commits
|
||||||
err = cIter.ForEach(func(c *object.Commit) error {
|
err = cIter.ForEach(func(c *object.Commit) error {
|
||||||
tag, ok := tagMap[c.Hash]
|
tag, ok := tagMap[c.Hash]
|
||||||
if ok {
|
if ok {
|
||||||
if releaseVersion == "" {
|
if version == "" {
|
||||||
releaseVersion = tag
|
version = tag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if releaseVersion == "" {
|
if version == "" {
|
||||||
aheadCnt++
|
commitOffset++
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, "", "", err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return aheadCnt, ref.Hash().String(), releaseVersion, nil
|
res = &VersionInfo{
|
||||||
|
Version: version,
|
||||||
|
CommitId: ref.Hash().String(),
|
||||||
|
CommitOffset: commitOffset,
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -29,21 +29,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetCpuUsage(t *testing.T) {
|
func TestGetCpuUsage(t *testing.T) {
|
||||||
usage, err := GetCpuUsage()
|
usage, err := getCpuUsage()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
t.Log(usage)
|
t.Log(usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMemoryUsage(t *testing.T) {
|
func TestGetMemoryUsage(t *testing.T) {
|
||||||
used, total, err := GetMemoryUsage()
|
used, total, err := getMemoryUsage()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
t.Log(used, total)
|
t.Log(used, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGitRepoVersion(t *testing.T) {
|
func TestGetGitRepoVersion(t *testing.T) {
|
||||||
aheadCnt, commit, version, err := GetGitRepoVersion()
|
versionInfo, err := GetVersionInfo()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
t.Log(aheadCnt, commit, version)
|
t.Log(versionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetVersion(t *testing.T) {
|
func TestGetVersion(t *testing.T) {
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as AdapterBackend from "./backend/AdapterBackend";
|
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class AdapterListPage extends BaseListPage {
|
class AdapterListPage extends BaseListPage {
|
||||||
newAdapter() {
|
newAdapter() {
|
||||||
@@ -204,12 +205,11 @@ class AdapterListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/adapters/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/adapters/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteAdapter(index)}
|
onConfirm={() => this.deleteAdapter(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -16,7 +16,7 @@ import React, {Component} from "react";
|
|||||||
import "./App.less";
|
import "./App.less";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {StyleProvider} from "@ant-design/cssinjs";
|
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||||
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
|
||||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||||
@@ -730,7 +730,7 @@ class App extends Component {
|
|||||||
},
|
},
|
||||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||||
}}>
|
}}>
|
||||||
<StyleProvider hashPriority="high">
|
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||||
{
|
{
|
||||||
this.renderPage()
|
this.renderPage()
|
||||||
}
|
}
|
||||||
|
@@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
|
import {Button, Col, List, Row, Table, Tooltip} from "antd";
|
||||||
import {EditOutlined} from "@ant-design/icons";
|
import {EditOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class ApplicationListPage extends BaseListPage {
|
class ApplicationListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -232,13 +233,12 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.organization}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/applications/${record.organization}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteApplication(index)}
|
onConfirm={() => this.deleteApplication(index)}
|
||||||
disabled={record.name === "app-built-in"}
|
disabled={record.name === "app-built-in"}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} disabled={record.name === "app-built-in"} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Table} from "antd";
|
import {Button, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class CertListPage extends BaseListPage {
|
class CertListPage extends BaseListPage {
|
||||||
newCert() {
|
newCert() {
|
||||||
@@ -168,12 +169,11 @@ class CertListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteCert(index)}
|
onConfirm={() => this.deleteCert(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -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} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
||||||
import * as LddpBackend from "./backend/LdapBackend";
|
import * as LddpBackend from "./backend/LdapBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
@@ -83,7 +83,12 @@ class LdapEditPage extends React.Component {
|
|||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("ldap:Edit LDAP")}
|
{i18next.t("ldap:Edit LDAP")}
|
||||||
<Button type="primary" onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
<Button onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitLdapEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
|
<Button style={{marginLeft: "20px"}}
|
||||||
|
onClick={() => Setting.goToLink(`/ldap/sync/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||||
|
{i18next.t("ldap:Sync")} LDAP
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
} style={{marginLeft: "5px"}} type="inner">
|
} style={{marginLeft: "5px"}} type="inner">
|
||||||
<Row style={{marginTop: "10px"}}>
|
<Row style={{marginTop: "10px"}}>
|
||||||
@@ -141,6 +146,16 @@ class LdapEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Enable SSL"), i18next.t("ldap:Enable SSL - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21} >
|
||||||
|
<Switch checked={this.state.ldap.enableSsl} onChange={checked => {
|
||||||
|
this.updateLdapField("enableSsl", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
{Setting.getLabel(i18next.t("ldap:Base DN"), i18next.t("ldap:Base DN - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Base DN"), i18next.t("ldap:Base DN - Tooltip"))} :
|
||||||
@@ -190,14 +205,18 @@ class LdapEditPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitLdapEdit() {
|
submitLdapEdit(willExist) {
|
||||||
LddpBackend.updateLdap(this.state.ldap)
|
LddpBackend.updateLdap(this.state.ldap)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Update LDAP server success");
|
Setting.showMessage("success", "Update LDAP server success");
|
||||||
this.setState((prevState) => {
|
this.setState({
|
||||||
prevState.ldap = res.data2;
|
organizationName: this.state.ldap.owner,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (willExist) {
|
||||||
|
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
@@ -210,25 +229,13 @@ class LdapEditPage extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row style={{width: "100%"}}>
|
{
|
||||||
<Col span={1}>
|
this.state.ldap !== null ? this.renderLdap() : null
|
||||||
</Col>
|
}
|
||||||
<Col span={22}>
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
{
|
<Button size="large" onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
||||||
this.state.ldap !== null ? this.renderLdap() : null
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitLdapEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
}
|
</div>
|
||||||
</Col>
|
|
||||||
<Col span={1}>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{margin: 10}}>
|
|
||||||
<Col span={2}>
|
|
||||||
</Col>
|
|
||||||
<Col span={18}>
|
|
||||||
<Button type="primary" size="large"
|
|
||||||
onClick={() => this.submitLdapEdit()}>{i18next.t("general:Save")}</Button>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -14,10 +14,11 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
import {Button, Col, Row, Table} from "antd";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class LdapListPage extends React.Component {
|
class LdapListPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -139,13 +140,11 @@ class LdapListPage extends React.Component {
|
|||||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
onClick={() => Setting.goToLink(`/ldap/sync/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||||
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
onClick={() => Setting.goToLink(`/ldap/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
||||||
onConfirm={() => this.deleteLdap(index)}
|
onConfirm={() => this.deleteLdap(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}}
|
</PopconfirmModal>
|
||||||
type="primary" danger>{i18next.t("general:Delete")}</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -81,44 +81,28 @@ class LdapSyncPage extends React.Component {
|
|||||||
prevState.ldap = res.data;
|
prevState.ldap = res.data;
|
||||||
return prevState;
|
return prevState;
|
||||||
});
|
});
|
||||||
this.getLdapUser(res.data);
|
this.getLdapUser();
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getLdapUser(ldap) {
|
getLdapUser() {
|
||||||
LdapBackend.getLdapUser(ldap)
|
LdapBackend.getLdapUser(this.state.organizationName, this.state.ldapId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
prevState.users = res.data.users;
|
prevState.users = res.data.users;
|
||||||
|
prevState.existUuids = res.data2?.length > 0 ? res.data2 : [];
|
||||||
return prevState;
|
return prevState;
|
||||||
});
|
});
|
||||||
this.getExistUsers(ldap.owner, res.data.users);
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getExistUsers(owner, users) {
|
|
||||||
const uuidArray = [];
|
|
||||||
users.forEach(elem => {
|
|
||||||
uuidArray.push(elem.uuid);
|
|
||||||
});
|
|
||||||
LdapBackend.checkLdapUsersExist(owner, uuidArray)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === "ok") {
|
|
||||||
this.setState(prevState => {
|
|
||||||
prevState.existUuids = res.data?.length > 0 ? res.data : [];
|
|
||||||
return prevState;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildValArray(data, key) {
|
buildValArray(data, key) {
|
||||||
const valTypesArray = [];
|
const valTypesArray = [];
|
||||||
|
|
||||||
@@ -220,9 +204,14 @@ class LdapSyncPage extends React.Component {
|
|||||||
title={"Please confirm to sync selected users"}
|
title={"Please confirm to sync selected users"}
|
||||||
onConfirm={() => this.syncUsers()}
|
onConfirm={() => this.syncUsers()}
|
||||||
>
|
>
|
||||||
<Button type="primary" size="small"
|
<Button type="primary" style={{marginLeft: "10px"}}>
|
||||||
style={{marginLeft: "10px"}}>{i18next.t("ldap:Sync")}</Button>
|
{i18next.t("ldap:Sync")}
|
||||||
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
<Button style={{marginLeft: "20px"}}
|
||||||
|
onClick={() => Setting.goToLink(`/ldap/${this.state.organizationName}/${this.state.ldapId}`)}>
|
||||||
|
{i18next.t("general:Edit")} LDAP
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
loading={users === null}
|
loading={users === null}
|
||||||
@@ -234,17 +223,20 @@ class LdapSyncPage extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row style={{width: "100%"}}>
|
<Row style={{width: "100%", justifyContent: "center"}}>
|
||||||
<Col span={1}>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
{
|
{
|
||||||
this.renderTable(this.state.users)
|
this.renderTable(this.state.users)
|
||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={1}>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => {
|
||||||
|
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||||
|
}}>
|
||||||
|
{i18next.t("general:Save & Exit")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -13,11 +13,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Col, Popconfirm, Row, Table} from "antd";
|
import {Button, Col, Row, Table} from "antd";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class LdapTable extends React.Component {
|
class LdapTable extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -57,14 +58,14 @@ class LdapTable extends React.Component {
|
|||||||
LdapBackend.addLdap(newLdap)
|
LdapBackend.addLdap(newLdap)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Add LDAP server success");
|
Setting.showMessage("success", i18next.t("general:Successfully added"));
|
||||||
if (table === undefined) {
|
if (table === undefined) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
table = Setting.addRow(table, res.data2);
|
table = Setting.addRow(table, res.data2);
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -77,14 +78,13 @@ class LdapTable extends React.Component {
|
|||||||
LdapBackend.deleteLdap(table[i])
|
LdapBackend.deleteLdap(table[i])
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Delete LDAP server success");
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
table = Setting.deleteRow(table, i);
|
table = Setting.deleteRow(table, i);
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
Setting.showMessage("error", `Delete LDAP server failed: ${error}`);
|
Setting.showMessage("error", `Delete LDAP server failed: ${error}`);
|
||||||
});
|
});
|
||||||
@@ -152,18 +152,19 @@ class LdapTable extends React.Component {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
|
||||||
|
onClick={() => Setting.goToLink(`/ldap/sync/${record.owner}/${record.id}`)}>
|
||||||
|
{i18next.t("ldap:Sync")}
|
||||||
|
</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
||||||
type="primary"
|
onClick={() => Setting.goToLink(`/ldap/${record.owner}/${record.id}`)}>
|
||||||
onClick={() => Setting.goToLink(`/ldap/sync/${record.owner}/${record.id}`)}>{i18next.t("ldap:Sync")}</Button>
|
{i18next.t("general:Edit")}
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}}
|
</Button>
|
||||||
onClick={() => Setting.goToLink(`/ldap/${record.owner}/${record.id}`)}>{i18next.t("general:Edit")}</Button>
|
<PopconfirmModal
|
||||||
<Popconfirm
|
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.serverName} ?`}
|
||||||
onConfirm={() => this.deleteRow(table, index)}
|
onConfirm={() => this.deleteRow(table, index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}}
|
</PopconfirmModal>
|
||||||
type="primary" danger>{i18next.t("general:Delete")}</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as ModelBackend from "./backend/ModelBackend";
|
import * as ModelBackend from "./backend/ModelBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class ModelListPage extends BaseListPage {
|
class ModelListPage extends BaseListPage {
|
||||||
newModel() {
|
newModel() {
|
||||||
@@ -142,12 +143,11 @@ class ModelListPage extends BaseListPage {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary"
|
||||||
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
onClick={() => this.props.history.push(`/models/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteModel(index)}
|
onConfirm={() => this.deleteModel(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class OrganizationListPage extends BaseListPage {
|
class OrganizationListPage extends BaseListPage {
|
||||||
newOrganization() {
|
newOrganization() {
|
||||||
@@ -226,13 +227,12 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/organizations/${record.name}/users`)}>{i18next.t("general:Users")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/organizations/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteOrganization(index)}
|
onConfirm={() => this.deleteOrganization(index)}
|
||||||
disabled={record.name === "built-in"}
|
disabled={record.name === "built-in"}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} disabled={record.name === "built-in"} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Table} from "antd";
|
import {Button, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import * as Provider from "./auth/Provider";
|
import * as Provider from "./auth/Provider";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class PaymentListPage extends BaseListPage {
|
class PaymentListPage extends BaseListPage {
|
||||||
newPayment() {
|
newPayment() {
|
||||||
@@ -222,12 +223,11 @@ class PaymentListPage extends BaseListPage {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deletePayment(index)}
|
onConfirm={() => this.deletePayment(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as PermissionBackend from "./backend/PermissionBackend";
|
import * as PermissionBackend from "./backend/PermissionBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class PermissionListPage extends BaseListPage {
|
class PermissionListPage extends BaseListPage {
|
||||||
newPermission() {
|
newPermission() {
|
||||||
@@ -300,12 +301,11 @@ class PermissionListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/permissions/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deletePermission(index)}
|
onConfirm={() => this.deletePermission(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
33
web/src/PopconfirmModal.js
Normal file
33
web/src/PopconfirmModal.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2021 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 {Button, Popconfirm} from "antd";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const PopconfirmModal = (props) => {
|
||||||
|
return (
|
||||||
|
<Popconfirm
|
||||||
|
title={props.title}
|
||||||
|
onConfirm={props.onConfirm}
|
||||||
|
disabled={props.disabled}
|
||||||
|
okText={i18next.t("user:OK")}
|
||||||
|
cancelText={i18next.t("general:Cancel")}
|
||||||
|
>
|
||||||
|
<Button style={{marginBottom: "10px"}} disabled={props.disabled} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PopconfirmModal;
|
@@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd";
|
import {Button, Col, List, Row, Table, Tooltip} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as ProductBackend from "./backend/ProductBackend";
|
import * as ProductBackend from "./backend/ProductBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
import {EditOutlined} from "@ant-design/icons";
|
import {EditOutlined} from "@ant-design/icons";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class ProductListPage extends BaseListPage {
|
class ProductListPage extends BaseListPage {
|
||||||
newProduct() {
|
newProduct() {
|
||||||
@@ -239,12 +240,11 @@ class ProductListPage extends BaseListPage {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/products/${record.name}/buy`)}>{i18next.t("product:Buy")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/products/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteProduct(index)}
|
onConfirm={() => this.deleteProduct(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,13 +14,14 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Table} from "antd";
|
import {Button, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||||
import * as Provider from "./auth/Provider";
|
import * as Provider from "./auth/Provider";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class ProviderListPage extends BaseListPage {
|
class ProviderListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -206,12 +207,11 @@ class ProviderListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteProvider(index)}
|
onConfirm={() => this.deleteProvider(index)}
|
||||||
>
|
>
|
||||||
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Popconfirm, Table, Upload} from "antd";
|
import {Button, Table, Upload} from "antd";
|
||||||
import {UploadOutlined} from "@ant-design/icons";
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@@ -21,6 +21,7 @@ import * as ResourceBackend from "./backend/ResourceBackend";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class ResourceListPage extends BaseListPage {
|
class ResourceListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -244,15 +245,13 @@ class ResourceListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* <Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/resources/${record.name}`)}>{i18next.t("general:Edit")}</Button>*/}
|
<PopconfirmModal
|
||||||
<Popconfirm
|
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteResource(index)}
|
onConfirm={() => this.deleteResource(index)}
|
||||||
okText={i18next.t("user:OK")}
|
okText={i18next.t("user:OK")}
|
||||||
cancelText={i18next.t("user:Cancel")}
|
cancelText={i18next.t("user:Cancel")}
|
||||||
>
|
>
|
||||||
<Button type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as RoleBackend from "./backend/RoleBackend";
|
import * as RoleBackend from "./backend/RoleBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class RoleListPage extends BaseListPage {
|
class RoleListPage extends BaseListPage {
|
||||||
newRole() {
|
newRole() {
|
||||||
@@ -175,12 +176,11 @@ class RoleListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/roles/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteRole(index)}
|
onConfirm={() => this.deleteRole(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -16,9 +16,10 @@ import BaseListPage from "./BaseListPage";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Table, Tag} from "antd";
|
import {Table, Tag} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as SessionBackend from "./backend/SessionBackend";
|
import * as SessionBackend from "./backend/SessionBackend";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class SessionListPage extends BaseListPage {
|
class SessionListPage extends BaseListPage {
|
||||||
|
|
||||||
@@ -97,12 +98,11 @@ class SessionListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteSession(index)}
|
onConfirm={() => this.deleteSession(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as SyncerBackend from "./backend/SyncerBackend";
|
import * as SyncerBackend from "./backend/SyncerBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class SyncerListPage extends BaseListPage {
|
class SyncerListPage extends BaseListPage {
|
||||||
newSyncer() {
|
newSyncer() {
|
||||||
@@ -232,12 +233,11 @@ class SyncerListPage extends BaseListPage {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.runSyncer(index)}>{i18next.t("general:Sync")}</Button>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/syncers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteSyncer(index)}
|
onConfirm={() => this.deleteSyncer(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -23,31 +23,24 @@ class SystemInfo extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
cpuUsage: [],
|
systemInfo: {cpuUsage: [], memoryUsed: 0, memoryTotal: 0},
|
||||||
memUsed: 0,
|
versionInfo: {},
|
||||||
memTotal: 0,
|
|
||||||
latestVersion: undefined,
|
|
||||||
intervalId: null,
|
intervalId: null,
|
||||||
href: "",
|
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
SystemBackend.getSystemInfo(this.props.account?.owner, this.props.account?.name).then(res => {
|
SystemBackend.getSystemInfo().then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
cpuUsage: res.cpu_usage,
|
systemInfo: res.data,
|
||||||
memUsed: res.memory_used,
|
|
||||||
memTotal: res.memory_total,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const id = setInterval(() => {
|
const id = setInterval(() => {
|
||||||
SystemBackend.getSystemInfo(this.props.account?.owner, this.props.account?.name).then(res => {
|
SystemBackend.getSystemInfo().then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
cpuUsage: res.cpu_usage,
|
systemInfo: res.data,
|
||||||
memUsed: res.memory_used,
|
|
||||||
memTotal: res.memory_total,
|
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||||
@@ -58,12 +51,12 @@ class SystemInfo extends React.Component {
|
|||||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
SystemBackend.getGitHubLatestReleaseVersion().then(res => {
|
SystemBackend.getVersionInfo().then(res => {
|
||||||
const href = res.version && res.version.length > 0 ? `https://github.com/casdoor/casdoor/releases/tag/${res.version}` : "";
|
this.setState({
|
||||||
const versionText = res.version && res.version.length > 0 ? res.version : i18next.t("system:Unknown Version");
|
versionInfo: res.data,
|
||||||
this.setState({latestVersion: versionText, href: href});
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
Setting.showMessage("error", `get latest commit version failed: ${err}`);
|
Setting.showMessage("error", `Version info failed to get: ${err}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,21 +67,25 @@ class SystemInfo extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const CPUInfo = this.state.cpuUsage?.length > 0 ?
|
const cpuUi = this.state.systemInfo.cpuUsage?.length <= 0 ? i18next.t("system:Get CPU Usage Failed") :
|
||||||
this.state.cpuUsage.map((usage, i) => {
|
this.state.systemInfo.cpuUsage.map((usage, i) => {
|
||||||
return (
|
return (
|
||||||
<Progress key={i} percent={Number(usage.toFixed(1))} />
|
<Progress key={i} percent={Number(usage.toFixed(1))} />
|
||||||
);
|
);
|
||||||
}) : i18next.t("system:Get CPU Usage Failed");
|
});
|
||||||
|
|
||||||
const MemInfo = (
|
const memUi = this.state.systemInfo.memoryUsed && this.state.systemInfo.memoryTotal && this.state.systemInfo.memoryTotal <= 0 ? i18next.t("system:Get Memory Usage Failed") :
|
||||||
this.state.memUsed && this.state.memTotal && this.state.memTotal !== 0 ?
|
<div>
|
||||||
<div>
|
{Setting.getFriendlyFileSize(this.state.systemInfo.memoryUsed)} / {Setting.getFriendlyFileSize(this.state.systemInfo.memoryTotal)}
|
||||||
{Setting.getFriendlyFileSize(this.state.memUsed)} / {Setting.getFriendlyFileSize(this.state.memTotal)}
|
<br /> <br />
|
||||||
<br /> <br />
|
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
|
||||||
<Progress type="circle" percent={Number((Number(this.state.memUsed) / Number(this.state.memTotal) * 100).toFixed(2))} />
|
</div>;
|
||||||
</div> : i18next.t("system:Get Memory Usage Failed")
|
|
||||||
);
|
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
|
||||||
|
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown Version");
|
||||||
|
if (this.state.versionInfo?.commitOffset > 0) {
|
||||||
|
versionText += ` (ahead+${this.state.versionInfo?.commitOffset})`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Setting.isMobile()) {
|
if (!Setting.isMobile()) {
|
||||||
return (
|
return (
|
||||||
@@ -98,25 +95,25 @@ class SystemInfo extends React.Component {
|
|||||||
<Row gutter={[10, 10]}>
|
<Row gutter={[10, 10]}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||||
{this.state.loading ? <Spin size="large" /> : CPUInfo}
|
{this.state.loading ? <Spin size="large" /> : cpuUi}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||||
{this.state.loading ? <Spin size="large" /> : MemInfo}
|
{this.state.loading ? <Spin size="large" /> : memUi}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||||
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
||||||
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
|
GitHub: <a target="_blank" rel="noreferrer" href="https://github.com/casdoor/casdoor">Casdoor</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
|
{i18next.t("system:Version")}: <a target="_blank" rel="noreferrer" href={link}>{versionText}</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
|
{i18next.t("system:Official Website")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org">https://casdoor.org</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
|
{i18next.t("system:Community")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">Get in Touch!</a>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}></Col>
|
<Col span={6}></Col>
|
||||||
@@ -127,24 +124,24 @@ class SystemInfo extends React.Component {
|
|||||||
<Row gutter={[16, 0]}>
|
<Row gutter={[16, 0]}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
||||||
{this.state.loading ? <Spin size="large" /> : CPUInfo}
|
{this.state.loading ? <Spin size="large" /> : cpuUi}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
||||||
{this.state.loading ? <Spin size="large" /> : MemInfo}
|
{this.state.loading ? <Spin size="large" /> : memUi}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||||
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
||||||
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
|
GitHub: <a target="_blank" rel="noreferrer" href="https://github.com/casdoor/casdoor">Casdoor</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
|
{i18next.t("system:Version")}: <a target="_blank" rel="noreferrer" href={link}>{versionText}</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
|
{i18next.t("system:Official Website")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org">https://casdoor.org</a>
|
||||||
<br />
|
<br />
|
||||||
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
|
{i18next.t("system:Community")}: <a target="_blank" rel="noreferrer" href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">Get in Touch!</a>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Table} from "antd";
|
import {Button, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as TokenBackend from "./backend/TokenBackend";
|
import * as TokenBackend from "./backend/TokenBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class TokenListPage extends BaseListPage {
|
class TokenListPage extends BaseListPage {
|
||||||
newToken() {
|
newToken() {
|
||||||
@@ -201,12 +202,11 @@ class TokenListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/tokens/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/tokens/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteToken(index)}
|
onConfirm={() => this.deleteToken(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table, Upload} from "antd";
|
import {Button, Switch, Table, Upload} from "antd";
|
||||||
import {UploadOutlined} from "@ant-design/icons";
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
@@ -22,6 +22,7 @@ import * as Setting from "./Setting";
|
|||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class UserListPage extends BaseListPage {
|
class UserListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -341,13 +342,12 @@ class UserListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/users/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteUser(index)}
|
onConfirm={() => this.deleteUser(index)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button disabled={disabled} style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -384,7 +384,7 @@ class UserListPage extends BaseListPage {
|
|||||||
const field = params.searchedColumn, value = params.searchText;
|
const field = params.searchedColumn, value = params.searchText;
|
||||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
if (this.state.organizationName === undefined) {
|
if (this.props.match.params.organizationName === undefined) {
|
||||||
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
@@ -413,7 +413,7 @@ class UserListPage extends BaseListPage {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
UserBackend.getUsers(this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
UserBackend.getUsers(this.props.match.params.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import {Button, Popconfirm, Switch, Table} from "antd";
|
import {Button, Switch, Table} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as WebhookBackend from "./backend/WebhookBackend";
|
import * as WebhookBackend from "./backend/WebhookBackend";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BaseListPage from "./BaseListPage";
|
import BaseListPage from "./BaseListPage";
|
||||||
|
import PopconfirmModal from "./PopconfirmModal";
|
||||||
|
|
||||||
class WebhookListPage extends BaseListPage {
|
class WebhookListPage extends BaseListPage {
|
||||||
newWebhook() {
|
newWebhook() {
|
||||||
@@ -197,12 +198,11 @@ class WebhookListPage extends BaseListPage {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/webhooks/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<Popconfirm
|
<PopconfirmModal
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteWebhook(index)}
|
onConfirm={() => this.deleteWebhook(index)}
|
||||||
>
|
>
|
||||||
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
|
</PopconfirmModal>
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@@ -46,7 +46,6 @@ class LoginPage extends React.Component {
|
|||||||
username: null,
|
username: null,
|
||||||
validEmailOrPhone: false,
|
validEmailOrPhone: false,
|
||||||
validEmail: false,
|
validEmail: false,
|
||||||
validPhone: false,
|
|
||||||
loginMethod: "password",
|
loginMethod: "password",
|
||||||
enableCaptchaModal: false,
|
enableCaptchaModal: false,
|
||||||
openCaptchaModal: false,
|
openCaptchaModal: false,
|
||||||
@@ -427,16 +426,15 @@ class LoginPage extends React.Component {
|
|||||||
{
|
{
|
||||||
validator: (_, value) => {
|
validator: (_, value) => {
|
||||||
if (this.state.loginMethod === "verificationCode") {
|
if (this.state.loginMethod === "verificationCode") {
|
||||||
if (!Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
|
||||||
this.setState({validEmailOrPhone: false});
|
this.setState({validEmailOrPhone: false});
|
||||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Setting.isValidPhone(this.state.username)) {
|
if (Setting.isValidEmail(value)) {
|
||||||
this.setState({validPhone: true});
|
|
||||||
}
|
|
||||||
if (Setting.isValidEmail(this.state.username)) {
|
|
||||||
this.setState({validEmail: true});
|
this.setState({validEmail: true});
|
||||||
|
} else {
|
||||||
|
this.setState({validEmail: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -378,7 +378,7 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
|
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
|
||||||
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
|
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
|
||||||
|
|
||||||
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook" || provider.type === "DingTalk"
|
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
|
||||||
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|
||||||
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|
||||||
|| provider.type === "Bitbucket" || provider.type === "Box" || provider.type === "CloudFoundry" || provider.type === "Dailymotion"
|
|| provider.type === "Bitbucket" || provider.type === "Box" || provider.type === "CloudFoundry" || provider.type === "Dailymotion"
|
||||||
@@ -390,6 +390,8 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
|| provider.type === "Twitch" || provider.type === "Typetalk" || provider.type === "Uber" || provider.type === "VK" || provider.type === "Wepay"
|
|| provider.type === "Twitch" || provider.type === "Typetalk" || provider.type === "Uber" || provider.type === "VK" || provider.type === "Wepay"
|
||||||
|| provider.type === "Xero" || provider.type === "Yahoo" || provider.type === "Yammer" || provider.type === "Yandex" || provider.type === "Zoom") {
|
|| provider.type === "Xero" || provider.type === "Yahoo" || provider.type === "Yammer" || provider.type === "Yandex" || provider.type === "Zoom") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
||||||
|
} else if (provider.type === "DingTalk") {
|
||||||
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&prompt=consent&state=${state}`;
|
||||||
} else if (provider.type === "WeChat") {
|
} else if (provider.type === "WeChat") {
|
||||||
if (navigator.userAgent.includes("MicroMessenger")) {
|
if (navigator.userAgent.includes("MicroMessenger")) {
|
||||||
return `${authInfo[provider.type].mpEndpoint}?appid=${provider.clientId2}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].mpScope}&response_type=code#wechat_redirect`;
|
return `${authInfo[provider.type].mpEndpoint}?appid=${provider.clientId2}&redirect_uri=${redirectUri}&state=${state}&scope=${authInfo[provider.type].mpScope}&response_type=code#wechat_redirect`;
|
||||||
|
@@ -67,11 +67,10 @@ export function updateLdap(body) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLdapUser(body) {
|
export function getLdapUser(owner, name) {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-ldap-user`, {
|
return fetch(`${Setting.ServerUrl}/api/get-ldap-users?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "POST",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers: {
|
headers: {
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
},
|
},
|
||||||
@@ -88,14 +87,3 @@ export function syncUsers(owner, ldapId, body) {
|
|||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkLdapUsersExist(owner, body) {
|
|
||||||
return fetch(`${Setting.ServerUrl}/api/check-ldap-users-exist?owner=${owner}`, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers: {
|
|
||||||
"Accept-Language": Setting.getAcceptLanguage(),
|
|
||||||
},
|
|
||||||
}).then(res => res.json());
|
|
||||||
}
|
|
||||||
|
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
|
||||||
export function getSystemInfo(owner, name) {
|
export function getSystemInfo() {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-system-info?id=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/get-system-info`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -24,8 +24,8 @@ export function getSystemInfo(owner, name) {
|
|||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGitHubLatestReleaseVersion() {
|
export function getVersionInfo() {
|
||||||
return fetch(`${Setting.ServerUrl}/api/get-release`, {
|
return fetch(`${Setting.ServerUrl}/api/get-version-info`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
@@ -67,7 +67,7 @@ export const SendCodeInput = (props) => {
|
|||||||
onChange={e => onChange(e.target.value)}
|
onChange={e => onChange(e.target.value)}
|
||||||
enterButton={
|
enterButton={
|
||||||
<Button style={{fontSize: 14}} type={"primary"} disabled={disabled || buttonLeftTime > 0} loading={buttonLoading}>
|
<Button style={{fontSize: 14}} type={"primary"} disabled={disabled || buttonLeftTime > 0} loading={buttonLoading}>
|
||||||
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending Code") : i18next.t("code:Send Code")}
|
{buttonLeftTime > 0 ? `${buttonLeftTime} s` : buttonLoading ? i18next.t("code:Sending") : i18next.t("code:Send Code")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
onSearch={() => setVisible(true)}
|
onSearch={() => setVisible(true)}
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Bitte geben Sie Ihren Telefon-Verifizierungscode ein!",
|
"Please input your phone verification code!": "Bitte geben Sie Ihren Telefon-Verifizierungscode ein!",
|
||||||
"Please input your verification code!": "Bitte geben Sie Ihren Bestätigungscode ein!",
|
"Please input your verification code!": "Bitte geben Sie Ihren Bestätigungscode ein!",
|
||||||
"Send Code": "Code senden",
|
"Send Code": "Code senden",
|
||||||
"Sending Code": "Code wird gesendet",
|
"Sending": "Code wird gesendet",
|
||||||
"Submit and complete": "Absenden und abschließen"
|
"Submit and complete": "Absenden und abschließen"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "KN",
|
"CN": "KN",
|
||||||
"Edit LDAP": "LDAP bearbeiten",
|
"Edit LDAP": "LDAP bearbeiten",
|
||||||
"Email": "E-Mail",
|
"Email": "E-Mail",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Gruppen Id",
|
"Group Id": "Gruppen Id",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Letzter Sync",
|
"Last Sync": "Letzter Sync",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||||
"Please input your verification code!": "Please input your verification code!",
|
"Please input your verification code!": "Please input your verification code!",
|
||||||
"Send Code": "Send Code",
|
"Send Code": "Send Code",
|
||||||
"Sending Code": "Sending Code",
|
"Sending": "Sending",
|
||||||
"Submit and complete": "Submit and complete"
|
"Submit and complete": "Submit and complete"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "Edit LDAP",
|
"Edit LDAP": "Edit LDAP",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Group Id",
|
"Group Id": "Group Id",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Last Sync",
|
"Last Sync": "Last Sync",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Ingrese su código de verificación teléfonico!",
|
"Please input your phone verification code!": "Ingrese su código de verificación teléfonico!",
|
||||||
"Please input your verification code!": "Ingrese su código de verificación!",
|
"Please input your verification code!": "Ingrese su código de verificación!",
|
||||||
"Send Code": "Enviar código",
|
"Send Code": "Enviar código",
|
||||||
"Sending Code": "Enviando código",
|
"Sending": "Enviando código",
|
||||||
"Submit and complete": "Enviar y completar"
|
"Submit and complete": "Enviar y completar"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "Editar LDAP",
|
"Edit LDAP": "Editar LDAP",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Group Id",
|
"Group Id": "Group Id",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Última Sincronización",
|
"Last Sync": "Última Sincronización",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Veuillez entrer le code de vérification de votre téléphone !",
|
"Please input your phone verification code!": "Veuillez entrer le code de vérification de votre téléphone !",
|
||||||
"Please input your verification code!": "Veuillez entrer votre code de vérification !",
|
"Please input your verification code!": "Veuillez entrer votre code de vérification !",
|
||||||
"Send Code": "Envoyer le code",
|
"Send Code": "Envoyer le code",
|
||||||
"Sending Code": "Code d'envoi",
|
"Sending": "Code d'envoi",
|
||||||
"Submit and complete": "Soumettre et compléter"
|
"Submit and complete": "Soumettre et compléter"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "Modifier LDAP",
|
"Edit LDAP": "Modifier LDAP",
|
||||||
"Email": "Courriel",
|
"Email": "Courriel",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Identifiant du groupe",
|
"Group Id": "Identifiant du groupe",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Dernière synchronisation",
|
"Last Sync": "Dernière synchronisation",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "電話番号を入力してください!",
|
"Please input your phone verification code!": "電話番号を入力してください!",
|
||||||
"Please input your verification code!": "認証コードを入力してください!",
|
"Please input your verification code!": "認証コードを入力してください!",
|
||||||
"Send Code": "コードを送信",
|
"Send Code": "コードを送信",
|
||||||
"Sending Code": "コードを送信中",
|
"Sending": "コードを送信中",
|
||||||
"Submit and complete": "提出して完了"
|
"Submit and complete": "提出して完了"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "LDAP を編集",
|
"Edit LDAP": "LDAP を編集",
|
||||||
"Email": "Eメールアドレス",
|
"Email": "Eメールアドレス",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "グループ ID",
|
"Group Id": "グループ ID",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "前回の同期",
|
"Last Sync": "前回の同期",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||||
"Please input your verification code!": "Please input your verification code!",
|
"Please input your verification code!": "Please input your verification code!",
|
||||||
"Send Code": "Send Code",
|
"Send Code": "Send Code",
|
||||||
"Sending Code": "Sending Code",
|
"Sending": "Sending",
|
||||||
"Submit and complete": "Submit and complete"
|
"Submit and complete": "Submit and complete"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "Edit LDAP",
|
"Edit LDAP": "Edit LDAP",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Group Id",
|
"Group Id": "Group Id",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Last Sync",
|
"Last Sync": "Last Sync",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Пожалуйста, введите код подтверждения!",
|
"Please input your phone verification code!": "Пожалуйста, введите код подтверждения!",
|
||||||
"Please input your verification code!": "Пожалуйста, введите код подтверждения!",
|
"Please input your verification code!": "Пожалуйста, введите код подтверждения!",
|
||||||
"Send Code": "Отправить код",
|
"Send Code": "Отправить код",
|
||||||
"Sending Code": "Отправка кода",
|
"Sending": "Отправка кода",
|
||||||
"Submit and complete": "Отправить и завершить"
|
"Submit and complete": "Отправить и завершить"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "КНР",
|
"CN": "КНР",
|
||||||
"Edit LDAP": "Редактировать LDAP",
|
"Edit LDAP": "Редактировать LDAP",
|
||||||
"Email": "Почта",
|
"Email": "Почта",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "ID группы",
|
"Group Id": "ID группы",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Последняя синхронизация",
|
"Last Sync": "Последняя синхронизация",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "Please input your phone verification code!",
|
"Please input your phone verification code!": "Please input your phone verification code!",
|
||||||
"Please input your verification code!": "Please input your verification code!",
|
"Please input your verification code!": "Please input your verification code!",
|
||||||
"Send Code": "Send Code",
|
"Send Code": "Send Code",
|
||||||
"Sending Code": "Sending Code",
|
"Sending": "Sending",
|
||||||
"Submit and complete": "Submit and complete"
|
"Submit and complete": "Submit and complete"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "Edit LDAP",
|
"Edit LDAP": "Edit LDAP",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
|
"Enable SSL": "Enable SSL",
|
||||||
|
"Enable SSL - Tooltip": "Enable SSL - Tooltip",
|
||||||
"Group Id": "Group Id",
|
"Group Id": "Group Id",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "Last Sync",
|
"Last Sync": "Last Sync",
|
||||||
|
@@ -121,7 +121,7 @@
|
|||||||
"Please input your phone verification code!": "请输入您的手机验证码!",
|
"Please input your phone verification code!": "请输入您的手机验证码!",
|
||||||
"Please input your verification code!": "请输入您的验证码!",
|
"Please input your verification code!": "请输入您的验证码!",
|
||||||
"Send Code": "发送验证码",
|
"Send Code": "发送验证码",
|
||||||
"Sending Code": "发送中",
|
"Sending": "发送中",
|
||||||
"Submit and complete": "完成提交"
|
"Submit and complete": "完成提交"
|
||||||
},
|
},
|
||||||
"forget": {
|
"forget": {
|
||||||
@@ -288,6 +288,8 @@
|
|||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
"Edit LDAP": "编辑LDAP",
|
"Edit LDAP": "编辑LDAP",
|
||||||
"Email": "电子邮件",
|
"Email": "电子邮件",
|
||||||
|
"Enable SSL": "启用 SSL",
|
||||||
|
"Enable SSL - Tooltip": "启用 SSL",
|
||||||
"Group Id": "组ID",
|
"Group Id": "组ID",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"Last Sync": "最近同步",
|
"Last Sync": "最近同步",
|
||||||
|
Reference in New Issue
Block a user