mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 01:13:41 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
8698f4111a | |||
fdccb8b22b | |||
19e7d0b0bd | |||
f6a502f7ff | |||
b34e16b145 | |||
11b56c340f | |||
cc6ea1b60e | |||
95b32d5ebf | |||
b47baa06e1 | |||
24a824d394 | |||
75b8357de8 | |||
087405dad2 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -79,7 +79,7 @@ jobs:
|
|||||||
e2e:
|
e2e:
|
||||||
name: e2e-test
|
name: e2e-test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ frontend, backend, linter ]
|
needs: [ go-tests ]
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
|
@ -160,7 +160,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
|
|
||||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
if method == "POST" {
|
if method == "POST" {
|
||||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" {
|
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" {
|
||||||
return true
|
return true
|
||||||
} else if urlPath == "/api/update-user" {
|
} else if urlPath == "/api/update-user" {
|
||||||
// Allow ordinary users to update their own information
|
// Allow ordinary users to update their own information
|
||||||
|
@ -174,13 +174,36 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" || providerType == "Line" {
|
if isProxyProviderType(providerType) {
|
||||||
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
||||||
} else {
|
} else {
|
||||||
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isProxyProviderType(providerType string) bool {
|
||||||
|
providerTypes := []string{
|
||||||
|
"GitHub",
|
||||||
|
"Google",
|
||||||
|
"Facebook",
|
||||||
|
"LinkedIn",
|
||||||
|
"Steam",
|
||||||
|
"Line",
|
||||||
|
"Amazon",
|
||||||
|
"Instagram",
|
||||||
|
"TikTok",
|
||||||
|
"Twitter",
|
||||||
|
"Uber",
|
||||||
|
"Yahoo",
|
||||||
|
}
|
||||||
|
for _, v := range providerTypes {
|
||||||
|
if strings.EqualFold(v, providerType) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Login ...
|
// Login ...
|
||||||
// @Title Login
|
// @Title Login
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
|
@ -105,14 +105,34 @@ func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
|||||||
}
|
}
|
||||||
for i := 0; i < len(users); i++ {
|
for i := 0; i < len(users); i++ {
|
||||||
user := users[i]
|
user := users[i]
|
||||||
dn := fmt.Sprintf("cn=%s,%s", user.DisplayName, string(r.BaseObject()))
|
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||||
e := ldapserver.NewSearchResultEntry(dn)
|
e := ldapserver.NewSearchResultEntry(dn)
|
||||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||||
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
||||||
e.AddAttribute("email", message.AttributeValue(user.Email))
|
e.AddAttribute("email", message.AttributeValue(user.Email))
|
||||||
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
||||||
|
e.AddAttribute("userPassword", message.AttributeValue(getUserPasswordWithType(user)))
|
||||||
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
||||||
w.Write(e)
|
w.Write(e)
|
||||||
}
|
}
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get user password with hash type prefix
|
||||||
|
// TODO not handle salt yet
|
||||||
|
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||||
|
func getUserPasswordWithType(user *object.User) string {
|
||||||
|
org := object.GetOrganizationByUser(user)
|
||||||
|
if org.PasswordType == "" || org.PasswordType == "plain" {
|
||||||
|
return user.Password
|
||||||
|
}
|
||||||
|
prefix := org.PasswordType
|
||||||
|
if prefix == "salt" {
|
||||||
|
prefix = "sha256"
|
||||||
|
} else if prefix == "md5-salt" {
|
||||||
|
prefix = "md5"
|
||||||
|
} else if prefix == "pbkdf2-salt" {
|
||||||
|
prefix = "pbkdf2"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||||
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (c *ApiController) UpdateModel() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
|
c.Data["json"] = wrapErrorResponse(object.UpdateModelWithCheck(id, &model))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
183
idp/goth.go
183
idp/goth.go
@ -25,30 +25,59 @@ import (
|
|||||||
"github.com/markbates/goth"
|
"github.com/markbates/goth"
|
||||||
"github.com/markbates/goth/providers/amazon"
|
"github.com/markbates/goth/providers/amazon"
|
||||||
"github.com/markbates/goth/providers/apple"
|
"github.com/markbates/goth/providers/apple"
|
||||||
|
"github.com/markbates/goth/providers/auth0"
|
||||||
"github.com/markbates/goth/providers/azureadv2"
|
"github.com/markbates/goth/providers/azureadv2"
|
||||||
|
"github.com/markbates/goth/providers/battlenet"
|
||||||
"github.com/markbates/goth/providers/bitbucket"
|
"github.com/markbates/goth/providers/bitbucket"
|
||||||
|
"github.com/markbates/goth/providers/box"
|
||||||
|
"github.com/markbates/goth/providers/cloudfoundry"
|
||||||
|
"github.com/markbates/goth/providers/dailymotion"
|
||||||
|
"github.com/markbates/goth/providers/deezer"
|
||||||
"github.com/markbates/goth/providers/digitalocean"
|
"github.com/markbates/goth/providers/digitalocean"
|
||||||
"github.com/markbates/goth/providers/discord"
|
"github.com/markbates/goth/providers/discord"
|
||||||
"github.com/markbates/goth/providers/dropbox"
|
"github.com/markbates/goth/providers/dropbox"
|
||||||
|
"github.com/markbates/goth/providers/eveonline"
|
||||||
"github.com/markbates/goth/providers/facebook"
|
"github.com/markbates/goth/providers/facebook"
|
||||||
|
"github.com/markbates/goth/providers/fitbit"
|
||||||
"github.com/markbates/goth/providers/gitea"
|
"github.com/markbates/goth/providers/gitea"
|
||||||
"github.com/markbates/goth/providers/github"
|
"github.com/markbates/goth/providers/github"
|
||||||
"github.com/markbates/goth/providers/gitlab"
|
"github.com/markbates/goth/providers/gitlab"
|
||||||
"github.com/markbates/goth/providers/google"
|
"github.com/markbates/goth/providers/google"
|
||||||
"github.com/markbates/goth/providers/heroku"
|
"github.com/markbates/goth/providers/heroku"
|
||||||
|
"github.com/markbates/goth/providers/influxcloud"
|
||||||
"github.com/markbates/goth/providers/instagram"
|
"github.com/markbates/goth/providers/instagram"
|
||||||
|
"github.com/markbates/goth/providers/intercom"
|
||||||
"github.com/markbates/goth/providers/kakao"
|
"github.com/markbates/goth/providers/kakao"
|
||||||
|
"github.com/markbates/goth/providers/lastfm"
|
||||||
"github.com/markbates/goth/providers/line"
|
"github.com/markbates/goth/providers/line"
|
||||||
"github.com/markbates/goth/providers/linkedin"
|
"github.com/markbates/goth/providers/linkedin"
|
||||||
|
"github.com/markbates/goth/providers/mailru"
|
||||||
|
"github.com/markbates/goth/providers/meetup"
|
||||||
"github.com/markbates/goth/providers/microsoftonline"
|
"github.com/markbates/goth/providers/microsoftonline"
|
||||||
|
"github.com/markbates/goth/providers/naver"
|
||||||
|
"github.com/markbates/goth/providers/nextcloud"
|
||||||
|
"github.com/markbates/goth/providers/onedrive"
|
||||||
|
"github.com/markbates/goth/providers/oura"
|
||||||
|
"github.com/markbates/goth/providers/patreon"
|
||||||
"github.com/markbates/goth/providers/paypal"
|
"github.com/markbates/goth/providers/paypal"
|
||||||
"github.com/markbates/goth/providers/salesforce"
|
"github.com/markbates/goth/providers/salesforce"
|
||||||
"github.com/markbates/goth/providers/shopify"
|
"github.com/markbates/goth/providers/shopify"
|
||||||
"github.com/markbates/goth/providers/slack"
|
"github.com/markbates/goth/providers/slack"
|
||||||
|
"github.com/markbates/goth/providers/soundcloud"
|
||||||
|
"github.com/markbates/goth/providers/spotify"
|
||||||
"github.com/markbates/goth/providers/steam"
|
"github.com/markbates/goth/providers/steam"
|
||||||
|
"github.com/markbates/goth/providers/strava"
|
||||||
|
"github.com/markbates/goth/providers/stripe"
|
||||||
|
"github.com/markbates/goth/providers/tiktok"
|
||||||
"github.com/markbates/goth/providers/tumblr"
|
"github.com/markbates/goth/providers/tumblr"
|
||||||
"github.com/markbates/goth/providers/twitter"
|
"github.com/markbates/goth/providers/twitch"
|
||||||
|
"github.com/markbates/goth/providers/twitterv2"
|
||||||
|
"github.com/markbates/goth/providers/typetalk"
|
||||||
|
"github.com/markbates/goth/providers/uber"
|
||||||
|
"github.com/markbates/goth/providers/wepay"
|
||||||
|
"github.com/markbates/goth/providers/xero"
|
||||||
"github.com/markbates/goth/providers/yahoo"
|
"github.com/markbates/goth/providers/yahoo"
|
||||||
|
"github.com/markbates/goth/providers/yammer"
|
||||||
"github.com/markbates/goth/providers/yandex"
|
"github.com/markbates/goth/providers/yandex"
|
||||||
"github.com/markbates/goth/providers/zoom"
|
"github.com/markbates/goth/providers/zoom"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -77,11 +106,41 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: "common"}),
|
Provider: azureadv2.New(clientId, clientSecret, redirectUrl, azureadv2.ProviderOptions{Tenant: "common"}),
|
||||||
Session: &azureadv2.Session{},
|
Session: &azureadv2.Session{},
|
||||||
}
|
}
|
||||||
|
case "Auth0":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: auth0.New(clientId, clientSecret, redirectUrl, "casdoor.auth0.com"),
|
||||||
|
Session: &auth0.Session{},
|
||||||
|
}
|
||||||
|
case "BattleNet":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: battlenet.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &battlenet.Session{},
|
||||||
|
}
|
||||||
case "Bitbucket":
|
case "Bitbucket":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: bitbucket.New(clientId, clientSecret, redirectUrl),
|
Provider: bitbucket.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &bitbucket.Session{},
|
Session: &bitbucket.Session{},
|
||||||
}
|
}
|
||||||
|
case "Box":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: box.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &box.Session{},
|
||||||
|
}
|
||||||
|
case "CloudFoundry":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: cloudfoundry.New("", clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &cloudfoundry.Session{},
|
||||||
|
}
|
||||||
|
case "Dailymotion":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: dailymotion.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &dailymotion.Session{},
|
||||||
|
}
|
||||||
|
case "Deezer":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: deezer.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &deezer.Session{},
|
||||||
|
}
|
||||||
case "DigitalOcean":
|
case "DigitalOcean":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: digitalocean.New(clientId, clientSecret, redirectUrl),
|
Provider: digitalocean.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -97,6 +156,16 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: dropbox.New(clientId, clientSecret, redirectUrl),
|
Provider: dropbox.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &dropbox.Session{},
|
Session: &dropbox.Session{},
|
||||||
}
|
}
|
||||||
|
case "EveOnline":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: eveonline.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &eveonline.Session{},
|
||||||
|
}
|
||||||
|
case "Fitbit":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: fitbit.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &fitbit.Session{},
|
||||||
|
}
|
||||||
case "Facebook":
|
case "Facebook":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: facebook.New(clientId, clientSecret, redirectUrl),
|
Provider: facebook.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -127,16 +196,31 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: heroku.New(clientId, clientSecret, redirectUrl),
|
Provider: heroku.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &heroku.Session{},
|
Session: &heroku.Session{},
|
||||||
}
|
}
|
||||||
|
case "InfluxCloud":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: influxcloud.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &influxcloud.Session{},
|
||||||
|
}
|
||||||
case "Instagram":
|
case "Instagram":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: instagram.New(clientId, clientSecret, redirectUrl),
|
Provider: instagram.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &instagram.Session{},
|
Session: &instagram.Session{},
|
||||||
}
|
}
|
||||||
|
case "Intercom":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: intercom.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &intercom.Session{},
|
||||||
|
}
|
||||||
case "Kakao":
|
case "Kakao":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: kakao.New(clientId, clientSecret, redirectUrl),
|
Provider: kakao.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &kakao.Session{},
|
Session: &kakao.Session{},
|
||||||
}
|
}
|
||||||
|
case "Lastfm":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: lastfm.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &lastfm.Session{},
|
||||||
|
}
|
||||||
case "Linkedin":
|
case "Linkedin":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: linkedin.New(clientId, clientSecret, redirectUrl),
|
Provider: linkedin.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -147,11 +231,46 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: line.New(clientId, clientSecret, redirectUrl),
|
Provider: line.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &line.Session{},
|
Session: &line.Session{},
|
||||||
}
|
}
|
||||||
|
case "Mailru":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: mailru.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &mailru.Session{},
|
||||||
|
}
|
||||||
|
case "Meetup":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: meetup.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &meetup.Session{},
|
||||||
|
}
|
||||||
case "MicrosoftOnline":
|
case "MicrosoftOnline":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: microsoftonline.New(clientId, clientSecret, redirectUrl),
|
Provider: microsoftonline.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: µsoftonline.Session{},
|
Session: µsoftonline.Session{},
|
||||||
}
|
}
|
||||||
|
case "Naver":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: naver.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &naver.Session{},
|
||||||
|
}
|
||||||
|
case "Nextcloud":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: nextcloud.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &nextcloud.Session{},
|
||||||
|
}
|
||||||
|
case "OneDrive":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: onedrive.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &onedrive.Session{},
|
||||||
|
}
|
||||||
|
case "Oura":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: oura.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &oura.Session{},
|
||||||
|
}
|
||||||
|
case "Patreon":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: patreon.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &patreon.Session{},
|
||||||
|
}
|
||||||
case "Paypal":
|
case "Paypal":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: paypal.New(clientId, clientSecret, redirectUrl),
|
Provider: paypal.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -172,26 +291,81 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: slack.New(clientId, clientSecret, redirectUrl),
|
Provider: slack.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &slack.Session{},
|
Session: &slack.Session{},
|
||||||
}
|
}
|
||||||
|
case "Soundcloud":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: soundcloud.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &soundcloud.Session{},
|
||||||
|
}
|
||||||
|
case "Spotify":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: spotify.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &spotify.Session{},
|
||||||
|
}
|
||||||
case "Steam":
|
case "Steam":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: steam.New(clientSecret, redirectUrl),
|
Provider: steam.New(clientSecret, redirectUrl),
|
||||||
Session: &steam.Session{},
|
Session: &steam.Session{},
|
||||||
}
|
}
|
||||||
|
case "Strava":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: strava.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &strava.Session{},
|
||||||
|
}
|
||||||
|
case "Stripe":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: stripe.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &stripe.Session{},
|
||||||
|
}
|
||||||
|
case "TikTok":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: tiktok.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &tiktok.Session{},
|
||||||
|
}
|
||||||
case "Tumblr":
|
case "Tumblr":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
|
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &tumblr.Session{},
|
Session: &tumblr.Session{},
|
||||||
}
|
}
|
||||||
|
case "Twitch":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: twitch.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &twitch.Session{},
|
||||||
|
}
|
||||||
case "Twitter":
|
case "Twitter":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: twitter.New(clientId, clientSecret, redirectUrl),
|
Provider: twitterv2.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &twitter.Session{},
|
Session: &twitterv2.Session{},
|
||||||
|
}
|
||||||
|
case "Typetalk":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: typetalk.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &typetalk.Session{},
|
||||||
|
}
|
||||||
|
case "Uber":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: uber.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &uber.Session{},
|
||||||
|
}
|
||||||
|
case "Wepay":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: wepay.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &wepay.Session{},
|
||||||
|
}
|
||||||
|
case "Xero":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: xero.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &xero.Session{},
|
||||||
}
|
}
|
||||||
case "Yahoo":
|
case "Yahoo":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: yahoo.New(clientId, clientSecret, redirectUrl),
|
Provider: yahoo.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &yahoo.Session{},
|
Session: &yahoo.Session{},
|
||||||
}
|
}
|
||||||
|
case "Yammer":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: yammer.New(clientId, clientSecret, redirectUrl),
|
||||||
|
Session: &yammer.Session{},
|
||||||
|
}
|
||||||
case "Yandex":
|
case "Yandex":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: yandex.New(clientId, clientSecret, redirectUrl),
|
Provider: yandex.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -232,6 +406,9 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
// to call the function to obtain accessToken
|
// to call the function to obtain accessToken
|
||||||
value = url.Values{}
|
value = url.Values{}
|
||||||
value.Add("code", code)
|
value.Add("code", code)
|
||||||
|
if idp.Provider.Name() == "twitterv2" || idp.Provider.Name() == "fitbit" {
|
||||||
|
value.Add("oauth_verifier", "casdoor-verifier")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -98,7 +98,61 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var gothList = []string{"Apple", "AzureAD", "Slack", "Steam", "Line"}
|
var gothList = []string{
|
||||||
|
"Apple",
|
||||||
|
"AzureAD",
|
||||||
|
"Slack",
|
||||||
|
"Steam",
|
||||||
|
"Line",
|
||||||
|
"Amazon",
|
||||||
|
"Auth0",
|
||||||
|
"BattleNet",
|
||||||
|
"Bitbucket",
|
||||||
|
"Box",
|
||||||
|
"CloudFoundry",
|
||||||
|
"Dailymotion",
|
||||||
|
"Deezer",
|
||||||
|
"DigitalOcean",
|
||||||
|
"Discord",
|
||||||
|
"Dropbox",
|
||||||
|
"EveOnline",
|
||||||
|
"Fitbit",
|
||||||
|
"Gitea",
|
||||||
|
"Heroku",
|
||||||
|
"InfluxCloud",
|
||||||
|
"Instagram",
|
||||||
|
"Intercom",
|
||||||
|
"Kakao",
|
||||||
|
"Lastfm",
|
||||||
|
"Mailru",
|
||||||
|
"Meetup",
|
||||||
|
"MicrosoftOnline",
|
||||||
|
"Naver",
|
||||||
|
"Nextcloud",
|
||||||
|
"OneDrive",
|
||||||
|
"Oura",
|
||||||
|
"Patreon",
|
||||||
|
"Paypal",
|
||||||
|
"SalesForce",
|
||||||
|
"Shopify",
|
||||||
|
"Soundcloud",
|
||||||
|
"Spotify",
|
||||||
|
"Strava",
|
||||||
|
"Stripe",
|
||||||
|
"TikTok",
|
||||||
|
"Tumblr",
|
||||||
|
"Twitch",
|
||||||
|
"Twitter",
|
||||||
|
"Typetalk",
|
||||||
|
"Uber",
|
||||||
|
"VK",
|
||||||
|
"Wepay",
|
||||||
|
"Xero",
|
||||||
|
"Yahoo",
|
||||||
|
"Yammer",
|
||||||
|
"Yandex",
|
||||||
|
"Zoom",
|
||||||
|
}
|
||||||
|
|
||||||
func isGothSupport(provider string) bool {
|
func isGothSupport(provider string) bool {
|
||||||
for _, value := range gothList {
|
for _, value := range gothList {
|
||||||
|
@ -57,23 +57,24 @@ type Application struct {
|
|||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||||
ExpireInHours int `json:"expireInHours"`
|
ExpireInHours int `json:"expireInHours"`
|
||||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||||
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
|
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
|
||||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||||
FormCss string `xorm:"text" json:"formCss"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
FormOffset int `json:"formOffset"`
|
FormCss string `xorm:"text" json:"formCss"`
|
||||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
FormOffset int `json:"formOffset"`
|
||||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||||
|
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApplicationCount(owner, field, value string) int {
|
func GetApplicationCount(owner, field, value string) int {
|
||||||
|
@ -86,6 +86,17 @@ func GetModel(id string) *Model {
|
|||||||
return getModel(owner, name)
|
return getModel(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateModelWithCheck(id string, modelObj *Model) error {
|
||||||
|
// check model grammar
|
||||||
|
_, err := model.NewModelFromString(modelObj.ModelText)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
UpdateModel(id, modelObj)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateModel(id string, modelObj *Model) bool {
|
func UpdateModel(id string, modelObj *Model) bool {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if getModel(owner, name) == nil {
|
if getModel(owner, name) == nil {
|
||||||
@ -98,11 +109,6 @@ func UpdateModel(id string, modelObj *Model) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check model grammar
|
|
||||||
_, err := model.NewModelFromString(modelObj.ModelText)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj)
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,25 +31,34 @@ type AccountItem struct {
|
|||||||
ModifyRule string `json:"modifyRule"`
|
ModifyRule string `json:"modifyRule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThemeData struct {
|
||||||
|
ThemeType string `xorm:"varchar(30)" json:"themeType"`
|
||||||
|
ColorPrimary string `xorm:"varchar(10)" json:"colorPrimary"`
|
||||||
|
BorderRadius int `xorm:"int" json:"borderRadius"`
|
||||||
|
IsCompact bool `xorm:"bool" json:"isCompact"`
|
||||||
|
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
InitScore int `json:"initScore"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
InitScore int `json:"initScore"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
@ -37,56 +37,57 @@ type UserShort struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserWithoutThirdIdp struct {
|
type UserWithoutThirdIdp struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||||
Id string `xorm:"varchar(100) index" json:"id"`
|
Id string `xorm:"varchar(100) index" json:"id"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||||
Email string `xorm:"varchar(100) index" json:"email"`
|
Email string `xorm:"varchar(100) index" json:"email"`
|
||||||
EmailVerified bool `json:"emailVerified"`
|
EmailVerified bool `json:"emailVerified"`
|
||||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||||
Title string `xorm:"varchar(100)" json:"title"`
|
Title string `xorm:"varchar(100)" json:"title"`
|
||||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Region string `xorm:"varchar(100)" json:"region"`
|
Region string `xorm:"varchar(100)" json:"region"`
|
||||||
Language string `xorm:"varchar(100)" json:"language"`
|
Language string `xorm:"varchar(100)" json:"language"`
|
||||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||||
Education string `xorm:"varchar(100)" json:"education"`
|
Education string `xorm:"varchar(100)" json:"education"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
Karma int `json:"karma"`
|
Karma int `json:"karma"`
|
||||||
Ranking int `json:"ranking"`
|
Ranking int `json:"ranking"`
|
||||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||||
IsOnline bool `json:"isOnline"`
|
IsOnline bool `json:"isOnline"`
|
||||||
IsAdmin bool `json:"isAdmin"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
IsGlobalAdmin bool `json:"isGlobalAdmin"`
|
IsGlobalAdmin bool `json:"isGlobalAdmin"`
|
||||||
IsForbidden bool `json:"isForbidden"`
|
IsForbidden bool `json:"isForbidden"`
|
||||||
IsDeleted bool `json:"isDeleted"`
|
IsDeleted bool `json:"isDeleted"`
|
||||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||||
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
||||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
Roles []*Role `xorm:"-" json:"roles"`
|
Properties map[string]string `json:"properties"`
|
||||||
Permissions []*Permission `xorm:"-" json:"permissions"`
|
Roles []*Role `xorm:"-" json:"roles"`
|
||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
Permissions []*Permission `xorm:"-" json:"permissions"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
@ -116,57 +117,64 @@ func getShortUser(user *User) *UserShort {
|
|||||||
|
|
||||||
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||||
res := &UserWithoutThirdIdp{
|
res := &UserWithoutThirdIdp{
|
||||||
Owner: user.Owner,
|
Owner: user.Owner,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
CreatedTime: user.CreatedTime,
|
CreatedTime: user.CreatedTime,
|
||||||
UpdatedTime: user.UpdatedTime,
|
UpdatedTime: user.UpdatedTime,
|
||||||
Id: user.Id,
|
|
||||||
Type: user.Type,
|
Id: user.Id,
|
||||||
Password: user.Password,
|
Type: user.Type,
|
||||||
PasswordSalt: user.PasswordSalt,
|
Password: user.Password,
|
||||||
DisplayName: user.DisplayName,
|
PasswordSalt: user.PasswordSalt,
|
||||||
FirstName: user.FirstName,
|
DisplayName: user.DisplayName,
|
||||||
LastName: user.LastName,
|
FirstName: user.FirstName,
|
||||||
Avatar: user.Avatar,
|
LastName: user.LastName,
|
||||||
PermanentAvatar: user.PermanentAvatar,
|
Avatar: user.Avatar,
|
||||||
Email: user.Email,
|
PermanentAvatar: user.PermanentAvatar,
|
||||||
EmailVerified: user.EmailVerified,
|
Email: user.Email,
|
||||||
Phone: user.Phone,
|
EmailVerified: user.EmailVerified,
|
||||||
Location: user.Location,
|
Phone: user.Phone,
|
||||||
Address: user.Address,
|
Location: user.Location,
|
||||||
Affiliation: user.Affiliation,
|
Address: user.Address,
|
||||||
Title: user.Title,
|
Affiliation: user.Affiliation,
|
||||||
IdCardType: user.IdCardType,
|
Title: user.Title,
|
||||||
IdCard: user.IdCard,
|
IdCardType: user.IdCardType,
|
||||||
Homepage: user.Homepage,
|
IdCard: user.IdCard,
|
||||||
Bio: user.Bio,
|
Homepage: user.Homepage,
|
||||||
Tag: user.Tag,
|
Bio: user.Bio,
|
||||||
Region: user.Region,
|
Tag: user.Tag,
|
||||||
Language: user.Language,
|
Region: user.Region,
|
||||||
Gender: user.Gender,
|
Language: user.Language,
|
||||||
Birthday: user.Birthday,
|
Gender: user.Gender,
|
||||||
Education: user.Education,
|
Birthday: user.Birthday,
|
||||||
Score: user.Score,
|
Education: user.Education,
|
||||||
Karma: user.Karma,
|
Score: user.Score,
|
||||||
Ranking: user.Ranking,
|
Karma: user.Karma,
|
||||||
IsDefaultAvatar: user.IsDefaultAvatar,
|
Ranking: user.Ranking,
|
||||||
IsOnline: user.IsOnline,
|
IsDefaultAvatar: user.IsDefaultAvatar,
|
||||||
IsAdmin: user.IsAdmin,
|
IsOnline: user.IsOnline,
|
||||||
IsGlobalAdmin: user.IsGlobalAdmin,
|
IsAdmin: user.IsAdmin,
|
||||||
IsForbidden: user.IsForbidden,
|
IsGlobalAdmin: user.IsGlobalAdmin,
|
||||||
IsDeleted: user.IsDeleted,
|
IsForbidden: user.IsForbidden,
|
||||||
SignupApplication: user.SignupApplication,
|
IsDeleted: user.IsDeleted,
|
||||||
Hash: user.Hash,
|
SignupApplication: user.SignupApplication,
|
||||||
PreHash: user.PreHash,
|
Hash: user.Hash,
|
||||||
CreatedIp: user.CreatedIp,
|
PreHash: user.PreHash,
|
||||||
LastSigninTime: user.LastSigninTime,
|
|
||||||
LastSigninIp: user.LastSigninIp,
|
CreatedIp: user.CreatedIp,
|
||||||
Ldap: user.Ldap,
|
LastSigninTime: user.LastSigninTime,
|
||||||
Roles: user.Roles,
|
LastSigninIp: user.LastSigninIp,
|
||||||
Permissions: user.Permissions,
|
|
||||||
|
Ldap: user.Ldap,
|
||||||
|
Properties: user.Properties,
|
||||||
|
|
||||||
|
Roles: user.Roles,
|
||||||
|
Permissions: user.Permissions,
|
||||||
|
|
||||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||||
SigninWrongTimes: user.SigninWrongTimes,
|
SigninWrongTimes: user.SigninWrongTimes,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,12 +201,32 @@ func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refineUser(user *User) *User {
|
||||||
|
user.Password = ""
|
||||||
|
|
||||||
|
if user.Address == nil {
|
||||||
|
user.Address = []string{}
|
||||||
|
}
|
||||||
|
if user.Properties == nil {
|
||||||
|
user.Properties = map[string]string{}
|
||||||
|
}
|
||||||
|
if user.Roles == nil {
|
||||||
|
user.Roles = []*Role{}
|
||||||
|
}
|
||||||
|
if user.Permissions == nil {
|
||||||
|
user.Permissions = []*Permission{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
|
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
|
|
||||||
user.Password = ""
|
user = refineUser(user)
|
||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
name := util.GenerateId()
|
name := util.GenerateId()
|
||||||
|
100
object/user.go
100
object/user.go
@ -78,32 +78,80 @@ type User struct {
|
|||||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||||
|
|
||||||
GitHub string `xorm:"github varchar(100)" json:"github"`
|
GitHub string `xorm:"github varchar(100)" json:"github"`
|
||||||
Google string `xorm:"varchar(100)" json:"google"`
|
Google string `xorm:"varchar(100)" json:"google"`
|
||||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
||||||
Line string `xorm:"line varchar(100)" json:"line"`
|
Line string `xorm:"line varchar(100)" json:"line"`
|
||||||
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
||||||
|
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
||||||
|
BattleNet string `xorm:"battlenet varchar(100)" json:"battlenet"`
|
||||||
|
Bitbucket string `xorm:"bitbucket varchar(100)" json:"bitbucket"`
|
||||||
|
Box string `xorm:"box varchar(100)" json:"box"`
|
||||||
|
CloudFoundry string `xorm:"cloudfoundry varchar(100)" json:"cloudfoundry"`
|
||||||
|
Dailymotion string `xorm:"dailymotion varchar(100)" json:"dailymotion"`
|
||||||
|
Deezer string `xorm:"deezer varchar(100)" json:"deezer"`
|
||||||
|
DigitalOcean string `xorm:"digitalocean varchar(100)" json:"digitalocean"`
|
||||||
|
Discord string `xorm:"discord varchar(100)" json:"discord"`
|
||||||
|
Dropbox string `xorm:"dropbox varchar(100)" json:"dropbox"`
|
||||||
|
EveOnline string `xorm:"eveonline varchar(100)" json:"eveonline"`
|
||||||
|
Fitbit string `xorm:"fitbit varchar(100)" json:"fitbit"`
|
||||||
|
Gitea string `xorm:"gitea varchar(100)" json:"gitea"`
|
||||||
|
Heroku string `xorm:"heroku varchar(100)" json:"heroku"`
|
||||||
|
InfluxCloud string `xorm:"influxcloud varchar(100)" json:"influxcloud"`
|
||||||
|
Instagram string `xorm:"instagram varchar(100)" json:"instagram"`
|
||||||
|
Intercom string `xorm:"intercom varchar(100)" json:"intercom"`
|
||||||
|
Kakao string `xorm:"kakao varchar(100)" json:"kakao"`
|
||||||
|
Lastfm string `xorm:"lastfm varchar(100)" json:"lastfm"`
|
||||||
|
Mailru string `xorm:"mailru varchar(100)" json:"mailru"`
|
||||||
|
Meetup string `xorm:"meetup varchar(100)" json:"meetup"`
|
||||||
|
MicrosoftOnline string `xorm:"microsoftonline varchar(100)" json:"microsoftonline"`
|
||||||
|
Naver string `xorm:"naver varchar(100)" json:"naver"`
|
||||||
|
Nextcloud string `xorm:"nextcloud varchar(100)" json:"nextcloud"`
|
||||||
|
OneDrive string `xorm:"onedrive varchar(100)" json:"onedrive"`
|
||||||
|
Oura string `xorm:"oura varchar(100)" json:"oura"`
|
||||||
|
Patreon string `xorm:"patreon varchar(100)" json:"patreon"`
|
||||||
|
Paypal string `xorm:"paypal varchar(100)" json:"paypal"`
|
||||||
|
SalesForce string `xorm:"salesforce varchar(100)" json:"salesforce"`
|
||||||
|
Shopify string `xorm:"shopify varchar(100)" json:"shopify"`
|
||||||
|
Soundcloud string `xorm:"soundcloud varchar(100)" json:"soundcloud"`
|
||||||
|
Spotify string `xorm:"spotify varchar(100)" json:"spotify"`
|
||||||
|
Strava string `xorm:"strava varchar(100)" json:"strava"`
|
||||||
|
Stripe string `xorm:"stripe varchar(100)" json:"stripe"`
|
||||||
|
TikTok string `xorm:"tiktok varchar(100)" json:"tiktok"`
|
||||||
|
Tumblr string `xorm:"tumblr varchar(100)" json:"tumblr"`
|
||||||
|
Twitch string `xorm:"twitch varchar(100)" json:"twitch"`
|
||||||
|
Twitter string `xorm:"twitter varchar(100)" json:"twitter"`
|
||||||
|
Typetalk string `xorm:"typetalk varchar(100)" json:"typetalk"`
|
||||||
|
Uber string `xorm:"uber varchar(100)" json:"uber"`
|
||||||
|
VK string `xorm:"vk varchar(100)" json:"vk"`
|
||||||
|
Wepay string `xorm:"wepay varchar(100)" json:"wepay"`
|
||||||
|
Xero string `xorm:"xero varchar(100)" json:"xero"`
|
||||||
|
Yahoo string `xorm:"yahoo varchar(100)" json:"yahoo"`
|
||||||
|
Yammer string `xorm:"yammer varchar(100)" json:"yammer"`
|
||||||
|
Yandex string `xorm:"yandex varchar(100)" json:"yandex"`
|
||||||
|
Zoom string `xorm:"zoom varchar(100)" json:"zoom"`
|
||||||
|
Custom string `xorm:"custom varchar(100)" json:"custom"`
|
||||||
|
|
||||||
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
module.exports = {
|
const { defineConfig } = require("cypress");
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
e2e: {
|
e2e: {
|
||||||
setupNodeEvents(on, config) {
|
"retries": {
|
||||||
// implement node event listeners here
|
"runMode": 2,
|
||||||
},
|
"openMode": 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
16
web/cypress/e2e/adapter.cy.js
Normal file
16
web/cypress/e2e/adapter.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test adapter', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn"
|
||||||
|
};
|
||||||
|
it("test adapter", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/adapters");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/adapters");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/adapters/built-in/")
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/application.cy.js
Normal file
13
web/cypress/e2e/application.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test aplication', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test aplication", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/applications");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/applications");
|
||||||
|
cy.visit("http://localhost:7001/applications/built-in/app-built-in");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/applications/built-in/app-built-in");
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/certs.cy.js
Normal file
13
web/cypress/e2e/certs.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test certs', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test certs", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/certs");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/certs");
|
||||||
|
cy.visit("http://localhost:7001/certs/cert-built-in");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/certs/cert-built-in");
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/models.cy.js
Normal file
13
web/cypress/e2e/models.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test models', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test org", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/models");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/models");
|
||||||
|
cy.visit("http://localhost:7001/models/built-in/model-built-in");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/models/built-in/model-built-in");
|
||||||
|
});
|
||||||
|
})
|
15
web/cypress/e2e/orgnazition.cy.js
Normal file
15
web/cypress/e2e/orgnazition.cy.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
describe('Test Orgnazition', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test org", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/organizations");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/organizations");
|
||||||
|
cy.visit("http://localhost:7001/organizations/built-in");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/organizations/built-in");
|
||||||
|
cy.visit("http://localhost:7001/organizations/built-in/users");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/organizations/built-in/users");
|
||||||
|
});
|
||||||
|
})
|
16
web/cypress/e2e/payments.cy.js
Normal file
16
web/cypress/e2e/payments.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test payments', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn"
|
||||||
|
};
|
||||||
|
it("test payments", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/payments");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/payments");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/payments/")
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/permissions.cy.js
Normal file
13
web/cypress/e2e/permissions.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test permissions', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test permissions", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/permissions");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/permissions");
|
||||||
|
cy.visit("http://localhost:7001/permissions/built-in/permission-built-in");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/permissions/built-in/permission-built-in");
|
||||||
|
});
|
||||||
|
})
|
16
web/cypress/e2e/products.cy.js
Normal file
16
web/cypress/e2e/products.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test products', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn > span"
|
||||||
|
};
|
||||||
|
it("test products", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/products");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/products");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/products/")
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/providers.cy.js
Normal file
13
web/cypress/e2e/providers.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test providers', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test providers", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/providers");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/providers");
|
||||||
|
cy.visit("http://localhost:7001/providers/admin/provider_captcha_default");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/providers/admin/provider_captcha_default");
|
||||||
|
});
|
||||||
|
})
|
11
web/cypress/e2e/records.cy.js
Normal file
11
web/cypress/e2e/records.cy.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
describe('Test records', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test records", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/records");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/records");
|
||||||
|
});
|
||||||
|
})
|
11
web/cypress/e2e/resource.cy.js
Normal file
11
web/cypress/e2e/resource.cy.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
describe('Test resource', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test resource", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/resources");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/resources");
|
||||||
|
});
|
||||||
|
})
|
11
web/cypress/e2e/role.cy.js
Normal file
11
web/cypress/e2e/role.cy.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
describe('Test roles', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test role", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/roles");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/roles");
|
||||||
|
});
|
||||||
|
})
|
11
web/cypress/e2e/sessions.cy.js
Normal file
11
web/cypress/e2e/sessions.cy.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
describe('Test sessions', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test sessions", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/sessions");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/sessions");
|
||||||
|
});
|
||||||
|
})
|
16
web/cypress/e2e/syncers.cy.js
Normal file
16
web/cypress/e2e/syncers.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test syncers', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn"
|
||||||
|
};
|
||||||
|
it("test syncers", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/syncers");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/syncers");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/syncers/")
|
||||||
|
});
|
||||||
|
})
|
11
web/cypress/e2e/sysinfo.cy.js
Normal file
11
web/cypress/e2e/sysinfo.cy.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
describe('Test sysinfo', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test sysinfo", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/sysinfo");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/sysinfo");
|
||||||
|
});
|
||||||
|
})
|
16
web/cypress/e2e/tokens.cy.js
Normal file
16
web/cypress/e2e/tokens.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test tokens', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn"
|
||||||
|
};
|
||||||
|
it("test records", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/tokens");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/tokens");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/tokens/")
|
||||||
|
});
|
||||||
|
})
|
13
web/cypress/e2e/user.cy.js
Normal file
13
web/cypress/e2e/user.cy.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Test User', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
it("test user", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/users");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/users");
|
||||||
|
cy.visit("http://localhost:7001/users/built-in/admin");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/users/built-in/admin");
|
||||||
|
});
|
||||||
|
})
|
16
web/cypress/e2e/webhooks.cy.js
Normal file
16
web/cypress/e2e/webhooks.cy.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
describe('Test webhooks', () => {
|
||||||
|
beforeEach(()=>{
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.login();
|
||||||
|
})
|
||||||
|
const selector = {
|
||||||
|
add: ".ant-table-title > div > .ant-btn"
|
||||||
|
};
|
||||||
|
it("test webhooks", () => {
|
||||||
|
cy.visit("http://localhost:7001");
|
||||||
|
cy.visit("http://localhost:7001/webhooks");
|
||||||
|
cy.url().should("eq", "http://localhost:7001/webhooks");
|
||||||
|
cy.get(selector.add).click();
|
||||||
|
cy.url().should("include","http://localhost:7001/webhooks/")
|
||||||
|
});
|
||||||
|
})
|
@ -22,4 +22,21 @@
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
// -- This will overwrite an existing command --
|
// -- This will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
Cypress.Commands.add('login', ()=>{
|
||||||
|
cy.request({
|
||||||
|
method: "POST",
|
||||||
|
url: "http://localhost:7001/api/login",
|
||||||
|
body: {
|
||||||
|
"application": "app-built-in",
|
||||||
|
"organization": "built-in",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "123",
|
||||||
|
"autoSignin": true,
|
||||||
|
"type": "login",
|
||||||
|
"phonePrefix": "86",
|
||||||
|
},
|
||||||
|
}).then((Response) => {
|
||||||
|
expect(Response).property("body").property("status").to.equal("ok");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@craco/craco": "^6.4.5",
|
"@craco/craco": "^6.4.5",
|
||||||
"@crowdin/cli": "^3.7.10",
|
"@crowdin/cli": "^3.7.10",
|
||||||
|
"@ctrl/tinycolor": "^3.5.0",
|
||||||
|
"@emotion/react": "^11.10.5",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"antd": "5.1.2",
|
"antd": "5.1.6",
|
||||||
|
"antd-token-previewer": "^1.1.0-22",
|
||||||
"codemirror": "^5.61.1",
|
"codemirror": "^5.61.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
@ -66,6 +69,7 @@
|
|||||||
"@babel/eslint-parser": "^7.18.9",
|
"@babel/eslint-parser": "^7.18.9",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"cypress": "^12.5.1",
|
||||||
"eslint": "8.22.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-plugin-react": "^7.31.1",
|
"eslint-plugin-react": "^7.31.1",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
|
@ -109,7 +109,10 @@ class AdapterEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateAdapterField("organization", value);})}>
|
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
|
||||||
|
this.getModels(value);
|
||||||
|
this.updateAdapterField("organization", value);
|
||||||
|
})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
|
431
web/src/App.js
431
web/src/App.js
@ -17,7 +17,7 @@ import "./App.less";
|
|||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, theme} from "antd";
|
import {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";
|
||||||
import OrganizationListPage from "./OrganizationListPage";
|
import OrganizationListPage from "./OrganizationListPage";
|
||||||
import OrganizationEditPage from "./OrganizationEditPage";
|
import OrganizationEditPage from "./OrganizationEditPage";
|
||||||
@ -83,8 +83,9 @@ class App extends Component {
|
|||||||
account: undefined,
|
account: undefined,
|
||||||
uri: null,
|
uri: null,
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
themeAlgorithm: null,
|
themeAlgorithm: ["default"],
|
||||||
logo: null,
|
themeData: Setting.ThemeDefault,
|
||||||
|
logo: this.getLogo(Setting.getAlgorithmNames(Setting.ThemeDefault)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Setting.initServerUrl();
|
Setting.initServerUrl();
|
||||||
@ -99,16 +100,6 @@ class App extends Component {
|
|||||||
this.getAccount();
|
this.getAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
localStorage.getItem("theme") ?
|
|
||||||
this.setState({"themeAlgorithm": this.getTheme()}) : this.setState({"themeAlgorithm": theme.defaultAlgorithm});
|
|
||||||
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
|
|
||||||
addEventListener("themeChange", (e) => {
|
|
||||||
this.setState({"themeAlgorithm": this.getTheme()});
|
|
||||||
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const uri = location.pathname;
|
const uri = location.pathname;
|
||||||
@ -198,8 +189,12 @@ class App extends Component {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
getTheme() {
|
getLogo(themes) {
|
||||||
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
|
if (themes.includes("dark")) {
|
||||||
|
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
|
||||||
|
} else {
|
||||||
|
return `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setLanguage(account) {
|
setLanguage(account) {
|
||||||
@ -209,6 +204,19 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTheme = (theme, initThemeAlgorithm) => {
|
||||||
|
this.setState({
|
||||||
|
themeData: theme,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (initThemeAlgorithm) {
|
||||||
|
this.setState({
|
||||||
|
logo: this.getLogo(Setting.getAlgorithmNames(theme)),
|
||||||
|
themeAlgorithm: Setting.getAlgorithmNames(theme),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getAccount() {
|
getAccount() {
|
||||||
const params = new URLSearchParams(this.props.location.search);
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
|
||||||
@ -233,7 +241,9 @@ class App extends Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
account = res.data;
|
account = res.data;
|
||||||
account.organization = res.data2;
|
account.organization = res.data2;
|
||||||
|
|
||||||
this.setLanguage(account);
|
this.setLanguage(account);
|
||||||
|
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
||||||
} else {
|
} else {
|
||||||
if (res.data !== "Please login first") {
|
if (res.data !== "Please login first") {
|
||||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||||
@ -259,6 +269,7 @@ class App extends Component {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
account: null,
|
account: null,
|
||||||
|
themeAlgorithm: ["default"],
|
||||||
});
|
});
|
||||||
|
|
||||||
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||||
@ -282,14 +293,6 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRightDropdownClick(e) {
|
|
||||||
if (e.key === "/account") {
|
|
||||||
this.props.history.push("/account");
|
|
||||||
} else if (e.key === "/logout") {
|
|
||||||
this.logout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAvatar() {
|
renderAvatar() {
|
||||||
if (this.state.account.avatar === "") {
|
if (this.state.account.avatar === "") {
|
||||||
return (
|
return (
|
||||||
@ -313,55 +316,56 @@ class App extends Component {
|
|||||||
));
|
));
|
||||||
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
items.push(Setting.getItem(<><LogoutOutlined /> {i18next.t("account:Logout")}</>,
|
||||||
"/logout"));
|
"/logout"));
|
||||||
const onClick = this.handleRightDropdownClick.bind(this);
|
|
||||||
|
const onClick = (e) => {
|
||||||
|
if (e.key === "/account") {
|
||||||
|
this.props.history.push("/account");
|
||||||
|
} else if (e.key === "/logout") {
|
||||||
|
this.logout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown key="/rightDropDown" menu={{items, onClick}} className="rightDropDown">
|
<Dropdown key="/rightDropDown" menu={{items, onClick}} >
|
||||||
<div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
|
<div className="rightDropDown">
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
this.renderAvatar()
|
this.renderAvatar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{Setting.isMobile() ? null : Setting.getShortName(this.state.account.displayName)} <DownOutlined />
|
{Setting.isMobile() ? null : Setting.getShortName(this.state.account.displayName)} <DownOutlined />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAccount() {
|
renderAccountMenu() {
|
||||||
const res = [];
|
|
||||||
|
|
||||||
if (this.state.account === undefined) {
|
if (this.state.account === undefined) {
|
||||||
return null;
|
return null;
|
||||||
} else if (this.state.account === null) {
|
} else if (this.state.account === null) {
|
||||||
// res.push(
|
return null;
|
||||||
// <Menu.Item key="/signup" style={{float: 'right', marginRight: '20px'}}>
|
|
||||||
// <Link to="/signup">
|
|
||||||
// {i18next.t("account:Sign Up")}
|
|
||||||
// </Link>
|
|
||||||
// </Menu.Item>
|
|
||||||
// );
|
|
||||||
// res.push(
|
|
||||||
// <Menu.Item key="/login" style={{float: 'right'}}>
|
|
||||||
// <Link to="/login">
|
|
||||||
// {i18next.t("account:Login")}
|
|
||||||
// </Link>
|
|
||||||
// </Menu.Item>
|
|
||||||
// );
|
|
||||||
} else {
|
} else {
|
||||||
res.push(this.renderRightDropdown());
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderRightDropdown()}
|
||||||
|
<SelectThemeBox
|
||||||
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
|
onChange={(nextThemeAlgorithm) => {
|
||||||
|
this.setState({
|
||||||
|
themeAlgorithm: nextThemeAlgorithm,
|
||||||
|
logo: this.getLogo(nextThemeAlgorithm),
|
||||||
|
});
|
||||||
|
}} />
|
||||||
|
<SelectLanguageBox languages={this.state.account.organization.languages} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMenu() {
|
getMenuItems() {
|
||||||
const res = [];
|
const res = [];
|
||||||
|
|
||||||
if (this.state.account === null || this.state.account === undefined) {
|
if (this.state.account === null || this.state.account === undefined) {
|
||||||
@ -481,69 +485,59 @@ class App extends Component {
|
|||||||
|
|
||||||
isStartPages() {
|
isStartPages() {
|
||||||
return window.location.pathname.startsWith("/login") ||
|
return window.location.pathname.startsWith("/login") ||
|
||||||
window.location.pathname.startsWith("/signup") ||
|
window.location.pathname.startsWith("/signup") ||
|
||||||
window.location.pathname === "/";
|
window.location.pathname === "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRouter() {
|
renderRouter() {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={{
|
<Switch>
|
||||||
token: {
|
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||||
colorPrimary: "rgb(89,54,213)",
|
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||||
colorInfo: "rgb(89,54,213)",
|
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
||||||
},
|
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||||
algorithm: this.state.themeAlgorithm,
|
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||||
}}>
|
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} onChangeTheme={this.setTheme} {...props} />)} />
|
||||||
<div>
|
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||||
<Switch>
|
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
</Switch>
|
||||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
|
||||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
|
||||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
|
||||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
|
||||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</ConfigProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,85 +554,56 @@ class App extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (!Setting.isMobile()) {
|
return (
|
||||||
return (
|
<Layout id="parent-area">
|
||||||
<Layout id="parent-area">
|
{/* https://github.com/ant-design/ant-design/issues/40394 ant design bug. If it will be fixed, we can delete the code for control the color of Header*/}
|
||||||
<Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm.includes("dark") ? "black" : "white"}}>
|
||||||
{
|
{Setting.isMobile() ? null : (
|
||||||
Setting.isMobile() ? null : (
|
<Link to={"/"}>
|
||||||
<Link to={"/"}>
|
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||||
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
</Link>
|
||||||
</Link>
|
)}
|
||||||
)
|
{Setting.isMobile() ?
|
||||||
}
|
<React.Fragment>
|
||||||
|
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
|
||||||
|
<Menu
|
||||||
|
items={this.getMenuItems()}
|
||||||
|
mode={"inline"}
|
||||||
|
selectedKeys={[this.state.selectedMenuKey]}
|
||||||
|
style={{lineHeight: "64px"}}
|
||||||
|
onClick={this.onClose}
|
||||||
|
>
|
||||||
|
</Menu>
|
||||||
|
</Drawer>
|
||||||
|
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
||||||
|
{i18next.t("general:Menu")}
|
||||||
|
</Button>
|
||||||
|
</React.Fragment> :
|
||||||
<Menu
|
<Menu
|
||||||
items={this.renderMenu()}
|
items={this.getMenuItems()}
|
||||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
mode={"horizontal"}
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
selectedKeys={[this.state.selectedMenuKey]}
|
||||||
style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
|
style={{position: "absolute", left: "145px", right: "260px"}}
|
||||||
/>
|
/>
|
||||||
{
|
|
||||||
this.renderAccount()
|
|
||||||
}
|
|
||||||
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
|
||||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
|
||||||
</Header>
|
|
||||||
<Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
|
|
||||||
<Card className="content-warp-card">
|
|
||||||
{
|
|
||||||
this.renderRouter()
|
|
||||||
}
|
|
||||||
</Card>
|
|
||||||
</Content>
|
|
||||||
{
|
|
||||||
this.renderFooter()
|
|
||||||
}
|
}
|
||||||
</Layout>
|
{
|
||||||
);
|
this.renderAccountMenu()
|
||||||
} else {
|
}
|
||||||
return (
|
</Header>
|
||||||
<Layout>
|
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||||
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
{Setting.isMobile() ?
|
||||||
{
|
this.renderRouter() :
|
||||||
Setting.isMobile() ? null : (
|
<Card className="content-warp-card">
|
||||||
<Link to={"/"}>
|
{this.renderRouter()}
|
||||||
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
</Card>
|
||||||
</Link>
|
}
|
||||||
)
|
</Content>
|
||||||
}
|
{this.renderFooter()}
|
||||||
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
|
</Layout>
|
||||||
<Menu
|
);
|
||||||
// theme="dark"
|
|
||||||
items={this.renderMenu()}
|
|
||||||
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
|
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
|
||||||
style={{lineHeight: "64px"}}
|
|
||||||
onClick={this.onClose}
|
|
||||||
>
|
|
||||||
</Menu>
|
|
||||||
</Drawer>
|
|
||||||
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
|
||||||
{i18next.t("general:Menu")}
|
|
||||||
</Button>
|
|
||||||
{
|
|
||||||
this.renderAccount()
|
|
||||||
}
|
|
||||||
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
|
||||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
|
||||||
</Header>
|
|
||||||
<Content style={{display: "flex", flexDirection: "column"}} >{
|
|
||||||
this.renderRouter()}
|
|
||||||
</Content>
|
|
||||||
{this.renderFooter()}
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
// How to keep your footer where it belongs ?
|
|
||||||
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
||||||
@ -647,7 +612,7 @@ class App extends Component {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
||||||
</Footer>
|
</Footer>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@ -659,36 +624,40 @@ class App extends Component {
|
|||||||
|
|
||||||
isEntryPages() {
|
isEntryPages() {
|
||||||
return window.location.pathname.startsWith("/signup") ||
|
return window.location.pathname.startsWith("/signup") ||
|
||||||
window.location.pathname.startsWith("/login") ||
|
window.location.pathname.startsWith("/login") ||
|
||||||
window.location.pathname.startsWith("/forget") ||
|
window.location.pathname.startsWith("/forget") ||
|
||||||
window.location.pathname.startsWith("/prompt") ||
|
window.location.pathname.startsWith("/prompt") ||
|
||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/auto-signup");
|
window.location.pathname.startsWith("/auto-signup");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
if (this.isDoorPages()) {
|
if (this.isDoorPages()) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Layout id="parent-area">
|
||||||
<Layout id="parent-area">
|
<Content style={{display: "flex", justifyContent: "center"}}>
|
||||||
<Content style={{display: "flex", justifyContent: "center"}}>
|
|
||||||
{
|
|
||||||
this.isEntryPages() ?
|
|
||||||
<EntryPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />
|
|
||||||
:
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/callback" component={AuthCallback} />
|
|
||||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
|
||||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
|
||||||
</Switch>
|
|
||||||
}
|
|
||||||
</Content>
|
|
||||||
{
|
{
|
||||||
this.renderFooter()
|
this.isEntryPages() ?
|
||||||
|
<EntryPage
|
||||||
|
account={this.state.account}
|
||||||
|
theme={this.state.themeData}
|
||||||
|
onUpdateAccount={(account) => {
|
||||||
|
this.onUpdateAccount(account);
|
||||||
|
}}
|
||||||
|
updataThemeData={this.setTheme}
|
||||||
|
/> :
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/callback" component={AuthCallback} />
|
||||||
|
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||||
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
|
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||||
|
</Switch>
|
||||||
}
|
}
|
||||||
</Layout>
|
</Content>
|
||||||
</React.Fragment>
|
{
|
||||||
|
this.renderFooter()
|
||||||
|
}
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,40 +673,24 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.account === undefined || this.state.account === null) {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Helmet>
|
|
||||||
<link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
|
|
||||||
</Helmet>
|
|
||||||
<ConfigProvider theme={{
|
|
||||||
token: {
|
|
||||||
colorPrimary: "rgb(89,54,213)",
|
|
||||||
colorInfo: "rgb(89,54,213)",
|
|
||||||
},
|
|
||||||
algorithm: this.state.themeAlgorithm,
|
|
||||||
}}>
|
|
||||||
{
|
|
||||||
this.renderPage()
|
|
||||||
}
|
|
||||||
</ConfigProvider>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const organization = this.state.account.organization;
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Helmet>
|
{(this.state.account === undefined || this.state.account === null) ?
|
||||||
<title>{organization.displayName}</title>
|
<Helmet>
|
||||||
<link rel="icon" href={organization.favicon} />
|
<link rel="icon" href={"https://cdn.casdoor.com/static/favicon.png"} />
|
||||||
</Helmet>
|
</Helmet> :
|
||||||
|
<Helmet>
|
||||||
|
<title>{this.state.account.organization?.displayName}</title>
|
||||||
|
<link rel="icon" href={this.state.account.organization?.favicon} />
|
||||||
|
</Helmet>
|
||||||
|
}
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider theme={{
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: "rgb(89,54,213)",
|
colorPrimary: this.state.themeData.colorPrimary,
|
||||||
colorInfo: "rgb(89,54,213)",
|
colorInfo: this.state.themeData.colorPrimary,
|
||||||
|
borderRadius: this.state.themeData.borderRadius,
|
||||||
},
|
},
|
||||||
algorithm: this.state.themeAlgorithm,
|
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
this.renderPage()
|
this.renderPage()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
/* stylelint-disable at-rule-name-case */
|
/* stylelint-disable at-rule-name-case */
|
||||||
/* stylelint-disable selector-class-pattern */
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
|
||||||
@StaticBaseUrl: "https://cdn.casbin.org";
|
|
||||||
|
|
||||||
.App {
|
.App {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -45,34 +43,13 @@ img {
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-box {
|
.select-box {
|
||||||
background: url("@{StaticBaseUrl}/img/muti_language.svg");
|
display: flex;
|
||||||
background-size: 25px, 25px;
|
align-items: center;
|
||||||
background-position: center;
|
justify-content: center;
|
||||||
background-repeat: no-repeat;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 100%;
|
height: 64px;
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form .language-box {
|
|
||||||
height: 65px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-box {
|
|
||||||
background: url("@{StaticBaseUrl}/img/muti_language.svg");
|
|
||||||
background-size: 25px, 25px;
|
|
||||||
background-position: center !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 45px;
|
|
||||||
height: 100%;
|
|
||||||
float: right;
|
float: right;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@ -82,7 +59,12 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rightDropDown {
|
.rightDropDown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
|
float: right;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
@ -151,6 +133,4 @@ img {
|
|||||||
|
|
||||||
.ant-menu-horizontal {
|
.ant-menu-horizontal {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
margin-right: 30px;
|
|
||||||
right: 230px;
|
|
||||||
}
|
}
|
||||||
|
@ -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, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
|
import {Button, Card, Col, ConfigProvider, Input, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
|
||||||
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as CertBackend from "./backend/CertBackend";
|
import * as CertBackend from "./backend/CertBackend";
|
||||||
@ -32,6 +32,7 @@ import copy from "copy-to-clipboard";
|
|||||||
|
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||||
|
|
||||||
require("codemirror/theme/material-darker.css");
|
require("codemirror/theme/material-darker.css");
|
||||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||||
@ -709,6 +710,31 @@ class ApplicationEditPage extends React.Component {
|
|||||||
: null}
|
: null}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} style={{marginTop: "5px"}}>
|
||||||
|
<Row>
|
||||||
|
<Radio.Group value={this.state.application.themeData?.isEnabled ?? false} onChange={e => {
|
||||||
|
const {_, ...theme} = this.state.application.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
|
||||||
|
this.updateApplicationField("themeData", {...theme, isEnabled: e.target.value});
|
||||||
|
}} >
|
||||||
|
<Radio.Button value={false}>{i18next.t("application:Follow organization theme")}</Radio.Button>
|
||||||
|
<Radio.Button value={true}>{i18next.t("theme:Customize theme")}</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
this.state.application.themeData?.isEnabled ?
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<ThemeEditor themeData={this.state.application.themeData} onThemeChange={(_, nextThemeData) => {
|
||||||
|
const {isEnabled} = this.state.application.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
|
||||||
|
this.updateApplicationField("themeData", {...nextThemeData, isEnabled});
|
||||||
|
}} />
|
||||||
|
</Row> : null
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
{
|
{
|
||||||
!this.state.application.enableSignUp ? null : (
|
!this.state.application.enableSignUp ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@ -738,6 +764,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSignupSigninPreview() {
|
renderSignupSigninPreview() {
|
||||||
|
const themeData = this.state.application.themeData ?? Setting.ThemeDefault;
|
||||||
let signUpUrl = `/signup/${this.state.application.name}`;
|
let signUpUrl = `/signup/${this.state.application.name}`;
|
||||||
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${this.state.application.redirectUris[0]}&scope=read&state=casdoor`;
|
||||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||||
@ -756,20 +783,28 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{i18next.t("application:Copy signup page URL")}
|
{i18next.t("application:Copy signup page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
<ConfigProvider theme={{
|
||||||
{
|
token: {
|
||||||
this.state.application.enablePassword ? (
|
colorPrimary: themeData.colorPrimary,
|
||||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
colorInfo: themeData.colorPrimary,
|
||||||
<SignupPage application={this.state.application} preview = "auto" />
|
borderRadius: themeData.borderRadius,
|
||||||
</div>
|
},
|
||||||
) : (
|
}}>
|
||||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
||||||
<LoginPage type={"login"} mode={"signup"} application={this.state.application} preview = "auto" />
|
{
|
||||||
</div>
|
this.state.application.enablePassword ? (
|
||||||
)
|
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||||
}
|
<SignupPage application={this.state.application} preview = "auto" />
|
||||||
<div style={{overflow: "auto", ...maskStyle}} />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||||
|
<LoginPage type={"login"} mode={"signup"} application={this.state.application} preview = "auto" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div style={{overflow: "auto", ...maskStyle}} />
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={previewGrid}>
|
<Col span={previewGrid}>
|
||||||
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
<Button style={{marginBottom: "10px", marginTop: Setting.isMobile() ? "15px" : "0"}} type="primary" shape="round" icon={<CopyOutlined />} onClick={() => {
|
||||||
@ -780,18 +815,27 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{i18next.t("application:Copy signin page URL")}
|
{i18next.t("application:Copy signin page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
<ConfigProvider theme={{
|
||||||
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
token: {
|
||||||
<LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
|
colorPrimary: themeData.colorPrimary,
|
||||||
|
colorInfo: themeData.colorPrimary,
|
||||||
|
borderRadius: themeData.borderRadius,
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
|
||||||
|
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
|
||||||
|
<LoginPage type={"login"} mode={"signin"} application={this.state.application} preview = "auto" />
|
||||||
|
</div>
|
||||||
|
<div style={{overflow: "auto", ...maskStyle}} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{overflow: "auto", ...maskStyle}} />
|
</ConfigProvider>
|
||||||
</div>
|
|
||||||
</Col>
|
</Col>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPromptPreview() {
|
renderPromptPreview() {
|
||||||
|
const themeData = this.state.application.themeData ?? Setting.ThemeDefault;
|
||||||
const promptUrl = `/prompt/${this.state.application.name}`;
|
const promptUrl = `/prompt/${this.state.application.name}`;
|
||||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "100%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||||
return (
|
return (
|
||||||
@ -804,10 +848,18 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{i18next.t("application:Copy prompt page URL")}
|
{i18next.t("application:Copy prompt page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
<ConfigProvider theme={{
|
||||||
<PromptPage application={this.state.application} account={this.props.account} />
|
token: {
|
||||||
<div style={maskStyle} />
|
colorPrimary: themeData.colorPrimary,
|
||||||
</div>
|
colorInfo: themeData.colorPrimary,
|
||||||
|
borderRadius: themeData.borderRadius,
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", flexDirection: "column", flex: "auto"}}>
|
||||||
|
<PromptPage application={this.state.application} account={this.props.account} />
|
||||||
|
<div style={maskStyle} />
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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, Col, List, Popconfirm, Result, Row, Table, Tooltip} from "antd";
|
import {Button, Col, List, Popconfirm, 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";
|
||||||
@ -25,19 +25,12 @@ import BaseListPage from "./BaseListPage";
|
|||||||
class ApplicationListPage extends BaseListPage {
|
class ApplicationListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
}
|
||||||
classes: props,
|
|
||||||
organizationName: props.account.owner,
|
componentDidMount() {
|
||||||
data: [],
|
this.setState({
|
||||||
pagination: {
|
organizationName: this.props.account.owner,
|
||||||
current: 1,
|
});
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
searchText: "",
|
|
||||||
searchedColumn: "",
|
|
||||||
isAuthorized: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newApplication() {
|
newApplication() {
|
||||||
@ -259,17 +252,6 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.isAuthorized) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
title="403 Unauthorized"
|
|
||||||
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
@ -17,5 +17,6 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
|
|||||||
|
|
||||||
export const ForceLanguage = "";
|
export const ForceLanguage = "";
|
||||||
export const DefaultLanguage = "en";
|
export const DefaultLanguage = "en";
|
||||||
|
export const InitThemeAlgorithm = true;
|
||||||
|
|
||||||
export const EnableExtraPages = true;
|
export const EnableExtraPages = true;
|
||||||
|
@ -52,32 +52,41 @@ class EntryPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getApplicationObj() {
|
||||||
|
return this.state.application || null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const onUpdateApplication = (application) => {
|
const onUpdateApplication = (application) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Setting.ThemeDefault;
|
||||||
|
this.props.updataThemeData(themeData);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
return (
|
||||||
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||||
<Switch>
|
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
|
||||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Switch>
|
||||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
|
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
|
||||||
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
|
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
|
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
|
||||||
</Switch>
|
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
|
||||||
</div>;
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,25 @@ class ManagedAccountTable extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
classes: props,
|
classes: props,
|
||||||
|
managedAccounts: this.props.table !== null ? this.props.table.map((item, index) => {
|
||||||
|
item.key = index;
|
||||||
|
return item;
|
||||||
|
}) : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count = this.props.table?.length ?? 0;
|
||||||
|
|
||||||
updateTable(table) {
|
updateTable(table) {
|
||||||
this.props.onUpdateTable(table);
|
this.setState({
|
||||||
|
managedAccounts: table,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onUpdateTable([...table].map((item) => {
|
||||||
|
const newItem = Setting.deepCopy(item);
|
||||||
|
delete newItem.key;
|
||||||
|
return newItem;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateField(table, index, key, value) {
|
updateField(table, index, key, value) {
|
||||||
@ -38,10 +52,12 @@ class ManagedAccountTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRow(table) {
|
addRow(table) {
|
||||||
const row = {application: "", username: "", password: ""};
|
const row = {key: this.count, application: "", username: "", password: ""};
|
||||||
if (table === undefined || table === null) {
|
if (table === undefined || table === null) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.count += 1;
|
||||||
table = Setting.addRow(table, row);
|
table = Setting.addRow(table, row);
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
@ -131,7 +147,7 @@ class ManagedAccountTable extends React.Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table scroll={{x: "max-content"}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
<Table scroll={{x: "max-content"}} rowKey="key" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
@ -148,7 +164,7 @@ class ManagedAccountTable extends React.Component {
|
|||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
{
|
{
|
||||||
this.renderTable(this.props.table)
|
this.renderTable(this.state.managedAccounts)
|
||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Radio, Row, Select, Switch} from "antd";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
@ -22,6 +22,7 @@ import i18next from "i18next";
|
|||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./LdapTable";
|
import LdapTable from "./LdapTable";
|
||||||
import AccountTable from "./AccountTable";
|
import AccountTable from "./AccountTable";
|
||||||
|
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -316,6 +317,31 @@ class OrganizationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} style={{marginTop: "5px"}}>
|
||||||
|
<Row>
|
||||||
|
<Radio.Group value={this.state.organization.themeData?.isEnabled ?? false} onChange={e => {
|
||||||
|
const {_, ...theme} = this.state.organization.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
|
||||||
|
this.updateOrganizationField("themeData", {...theme, isEnabled: e.target.value});
|
||||||
|
}} >
|
||||||
|
<Radio.Button value={false}>{i18next.t("organization:Follow global theme")}</Radio.Button>
|
||||||
|
<Radio.Button value={true}>{i18next.t("theme:Customize theme")}</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
this.state.organization.themeData?.isEnabled ?
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<ThemeEditor themeData={this.state.organization.themeData} onThemeChange={(_, nextThemeData) => {
|
||||||
|
const {isEnabled} = this.state.organization.themeData ?? {...Setting.ThemeDefault, isEnabled: false};
|
||||||
|
this.updateOrganizationField("themeData", {...nextThemeData, isEnabled});
|
||||||
|
}} />
|
||||||
|
</Row> : null
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||||
@ -341,6 +367,11 @@ class OrganizationEditPage extends React.Component {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
|
|
||||||
|
if (this.props.account.organization.name === this.state.organizationName) {
|
||||||
|
this.props.onChangeTheme(Setting.getThemeData(this.state.organization));
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
organizationName: this.state.organization.name,
|
organizationName: this.state.organization.name,
|
||||||
});
|
});
|
||||||
|
@ -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, Result, Switch, Table} from "antd";
|
import {Button, Popconfirm, 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";
|
||||||
@ -246,17 +246,6 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.isAuthorized) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
title="403 Unauthorized"
|
|
||||||
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
@ -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, Result, Table} from "antd";
|
import {Button, Popconfirm, 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";
|
||||||
@ -25,20 +25,14 @@ import BaseListPage from "./BaseListPage";
|
|||||||
class ProviderListPage extends BaseListPage {
|
class ProviderListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
classes: props,
|
|
||||||
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.owner,
|
|
||||||
data: [],
|
|
||||||
pagination: {
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
searchText: "",
|
|
||||||
searchedColumn: "",
|
|
||||||
isAuthorized: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
owner: Setting.isAdminUser(this.props.account) ? "admin" : this.props.account.owner,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
newProvider() {
|
newProvider() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
@ -228,17 +222,6 @@ class ProviderListPage extends BaseListPage {
|
|||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.isAuthorized) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
title="403 Unauthorized"
|
|
||||||
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Popconfirm, Result, Table, Upload} from "antd";
|
import {Button, Popconfirm, 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";
|
||||||
@ -25,20 +25,13 @@ import BaseListPage from "./BaseListPage";
|
|||||||
class ResourceListPage extends BaseListPage {
|
class ResourceListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
}
|
||||||
classes: props,
|
|
||||||
data: [],
|
componentDidMount() {
|
||||||
pagination: {
|
this.setState({
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
searchText: "",
|
|
||||||
searchedColumn: "",
|
|
||||||
fileList: [],
|
fileList: [],
|
||||||
uploading: false,
|
uploading: false,
|
||||||
isAuthorized: true,
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteResource(i) {
|
deleteResource(i) {
|
||||||
@ -273,17 +266,6 @@ class ResourceListPage extends BaseListPage {
|
|||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.isAuthorized) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
title="403 Unauthorized"
|
|
||||||
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||||
|
@ -16,6 +16,7 @@ import React from "react";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {Dropdown} from "antd";
|
import {Dropdown} from "antd";
|
||||||
import "./App.less";
|
import "./App.less";
|
||||||
|
import {GlobalOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
function flagIcon(country, alt) {
|
function flagIcon(country, alt) {
|
||||||
return (
|
return (
|
||||||
@ -30,6 +31,10 @@ class SelectLanguageBox extends React.Component {
|
|||||||
classes: props,
|
classes: props,
|
||||||
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
|
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Setting.Countries.forEach((country) => {
|
||||||
|
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||||
@ -50,7 +55,9 @@ class SelectLanguageBox extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{items: languageItems, onClick}} >
|
<Dropdown menu={{items: languageItems, onClick}} >
|
||||||
<div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} />
|
<div className="select-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} >
|
||||||
|
<GlobalOutlined style={{fontSize: "24px", color: "#4d4d4d"}} />
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -17,62 +17,75 @@ import * as Setting from "./Setting";
|
|||||||
import {Dropdown} from "antd";
|
import {Dropdown} from "antd";
|
||||||
import "./App.less";
|
import "./App.less";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import {CheckOutlined} from "@ant-design/icons";
|
||||||
|
import {CompactTheme, DarkTheme, Light} from "antd-token-previewer/es/icons";
|
||||||
|
|
||||||
function themeIcon(themeKey) {
|
export const Themes = [
|
||||||
return <img width={24} alt={themeKey} src={getLogoURL(themeKey)} />;
|
{label: "Default", key: "default", icon: <Light style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Default")
|
||||||
}
|
{label: "Dark", key: "dark", icon: <DarkTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Dark")
|
||||||
|
{label: "Compact", key: "compact", icon: <CompactTheme style={{fontSize: "24px", color: "#4d4d4d"}} />}, // i18next.t("theme:Compact")
|
||||||
|
];
|
||||||
|
|
||||||
function getLogoURL(themeKey) {
|
function getIcon(themeKey) {
|
||||||
if (themeKey) {
|
if (themeKey?.includes("dark")) {
|
||||||
return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"];
|
return Themes.find(t => t.key === "dark").icon;
|
||||||
} else {
|
} else if (themeKey?.includes("default")) {
|
||||||
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"];
|
return Themes.find(t => t.key === "default").icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectThemeBox extends React.Component {
|
class SelectThemeBox extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
classes: props,
|
|
||||||
themes: props.theme ?? ["Default", "Dark", "Compact"],
|
|
||||||
icon: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items = this.getThemes();
|
icon = getIcon(this.props.themeAlgorithm);
|
||||||
|
|
||||||
componentDidMount() {
|
getThemeItems() {
|
||||||
i18next.on("languageChanged", () => {
|
return Themes.map((theme) => Setting.getItem(
|
||||||
this.items = this.getThemes();
|
<div style={{display: "flex", justifyContent: "space-between"}}>
|
||||||
});
|
<div>{i18next.t(`theme:${theme.label}`)}</div>
|
||||||
localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")});
|
{this.props.themeAlgorithm.includes(theme.key) ? <CheckOutlined style={{marginLeft: "5px"}} /> : null}
|
||||||
addEventListener("themeChange", (e) => {
|
</div>,
|
||||||
this.setState({"icon": getLogoURL()});
|
theme.key, theme.icon));
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getThemes() {
|
|
||||||
return Setting.Themes.map((theme) => Setting.getItem(i18next.t(`general:${theme.label}`), theme.key, themeIcon(theme.key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
getOrganizationThemes(themes) {
|
|
||||||
const select = [];
|
|
||||||
for (const theme of themes) {
|
|
||||||
this.items.map((item, index) => item.key === theme ? select.push(item) : null);
|
|
||||||
}
|
|
||||||
return select;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const themeItems = this.getOrganizationThemes(this.state.themes);
|
|
||||||
const onClick = (e) => {
|
const onClick = (e) => {
|
||||||
Setting.setTheme(e.key);
|
let nextTheme;
|
||||||
|
if (e.key === "compact") {
|
||||||
|
if (this.props.themeAlgorithm.includes("compact")) {
|
||||||
|
nextTheme = this.props.themeAlgorithm.filter((theme) => theme !== "compact");
|
||||||
|
} else {
|
||||||
|
nextTheme = [...this.props.themeAlgorithm, "compact"];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.props.themeAlgorithm.includes(e.key)) {
|
||||||
|
if (e.key === "dark") {
|
||||||
|
nextTheme = [...this.props.themeAlgorithm.filter((theme) => theme !== "default"), e.key];
|
||||||
|
} else {
|
||||||
|
nextTheme = [...this.props.themeAlgorithm.filter((theme) => theme !== "dark"), e.key];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextTheme = [...this.props.themeAlgorithm];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.icon = getIcon(nextTheme);
|
||||||
|
this.props.onChange(nextTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{items: themeItems, onClick}} >
|
<Dropdown menu={{
|
||||||
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} />
|
items: this.getThemeItems(),
|
||||||
|
onClick,
|
||||||
|
selectable: true,
|
||||||
|
multiple: true,
|
||||||
|
selectedKeys: [...this.props.themeAlgorithm],
|
||||||
|
}}>
|
||||||
|
<div className="select-box">
|
||||||
|
{this.icon}
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,43 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
|||||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||||
];
|
];
|
||||||
|
|
||||||
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme;
|
export const ThemeDefault = {
|
||||||
|
themeType: "default",
|
||||||
|
colorPrimary: "#5734d3",
|
||||||
|
borderRadius: 6,
|
||||||
|
isCompact: false,
|
||||||
|
};
|
||||||
|
|
||||||
export const Themes = [{label: i18next.t("general:Dark"), key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`},
|
export function getThemeData(organization, application) {
|
||||||
{label: i18next.t("general:Compact"), key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`},
|
if (application?.themeData?.isEnabled) {
|
||||||
{label: i18next.t("general:Default"), key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`},
|
return application.themeData;
|
||||||
];
|
} else if (organization?.themeData?.isEnabled) {
|
||||||
|
return organization.themeData;
|
||||||
|
} else {
|
||||||
|
return ThemeDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAlgorithm(themeAlgorithmNames) {
|
||||||
|
return themeAlgorithmNames.map((algorithmName) => {
|
||||||
|
if (algorithmName === "dark") {
|
||||||
|
return theme.darkAlgorithm;
|
||||||
|
}
|
||||||
|
if (algorithmName === "compact") {
|
||||||
|
return theme.compactAlgorithm;
|
||||||
|
}
|
||||||
|
return theme.defaultAlgorithm;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAlgorithmNames(themeData) {
|
||||||
|
const algorithms = [themeData?.themeType !== "dark" ? "default" : "dark"];
|
||||||
|
if (themeData?.isCompact === true) {
|
||||||
|
algorithms.push("compact");
|
||||||
|
}
|
||||||
|
|
||||||
|
return algorithms;
|
||||||
|
}
|
||||||
|
|
||||||
export const OtherProviderInfo = {
|
export const OtherProviderInfo = {
|
||||||
SMS: {
|
SMS: {
|
||||||
@ -520,10 +551,10 @@ export function getTermsOfUseContent(url, setTermsOfUseContent) {
|
|||||||
export function isAgreementRequired(application) {
|
export function isAgreementRequired(application) {
|
||||||
if (application) {
|
if (application) {
|
||||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||||
if (agreementItem.rule === "None" || !agreementItem.rule) {
|
if (!agreementItem || agreementItem.rule === "None" || !agreementItem.rule) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (agreementItem && agreementItem.required) {
|
if (agreementItem.required) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,10 +563,8 @@ export function isAgreementRequired(application) {
|
|||||||
|
|
||||||
export function isDefaultTrue(application) {
|
export function isDefaultTrue(application) {
|
||||||
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
|
||||||
if (isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)") {
|
|
||||||
return true;
|
return isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)";
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
|
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
|
||||||
@ -670,13 +699,12 @@ export function getLanguage() {
|
|||||||
|
|
||||||
export function setLanguage(language) {
|
export function setLanguage(language) {
|
||||||
localStorage.setItem("language", language);
|
localStorage.setItem("language", language);
|
||||||
changeMomentLanguage(language);
|
|
||||||
i18next.changeLanguage(language);
|
i18next.changeLanguage(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTheme(themeKey) {
|
export function setTheme(themeKey) {
|
||||||
localStorage.setItem("theme", themeKey);
|
localStorage.setItem("theme", themeKey);
|
||||||
dispatchEvent(new Event("themeChange"));
|
dispatchEvent(new Event("changeTheme"));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAcceptLanguage() {
|
export function getAcceptLanguage() {
|
||||||
@ -686,29 +714,6 @@ export function getAcceptLanguage() {
|
|||||||
return i18next.language + ";q=0.9,en;q=0.8";
|
return i18next.language + ";q=0.9,en;q=0.8";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeMomentLanguage(language) {
|
|
||||||
// if (language === "zh") {
|
|
||||||
// moment.locale("zh", {
|
|
||||||
// relativeTime: {
|
|
||||||
// future: "%s内",
|
|
||||||
// past: "%s前",
|
|
||||||
// s: "几秒",
|
|
||||||
// ss: "%d秒",
|
|
||||||
// m: "1分钟",
|
|
||||||
// mm: "%d分钟",
|
|
||||||
// h: "1小时",
|
|
||||||
// hh: "%d小时",
|
|
||||||
// d: "1天",
|
|
||||||
// dd: "%d天",
|
|
||||||
// M: "1个月",
|
|
||||||
// MM: "%d个月",
|
|
||||||
// y: "1年",
|
|
||||||
// yy: "%d年",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClickable(text) {
|
export function getClickable(text) {
|
||||||
return (
|
return (
|
||||||
<a onClick={() => {
|
<a onClick={() => {
|
||||||
@ -774,6 +779,54 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Okta", name: "Okta"},
|
{id: "Okta", name: "Okta"},
|
||||||
{id: "Douyin", name: "Douyin"},
|
{id: "Douyin", name: "Douyin"},
|
||||||
{id: "Line", name: "Line"},
|
{id: "Line", name: "Line"},
|
||||||
|
{id: "Amazon", name: "Amazon"},
|
||||||
|
{id: "Auth0", name: "Auth0"},
|
||||||
|
{id: "BattleNet", name: "Battle.net"},
|
||||||
|
{id: "Bitbucket", name: "Bitbucket"},
|
||||||
|
{id: "Box", name: "Box"},
|
||||||
|
{id: "CloudFoundry", name: "Cloud Foundry"},
|
||||||
|
{id: "Dailymotion", name: "Dailymotion"},
|
||||||
|
{id: "Deezer", name: "Deezer"},
|
||||||
|
{id: "DigitalOcean", name: "DigitalOcean"},
|
||||||
|
{id: "Discord", name: "Discord"},
|
||||||
|
{id: "Dropbox", name: "Dropbox"},
|
||||||
|
{id: "EveOnline", name: "Eve Online"},
|
||||||
|
{id: "Fitbit", name: "Fitbit"},
|
||||||
|
{id: "Gitea", name: "Gitea"},
|
||||||
|
{id: "Heroku", name: "Heroku"},
|
||||||
|
{id: "InfluxCloud", name: "InfluxCloud"},
|
||||||
|
{id: "Instagram", name: "Instagram"},
|
||||||
|
{id: "Intercom", name: "Intercom"},
|
||||||
|
{id: "Kakao", name: "Kakao"},
|
||||||
|
{id: "Lastfm", name: "Lastfm"},
|
||||||
|
{id: "Mailru", name: "Mailru"},
|
||||||
|
{id: "Meetup", name: "Meetup"},
|
||||||
|
{id: "MicrosoftOnline", name: "MicrosoftOnline"},
|
||||||
|
{id: "Naver", name: "Naver"},
|
||||||
|
{id: "Nextcloud", name: "Nextcloud"},
|
||||||
|
{id: "OneDrive", name: "OneDrive"},
|
||||||
|
{id: "Oura", name: "Oura"},
|
||||||
|
{id: "Patreon", name: "Patreon"},
|
||||||
|
{id: "Paypal", name: "Paypal"},
|
||||||
|
{id: "SalesForce", name: "SalesForce"},
|
||||||
|
{id: "Shopify", name: "Shopify"},
|
||||||
|
{id: "Soundcloud", name: "Soundcloud"},
|
||||||
|
{id: "Spotify", name: "Spotify"},
|
||||||
|
{id: "Strava", name: "Strava"},
|
||||||
|
{id: "Stripe", name: "Stripe"},
|
||||||
|
{id: "TikTok", name: "TikTok"},
|
||||||
|
{id: "Tumblr", name: "Tumblr"},
|
||||||
|
{id: "Twitch", name: "Twitch"},
|
||||||
|
{id: "Twitter", name: "Twitter"},
|
||||||
|
{id: "Typetalk", name: "Typetalk"},
|
||||||
|
{id: "Uber", name: "Uber"},
|
||||||
|
{id: "VK", name: "VK"},
|
||||||
|
{id: "Wepay", name: "Wepay"},
|
||||||
|
{id: "Xero", name: "Xero"},
|
||||||
|
{id: "Yahoo", name: "Yahoo"},
|
||||||
|
{id: "Yammer", name: "Yammer"},
|
||||||
|
{id: "Yandex", name: "Yandex"},
|
||||||
|
{id: "Zoom", name: "Zoom"},
|
||||||
{id: "Custom", name: "Custom"},
|
{id: "Custom", name: "Custom"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -182,7 +182,10 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.owner} onChange={(value => {this.updateUserField("owner", value);})}>
|
<Select virtual={false} style={{width: "100%"}} disabled={disabled} value={this.state.user.owner} onChange={(value => {
|
||||||
|
this.getApplicationsByOrganization(value);
|
||||||
|
this.updateUserField("owner", value);
|
||||||
|
})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
@ -571,7 +574,7 @@ class UserEditPage extends React.Component {
|
|||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<ManagedAccountTable
|
<ManagedAccountTable
|
||||||
title={i18next.t("user:Managed accounts")}
|
title={i18next.t("user:Managed accounts")}
|
||||||
table={this.state.user.managedAccounts ?? []}
|
table={this.state.user.managedAccounts}
|
||||||
onUpdateTable={(table) => {this.updateUserField("managedAccounts", table);}}
|
onUpdateTable={(table) => {this.updateUserField("managedAccounts", table);}}
|
||||||
applications={this.state.applications}
|
applications={this.state.applications}
|
||||||
/>
|
/>
|
||||||
|
@ -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, Result, Switch, Table, Upload} from "antd";
|
import {Button, Popconfirm, Switch, Table, Upload} from "antd";
|
||||||
import {UploadOutlined} from "@ant-design/icons";
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
@ -26,20 +26,13 @@ import BaseListPage from "./BaseListPage";
|
|||||||
class UserListPage extends BaseListPage {
|
class UserListPage extends BaseListPage {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
}
|
||||||
classes: props,
|
|
||||||
organizationName: props.match.params.organizationName,
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
organizationName: this.props.match.params.organizationName,
|
||||||
organization: null,
|
organization: null,
|
||||||
data: [],
|
});
|
||||||
pagination: {
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
searchText: "",
|
|
||||||
searchedColumn: "",
|
|
||||||
isAuthorized: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newUser() {
|
newUser() {
|
||||||
@ -371,17 +364,6 @@ class UserListPage extends BaseListPage {
|
|||||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.isAuthorized) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
title="403 Unauthorized"
|
|
||||||
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
|
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// 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 {createButton} from "react-social-login-buttons";
|
|
||||||
import {StaticBaseUrl} from "../Setting";
|
|
||||||
|
|
||||||
function Icon({width = 24, height = 24, color}) {
|
|
||||||
return <img src={`${StaticBaseUrl}/buttons/line.svg`} alt="Sign in with Line" style={{width: 24, height: 24}} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
text: "Sign in with Line",
|
|
||||||
icon: Icon,
|
|
||||||
iconFormat: name => `fa fa-${name}`,
|
|
||||||
style: {background: "#ffffff", color: "#000000"},
|
|
||||||
activeStyle: {background: "#ededee"},
|
|
||||||
};
|
|
||||||
|
|
||||||
const LineLoginButton = createButton(config);
|
|
||||||
|
|
||||||
export default LineLoginButton;
|
|
34
web/src/auth/LoginButton.js
Normal file
34
web/src/auth/LoginButton.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import i18next from "i18next";
|
||||||
|
import {createButton} from "react-social-login-buttons";
|
||||||
|
import {StaticBaseUrl} from "../Setting";
|
||||||
|
|
||||||
|
function LoginButton({type, align = "center", style = {background: "#ffffff", color: "#000000"}, activeStyle = {background: "#ededee"}}) {
|
||||||
|
function Icon({width = 24, height = 24, color}) {
|
||||||
|
return <img src={`${StaticBaseUrl}/buttons/${type.toLowerCase()}.svg`} alt={`Sign in with ${type}`} style={{width: width, height: height}} />;
|
||||||
|
}
|
||||||
|
const config = {
|
||||||
|
text: `Sign in with ${type}`,
|
||||||
|
icon: Icon,
|
||||||
|
style: style,
|
||||||
|
activeStyle: activeStyle,
|
||||||
|
};
|
||||||
|
const Button = createButton(config);
|
||||||
|
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
|
||||||
|
return <Button text={text} align={align} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginButton;
|
@ -77,10 +77,6 @@ class LoginPage extends React.Component {
|
|||||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Setting.Countries.forEach((country) => {
|
|
||||||
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
@ -125,6 +125,198 @@ const authInfo = {
|
|||||||
scope: "profile%20openid%20email",
|
scope: "profile%20openid%20email",
|
||||||
endpoint: "https://access.line.me/oauth2/v2.1/authorize",
|
endpoint: "https://access.line.me/oauth2/v2.1/authorize",
|
||||||
},
|
},
|
||||||
|
Amazon: {
|
||||||
|
scope: "profile",
|
||||||
|
endpoint: "https://www.amazon.com/ap/oa",
|
||||||
|
},
|
||||||
|
Auth0: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "http://auth0.com/authorize",
|
||||||
|
},
|
||||||
|
BattleNet: {
|
||||||
|
scope: "openid",
|
||||||
|
endpoint: "https://oauth.battlenet.com.cn/authorize",
|
||||||
|
},
|
||||||
|
Bitbucket: {
|
||||||
|
scope: "account",
|
||||||
|
endpoint: "https://bitbucket.org/site/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Box: {
|
||||||
|
scope: "root_readwrite",
|
||||||
|
endpoint: "https://account.box.com/api/oauth2/authorize",
|
||||||
|
},
|
||||||
|
CloudFoundry: {
|
||||||
|
scope: "cloud_controller.read",
|
||||||
|
endpoint: "https://login.cloudfoundry.org/oauth/authorize",
|
||||||
|
},
|
||||||
|
Dailymotion: {
|
||||||
|
scope: "userinfo",
|
||||||
|
endpoint: "https://api.dailymotion.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Deezer: {
|
||||||
|
scope: "basic_access",
|
||||||
|
endpoint: "https://connect.deezer.com/oauth/auth.php",
|
||||||
|
},
|
||||||
|
DigitalOcean: {
|
||||||
|
scope: "read",
|
||||||
|
endpoint: "https://cloud.digitalocean.com/v1/oauth/authorize",
|
||||||
|
},
|
||||||
|
Discord: {
|
||||||
|
scope: "identify%20email",
|
||||||
|
endpoint: "https://discord.com/api/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Dropbox: {
|
||||||
|
scope: "account_info.read",
|
||||||
|
endpoint: "https://www.dropbox.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
EveOnline: {
|
||||||
|
scope: "publicData",
|
||||||
|
endpoint: "https://login.eveonline.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Fitbit: {
|
||||||
|
scope: "activity%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight",
|
||||||
|
endpoint: "https://www.fitbit.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Gitea: {
|
||||||
|
scope: "user:email",
|
||||||
|
endpoint: "https://gitea.com/login/oauth/authorize",
|
||||||
|
},
|
||||||
|
Heroku: {
|
||||||
|
scope: "global",
|
||||||
|
endpoint: "https://id.heroku.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
InfluxCloud: {
|
||||||
|
scope: "read:org",
|
||||||
|
endpoint: "https://cloud2.influxdata.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Instagram: {
|
||||||
|
scope: "user_profile",
|
||||||
|
endpoint: "https://api.instagram.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Intercom: {
|
||||||
|
scope: "user.read",
|
||||||
|
endpoint: "https://app.intercom.com/oauth",
|
||||||
|
},
|
||||||
|
Kakao: {
|
||||||
|
scope: "account_email",
|
||||||
|
endpoint: "https://kauth.kakao.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Lastfm: {
|
||||||
|
scope: "user_read",
|
||||||
|
endpoint: "https://www.last.fm/api/auth",
|
||||||
|
},
|
||||||
|
Mailru: {
|
||||||
|
scope: "userinfo",
|
||||||
|
endpoint: "https://oauth.mail.ru/login",
|
||||||
|
},
|
||||||
|
Meetup: {
|
||||||
|
scope: "basic",
|
||||||
|
endpoint: "https://secure.meetup.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
MicrosoftOnline: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||||
|
},
|
||||||
|
Naver: {
|
||||||
|
scope: "profile",
|
||||||
|
endpoint: "https://nid.naver.com/oauth2.0/authorize",
|
||||||
|
},
|
||||||
|
Nextcloud: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://cloud.example.org/apps/oauth2/authorize",
|
||||||
|
},
|
||||||
|
OneDrive: {
|
||||||
|
scope: "offline_access%20onedrive.readonly",
|
||||||
|
endpoint: "https://login.live.com/oauth20_authorize.srf",
|
||||||
|
},
|
||||||
|
Oura: {
|
||||||
|
scope: "personal",
|
||||||
|
endpoint: "https://cloud.ouraring.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Patreon: {
|
||||||
|
scope: "identity",
|
||||||
|
endpoint: "https://www.patreon.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Paypal: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://www.sandbox.paypal.com/connect",
|
||||||
|
},
|
||||||
|
SalesForce: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://login.salesforce.com/services/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Shopify: {
|
||||||
|
scope: "read_products",
|
||||||
|
endpoint: "https://myshopify.com/admin/oauth/authorize",
|
||||||
|
},
|
||||||
|
Soundcloud: {
|
||||||
|
scope: "non-expiring",
|
||||||
|
endpoint: "https://api.soundcloud.com/connect",
|
||||||
|
},
|
||||||
|
Spotify: {
|
||||||
|
scope: "user-read-email",
|
||||||
|
endpoint: "https://accounts.spotify.com/authorize",
|
||||||
|
},
|
||||||
|
Strava: {
|
||||||
|
scope: "read",
|
||||||
|
endpoint: "https://www.strava.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
Stripe: {
|
||||||
|
scope: "read_only",
|
||||||
|
endpoint: "https://connect.stripe.com/oauth/authorize",
|
||||||
|
},
|
||||||
|
TikTok: {
|
||||||
|
scope: "user.info.basic",
|
||||||
|
endpoint: "https://www.tiktok.com/auth/authorize/",
|
||||||
|
},
|
||||||
|
Tumblr: {
|
||||||
|
scope: "email",
|
||||||
|
endpoint: "https://www.tumblr.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Twitch: {
|
||||||
|
scope: "user_read",
|
||||||
|
endpoint: "https://id.twitch.tv/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Twitter: {
|
||||||
|
scope: "users.read",
|
||||||
|
endpoint: "https://twitter.com/i/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Typetalk: {
|
||||||
|
scope: "my",
|
||||||
|
endpoint: "https://typetalk.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Uber: {
|
||||||
|
scope: "profile",
|
||||||
|
endpoint: "https://login.uber.com/oauth/v2/authorize",
|
||||||
|
},
|
||||||
|
VK: {
|
||||||
|
scope: "email",
|
||||||
|
endpoint: "https://oauth.vk.com/authorize",
|
||||||
|
},
|
||||||
|
Wepay: {
|
||||||
|
scope: "manage_accounts%20view_user",
|
||||||
|
endpoint: "https://www.wepay.com/v2/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Xero: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://login.xero.com/identity/connect/authorize",
|
||||||
|
},
|
||||||
|
Yahoo: {
|
||||||
|
scope: "openid%20profile%20email",
|
||||||
|
endpoint: "https://api.login.yahoo.com/oauth2/request_auth",
|
||||||
|
},
|
||||||
|
Yammer: {
|
||||||
|
scope: "user",
|
||||||
|
endpoint: "https://www.yammer.com/oauth2/authorize",
|
||||||
|
},
|
||||||
|
Yandex: {
|
||||||
|
scope: "login:email",
|
||||||
|
endpoint: "https://oauth.yandex.com/authorize",
|
||||||
|
},
|
||||||
|
Zoom: {
|
||||||
|
scope: "user:read",
|
||||||
|
endpoint: "https://zoom.us/oauth/authorize",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getProviderUrl(provider) {
|
export function getProviderUrl(provider) {
|
||||||
@ -184,12 +376,19 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
|
|
||||||
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger");
|
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger");
|
||||||
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"))
|
||||||
|
|
||||||
if (provider.type === "Google") {
|
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook" || provider.type === "DingTalk"
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
|| provider.type === "Weibo" || provider.type === "Gitee" || provider.type === "LinkedIn" || provider.type === "GitLab" || provider.type === "AzureAD"
|
||||||
} else if (provider.type === "GitHub") {
|
|| provider.type === "Slack" || provider.type === "Line" || provider.type === "Amazon" || provider.type === "Auth0" || provider.type === "BattleNet"
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
|| provider.type === "Bitbucket" || provider.type === "Box" || provider.type === "CloudFoundry" || provider.type === "Dailymotion"
|
||||||
} else if (provider.type === "QQ") {
|
|| provider.type === "DigitalOcean" || provider.type === "Discord" || provider.type === "Dropbox" || provider.type === "EveOnline" || provider.type === "Gitea"
|
||||||
|
|| provider.type === "Heroku" || provider.type === "InfluxCloud" || provider.type === "Instagram" || provider.type === "Intercom" || provider.type === "Kakao"
|
||||||
|
|| provider.type === "MailRu" || provider.type === "Meetup" || provider.type === "MicrosoftOnline" || provider.type === "Naver" || provider.type === "Nextcloud"
|
||||||
|
|| provider.type === "OneDrive" || provider.type === "Oura" || provider.type === "Patreon" || provider.type === "PayPal" || provider.type === "SalesForce"
|
||||||
|
|| provider.type === "SoundCloud" || provider.type === "Spotify" || provider.type === "Strava" || provider.type === "Stripe" || provider.type === "Tumblr"
|
||||||
|
|| 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") {
|
||||||
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 === "WeChat") {
|
} else if (provider.type === "WeChat") {
|
||||||
if (navigator.userAgent.includes("MicroMessenger")) {
|
if (navigator.userAgent.includes("MicroMessenger")) {
|
||||||
@ -197,16 +396,6 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
} else {
|
} else {
|
||||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}#wechat_redirect`;
|
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}#wechat_redirect`;
|
||||||
}
|
}
|
||||||
} else if (provider.type === "Facebook") {
|
|
||||||
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&state=${state}&prompt=consent`;
|
|
||||||
} else if (provider.type === "Weibo") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
|
||||||
} else if (provider.type === "Gitee") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
|
||||||
} else if (provider.type === "LinkedIn") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`;
|
|
||||||
} else if (provider.type === "WeCom") {
|
} else if (provider.type === "WeCom") {
|
||||||
if (provider.subType === "Internal") {
|
if (provider.subType === "Internal") {
|
||||||
if (provider.method === "Silent") {
|
if (provider.method === "Silent") {
|
||||||
@ -232,8 +421,6 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
}
|
}
|
||||||
} else if (provider.type === "Lark") {
|
} else if (provider.type === "Lark") {
|
||||||
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
||||||
} else if (provider.type === "GitLab") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
|
||||||
} else if (provider.type === "Adfs") {
|
} else if (provider.type === "Adfs") {
|
||||||
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
|
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
|
||||||
} else if (provider.type === "Baidu") {
|
} else if (provider.type === "Baidu") {
|
||||||
@ -246,21 +433,23 @@ export function getAuthUrl(application, provider, method) {
|
|||||||
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`;
|
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`;
|
||||||
} else if (provider.type === "Apple") {
|
} else if (provider.type === "Apple") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
|
||||||
} else if (provider.type === "AzureAD") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
|
||||||
} else if (provider.type === "Slack") {
|
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
|
||||||
} else if (provider.type === "Steam") {
|
} else if (provider.type === "Steam") {
|
||||||
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
|
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
|
||||||
} else if (provider.type === "Okta") {
|
} else if (provider.type === "Okta") {
|
||||||
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Douyin") {
|
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
||||||
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
|
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "Bilibili") {
|
} else if (provider.type === "Bilibili") {
|
||||||
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`;
|
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`;
|
||||||
} else if (provider.type === "Line") {
|
} else if (provider.type === "Deezer") {
|
||||||
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&perms=${scope}`;
|
||||||
|
} else if (provider.type === "Lastfm") {
|
||||||
|
return `${endpoint}?api_key=${provider.clientId}&cb=${redirectUri}`;
|
||||||
|
} else if (provider.type === "Shopify") {
|
||||||
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&grant_options[]=per-user`;
|
||||||
|
} else if (provider.type === "Twitter" || provider.type === "Fitbit") {
|
||||||
|
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import SteamLoginButton from "./SteamLoginButton";
|
|||||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||||
import OktaLoginButton from "./OktaLoginButton";
|
import OktaLoginButton from "./OktaLoginButton";
|
||||||
import DouyinLoginButton from "./DouyinLoginButton";
|
import DouyinLoginButton from "./DouyinLoginButton";
|
||||||
import LineLoginButton from "./LineLoginButton";
|
import LoginButton from "./LoginButton";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import {getEvent} from "./Util";
|
import {getEvent} from "./Util";
|
||||||
import {Modal} from "antd";
|
import {Modal} from "antd";
|
||||||
@ -94,11 +94,9 @@ function getSigninButton(type) {
|
|||||||
return <OktaLoginButton text={text} align={"center"} />;
|
return <OktaLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Douyin") {
|
} else if (type === "Douyin") {
|
||||||
return <DouyinLoginButton text={text} align={"center"} />;
|
return <DouyinLoginButton text={text} align={"center"} />;
|
||||||
} else if (type === "Line") {
|
} else {
|
||||||
return <LineLoginButton text={text} align={"center"} />;
|
return <LoginButton key={type} type={type} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSamlUrl(provider, location) {
|
function getSamlUrl(provider, location) {
|
||||||
|
@ -71,8 +71,6 @@ export function deleteAdapter(Adapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function UpdatePolicy(owner, name, policy) {
|
export function UpdatePolicy(owner, name, policy) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(policy);
|
|
||||||
return fetch(`${Setting.ServerUrl}/api/update-policy?id=${owner}/${encodeURIComponent(name)}`, {
|
return fetch(`${Setting.ServerUrl}/api/update-policy?id=${owner}/${encodeURIComponent(name)}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -28,9 +28,18 @@ class PolicyTable extends React.Component {
|
|||||||
editingIndex: "",
|
editingIndex: "",
|
||||||
oldPolicy: "",
|
oldPolicy: "",
|
||||||
add: false,
|
add: false,
|
||||||
|
page: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
pageSize = 10;
|
||||||
|
|
||||||
|
getIndex(index) {
|
||||||
|
// Need to be used in all place when modify table. Parameter is the row index in table, need to calculate the index in dataSource.
|
||||||
|
return index + (this.state.page - 1) * 10;
|
||||||
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
if (this.props.mode === "edit") {
|
if (this.props.mode === "edit") {
|
||||||
this.synPolicies();
|
this.synPolicies();
|
||||||
@ -46,8 +55,8 @@ class PolicyTable extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cancel = (table, index) => {
|
cancel = (table, index) => {
|
||||||
Object.keys(table[index]).forEach((key) => {
|
Object.keys(table[this.getIndex(index)]).forEach((key) => {
|
||||||
table[index][key] = this.state.oldPolicy[key];
|
table[this.getIndex(index)][key] = this.state.oldPolicy[key];
|
||||||
});
|
});
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
this.setState({editingIndex: "", oldPolicy: ""});
|
this.setState({editingIndex: "", oldPolicy: ""});
|
||||||
@ -62,23 +71,28 @@ class PolicyTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateField(table, index, key, value) {
|
updateField(table, index, key, value) {
|
||||||
table[index][key] = value;
|
table[this.getIndex(index)][key] = value;
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
addRow(table) {
|
addRow(table) {
|
||||||
const row = {Ptype: "p"};
|
const row = {key: this.count, Ptype: "p"};
|
||||||
if (table === undefined) {
|
if (table === undefined) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
table = Setting.addRow(table, row, "top");
|
table = Setting.addRow(table, row, "top");
|
||||||
|
|
||||||
|
this.count = this.count + 1;
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
this.edit(row, 0);
|
this.edit(row, 0);
|
||||||
this.setState({add: true});
|
this.setState({
|
||||||
|
page: 1,
|
||||||
|
add: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRow(table, i) {
|
deleteRow(table, index) {
|
||||||
table = Setting.deleteRow(table, i);
|
table = Setting.deleteRow(table, this.getIndex(index));
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +105,14 @@ class PolicyTable extends React.Component {
|
|||||||
AdapterBackend.syncPolicies(this.props.owner, this.props.name)
|
AdapterBackend.syncPolicies(this.props.owner, this.props.name)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({policyLists: res.data});
|
|
||||||
Setting.showMessage("success", i18next.t("adapter:Sync policies successfully"));
|
Setting.showMessage("success", i18next.t("adapter:Sync policies successfully"));
|
||||||
|
|
||||||
|
const policyList = res.data;
|
||||||
|
policyList.map((policy, index) => {
|
||||||
|
policy.key = index;
|
||||||
|
});
|
||||||
|
this.count = policyList.length;
|
||||||
|
this.setState({policyLists: policyList});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("adapter:Failed to sync policies")}: ${res.msg}`);
|
||||||
}
|
}
|
||||||
@ -129,12 +149,12 @@ class PolicyTable extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePolicy(table, i) {
|
deletePolicy(table, index) {
|
||||||
AdapterBackend.RemovePolicy(this.props.owner, this.props.name, table[i]).then(res => {
|
AdapterBackend.RemovePolicy(this.props.owner, this.props.name, table[this.getIndex(index)]).then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
table = Setting.deleteRow(table, i);
|
|
||||||
this.updateTable(table);
|
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
|
|
||||||
|
this.deleteRow(table, index);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", i18next.t("general:Failed to delete"));
|
Setting.showMessage("error", i18next.t("general:Failed to delete"));
|
||||||
}
|
}
|
||||||
@ -279,9 +299,14 @@ class PolicyTable extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
pagination={{
|
pagination={{
|
||||||
defaultPageSize: 10,
|
defaultPageSize: this.pageSize,
|
||||||
|
onChange: (page) => this.setState({
|
||||||
|
page: page,
|
||||||
|
}),
|
||||||
|
disabled: this.state.editingIndex !== "",
|
||||||
|
current: this.state.page,
|
||||||
}}
|
}}
|
||||||
columns={columns} dataSource={table} rowKey="index" size="middle" bordered
|
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
|
173
web/src/common/theme/ColorPicker.js
Normal file
173
web/src/common/theme/ColorPicker.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/** @jsxImportSource @emotion/react */
|
||||||
|
|
||||||
|
import {Input, Popover, Space, theme} from "antd";
|
||||||
|
import React, {useEffect, useMemo, useState} from "react";
|
||||||
|
import {css} from "@emotion/react";
|
||||||
|
import {TinyColor} from "@ctrl/tinycolor";
|
||||||
|
import ColorPanel from "antd-token-previewer/es/ColorPanel";
|
||||||
|
|
||||||
|
export const BLUE_COLOR = "#1677FF";
|
||||||
|
export const PINK_COLOR = "#ED4192";
|
||||||
|
export const GREEN_COLOR = "#00B96B";
|
||||||
|
|
||||||
|
export const COLORS = [
|
||||||
|
{
|
||||||
|
color: BLUE_COLOR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "#5734d3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "#9E339F",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: PINK_COLOR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "#E0282E",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "#F4801A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "#F2BD27",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: GREEN_COLOR,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PRESET_COLORS = COLORS.map(({color}) => color);
|
||||||
|
|
||||||
|
const {useToken} = theme;
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const {token} = useToken();
|
||||||
|
return {
|
||||||
|
color: css `
|
||||||
|
width: ${token.controlHeightLG / 2}px;
|
||||||
|
height: ${token.controlHeightLG / 2}px;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all ${token.motionDurationFast};
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
& > input[type="radio"] {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
colorActive: css `
|
||||||
|
box-shadow: 0 0 0 1px ${token.colorBgContainer},
|
||||||
|
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const DebouncedColorPanel = ({color, onChange}) => {
|
||||||
|
const [value, setValue] = useState(color);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
onChange?.(value);
|
||||||
|
}, 200);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(color);
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
return <ColorPanel color={value} onChange={setValue} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ColorPicker({value, onChange}) {
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
const matchColors = useMemo(() => {
|
||||||
|
const valueStr = new TinyColor(value).toRgbString();
|
||||||
|
let existActive = false;
|
||||||
|
|
||||||
|
const colors = PRESET_COLORS.map((color) => {
|
||||||
|
const colorStr = new TinyColor(color).toRgbString();
|
||||||
|
const active = colorStr === valueStr;
|
||||||
|
existActive = existActive || active;
|
||||||
|
|
||||||
|
return {color, active, picker: false};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...colors,
|
||||||
|
{
|
||||||
|
color: "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",
|
||||||
|
picker: true,
|
||||||
|
active: !existActive,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space size="large">
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={(event) => {
|
||||||
|
onChange?.(event.target.value);
|
||||||
|
}}
|
||||||
|
style={{width: 120}}
|
||||||
|
/>
|
||||||
|
<Space size="middle">
|
||||||
|
{matchColors.map(({color, active, picker}) => {
|
||||||
|
let colorNode = (
|
||||||
|
<label
|
||||||
|
key={color}
|
||||||
|
css={[style.color, active && style.colorActive]}
|
||||||
|
style={{
|
||||||
|
background: color,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!picker) {
|
||||||
|
onChange?.(color);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="radio" name={picker ? "picker" : "color"} tabIndex={picker ? -1 : 0} />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picker) {
|
||||||
|
colorNode = (
|
||||||
|
<Popover
|
||||||
|
key={color}
|
||||||
|
overlayInnerStyle={{padding: 0}}
|
||||||
|
content={
|
||||||
|
<DebouncedColorPanel color={value || ""} onChange={(c) => onChange?.(c)} />
|
||||||
|
}
|
||||||
|
trigger="click"
|
||||||
|
showArrow={false}
|
||||||
|
>
|
||||||
|
{colorNode}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorNode;
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
38
web/src/common/theme/RadiusPicker.js
Normal file
38
web/src/common/theme/RadiusPicker.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import {InputNumber, Slider, Space} from "antd";
|
||||||
|
|
||||||
|
export default function RadiusPicker({value, onChange}) {
|
||||||
|
return (
|
||||||
|
<Space size="large">
|
||||||
|
<InputNumber
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
style={{width: 120}}
|
||||||
|
min={0}
|
||||||
|
formatter={(val) => `${val}px`}
|
||||||
|
parser={(str) => (str ? parseFloat(str) : str)}
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
tooltip={{open: false}}
|
||||||
|
style={{width: 128}}
|
||||||
|
min={0}
|
||||||
|
value={value}
|
||||||
|
max={20}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
108
web/src/common/theme/ThemeEditor.js
Normal file
108
web/src/common/theme/ThemeEditor.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import {Card, ConfigProvider, Form, Layout, Switch, theme} from "antd";
|
||||||
|
import ThemePicker from "./ThemePicker";
|
||||||
|
import ColorPicker from "./ColorPicker";
|
||||||
|
import RadiusPicker from "./RadiusPicker";
|
||||||
|
import * as React from "react";
|
||||||
|
import {GREEN_COLOR, PINK_COLOR} from "./ColorPicker";
|
||||||
|
import {Content} from "antd/es/layout/layout";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
import * as Setting from "../../Setting";
|
||||||
|
|
||||||
|
const ThemesInfo = {
|
||||||
|
default: {},
|
||||||
|
dark: {
|
||||||
|
borderRadius: 2,
|
||||||
|
},
|
||||||
|
lark: {
|
||||||
|
colorPrimary: GREEN_COLOR,
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
comic: {
|
||||||
|
colorPrimary: PINK_COLOR,
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = () => {};
|
||||||
|
|
||||||
|
export default function ThemeEditor(props) {
|
||||||
|
const themeData = props.themeData ?? Setting.ThemeDefault;
|
||||||
|
const onThemeChange = props.onThemeChange ?? onChange;
|
||||||
|
|
||||||
|
const {isCompact, themeType, ...themeToken} = themeData;
|
||||||
|
const isLight = themeType !== "dark";
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const algorithmFn = React.useMemo(() => {
|
||||||
|
const algorithms = [isLight ? theme.defaultAlgorithm : theme.darkAlgorithm];
|
||||||
|
|
||||||
|
if (isCompact === true) {
|
||||||
|
algorithms.push(theme.compactAlgorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return algorithms;
|
||||||
|
}, [isLight, isCompact]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mergedData = Object.assign(Object.assign(Object.assign({}, Setting.ThemeDefault), {themeType}), ThemesInfo[themeType]);
|
||||||
|
onThemeChange(null, mergedData);
|
||||||
|
form.setFieldsValue(mergedData);
|
||||||
|
}, [themeType]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
...themeToken,
|
||||||
|
},
|
||||||
|
hashed: true,
|
||||||
|
algorithm: algorithmFn,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout style={{width: "800px", backgroundColor: "white"}}>
|
||||||
|
<Content >
|
||||||
|
<Card
|
||||||
|
title={i18next.t("theme:Theme")}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
initialValues={themeData}
|
||||||
|
onValuesChange={onThemeChange}
|
||||||
|
labelCol={{span: 4}}
|
||||||
|
wrapperCol={{span: 20}}
|
||||||
|
style={{width: "800px"}}
|
||||||
|
>
|
||||||
|
<Form.Item label={i18next.t("theme:Theme")} name="themeType">
|
||||||
|
<ThemePicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={i18next.t("theme:Primary color")} name="colorPrimary">
|
||||||
|
<ColorPicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={i18next.t("theme:Border radius")} name="borderRadius">
|
||||||
|
<RadiusPicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={i18next.t("theme:Is compact")} valuePropName="checked" name="isCompact">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
104
web/src/common/theme/ThemePicker.js
Normal file
104
web/src/common/theme/ThemePicker.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/** @jsxImportSource @emotion/react */
|
||||||
|
|
||||||
|
import {css} from "@emotion/react";
|
||||||
|
import {Space, theme} from "antd";
|
||||||
|
import * as React from "react";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import * as Setting from "../../Setting";
|
||||||
|
|
||||||
|
const {useToken} = theme;
|
||||||
|
|
||||||
|
export const THEMES = {
|
||||||
|
default: `${Setting.StaticBaseUrl}/img/theme_default.svg`,
|
||||||
|
dark: `${Setting.StaticBaseUrl}/img/theme_dark.svg`,
|
||||||
|
lark: `${Setting.StaticBaseUrl}/img/theme_lark.svg`,
|
||||||
|
comic: `${Setting.StaticBaseUrl}/img/theme_comic.svg`,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.values(THEMES).map(value => new Image().src = value);
|
||||||
|
|
||||||
|
const themeTypes = {
|
||||||
|
default: "Default", // i18next.t("theme:Default")
|
||||||
|
dark: "Dark", // i18next.t("theme:Dark")
|
||||||
|
lark: "Document", // i18next.t("theme:Document")
|
||||||
|
comic: "Blossom", // i18next.t("theme:Blossom")
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyle = () => {
|
||||||
|
const {token} = useToken();
|
||||||
|
return {
|
||||||
|
themeCard: css `
|
||||||
|
border-radius: ${token.borderRadius}px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all ${token.motionDurationSlow};
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
& > input[type="radio"] {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: top;
|
||||||
|
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||||
|
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within,
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
themeCardActive: css `
|
||||||
|
box-shadow: 0 0 0 1px ${token.colorBgContainer},
|
||||||
|
0 0 0 ${token.controlOutlineWidth * 2 + 1}px ${token.colorPrimary};
|
||||||
|
|
||||||
|
&:hover:not(:focus-within) {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ThemePicker({value, onChange}) {
|
||||||
|
const {token} = useToken();
|
||||||
|
const style = useStyle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space size={token.paddingLG}>
|
||||||
|
{Object.keys(THEMES).map((theme) => {
|
||||||
|
const url = THEMES[theme];
|
||||||
|
return (
|
||||||
|
<Space key={theme} direction="vertical" align="center">
|
||||||
|
<label
|
||||||
|
css={[style.themeCard, value === theme && style.themeCardActive]}
|
||||||
|
onClick={() => {
|
||||||
|
onChange?.(theme);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="radio" name="theme" />
|
||||||
|
<img src={url} alt={theme} />
|
||||||
|
</label>
|
||||||
|
<span>{i18next.t(`theme:${themeTypes[theme]}`)}</span>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
@ -22,7 +22,6 @@ import ru from "./locales/ru/data.json";
|
|||||||
import ja from "./locales/ja/data.json";
|
import ja from "./locales/ja/data.json";
|
||||||
import es from "./locales/es/data.json";
|
import es from "./locales/es/data.json";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
import * as Setting from "./Setting";
|
|
||||||
import {initReactI18next} from "react-i18next";
|
import {initReactI18next} from "react-i18next";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
@ -76,7 +75,6 @@ function initLanguage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Setting.changeMomentLanguage(language);
|
|
||||||
|
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Anmelden",
|
|
||||||
"Logout": "Abmelden",
|
"Logout": "Abmelden",
|
||||||
"My Account": "Mein Konto",
|
"My Account": "Mein Konto",
|
||||||
"Sign Up": "Registrieren"
|
"Sign Up": "Registrieren"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "Datei erfolgreich hochgeladen",
|
"File uploaded successfully": "Datei erfolgreich hochgeladen",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client-IP",
|
"Client IP": "Client-IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Erstellte Zeit",
|
"Created time": "Erstellte Zeit",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Standard Avatar",
|
"Default avatar": "Standard Avatar",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Standard Avatar",
|
"Default avatar": "Standard Avatar",
|
||||||
"Edit Organization": "Organisation bearbeiten",
|
"Edit Organization": "Organisation bearbeiten",
|
||||||
"Favicon": "Févicon",
|
"Favicon": "Févicon",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Zugangs-Token",
|
"Access token": "Zugangs-Token",
|
||||||
"Authorization code": "Autorisierungscode",
|
"Authorization code": "Autorisierungscode",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Login",
|
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
"My Account": "My Account",
|
"My Account": "My Account",
|
||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Enable signup - Tooltip",
|
"Enable signup - Tooltip": "Enable signup - Tooltip",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "File uploaded successfully",
|
"File uploaded successfully": "File uploaded successfully",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
"Edit Organization": "Edit Organization",
|
"Edit Organization": "Edit Organization",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Access token",
|
"Access token": "Access token",
|
||||||
"Authorization code": "Authorization code",
|
"Authorization code": "Authorization code",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Iniciar sesión",
|
|
||||||
"Logout": "Cerrar sesión",
|
"Logout": "Cerrar sesión",
|
||||||
"My Account": "Mi cuenta",
|
"My Account": "Mi cuenta",
|
||||||
"Sign Up": "Registrarme"
|
"Sign Up": "Registrarme"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Habilitar nuevos registros - Tooltip",
|
"Enable signup - Tooltip": "Habilitar nuevos registros - Tooltip",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "El archivo ha sido subido con éxito",
|
"File uploaded successfully": "El archivo ha sido subido con éxito",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click para subir archivo",
|
"Click to Upload": "Click para subir archivo",
|
||||||
"Client IP": "IP del Cliente",
|
"Client IP": "IP del Cliente",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Fecha de creación",
|
"Created time": "Fecha de creación",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Avatar por defecto",
|
"Default avatar": "Avatar por defecto",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Avatar por defecto",
|
"Default avatar": "Avatar por defecto",
|
||||||
"Edit Organization": "Editar Organización",
|
"Edit Organization": "Editar Organización",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Es el perfil publico",
|
"Is profile public": "Es el perfil publico",
|
||||||
"Is profile public - Tooltip": "Es el perfil publico - Tooltip",
|
"Is profile public - Tooltip": "Es el perfil publico - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Token de acceso",
|
"Access token": "Token de acceso",
|
||||||
"Authorization code": "Código de autorización",
|
"Authorization code": "Código de autorización",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Se connecter",
|
|
||||||
"Logout": "Déconnexion",
|
"Logout": "Déconnexion",
|
||||||
"My Account": "Mon Compte",
|
"My Account": "Mon Compte",
|
||||||
"Sign Up": "S'inscrire"
|
"Sign Up": "S'inscrire"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "Fichier téléchargé avec succès",
|
"File uploaded successfully": "Fichier téléchargé avec succès",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "IP du client",
|
"Client IP": "IP du client",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Date de création",
|
"Created time": "Date de création",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Avatar par défaut",
|
"Default avatar": "Avatar par défaut",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Avatar par défaut",
|
"Default avatar": "Avatar par défaut",
|
||||||
"Edit Organization": "Modifier l'organisation",
|
"Edit Organization": "Modifier l'organisation",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Jeton d'accès",
|
"Access token": "Jeton d'accès",
|
||||||
"Authorization code": "Code d'autorisation",
|
"Authorization code": "Code d'autorisation",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "ログイン",
|
|
||||||
"Logout": "ログアウト",
|
"Logout": "ログアウト",
|
||||||
"My Account": "マイアカウント",
|
"My Account": "マイアカウント",
|
||||||
"Sign Up": "新規登録"
|
"Sign Up": "新規登録"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "ファイルが正常にアップロードされました",
|
"File uploaded successfully": "ファイルが正常にアップロードされました",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "クライアント IP",
|
"Client IP": "クライアント IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "作成日時",
|
"Created time": "作成日時",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "デフォルトのアバター",
|
"Default avatar": "デフォルトのアバター",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "デフォルトのアバター",
|
"Default avatar": "デフォルトのアバター",
|
||||||
"Edit Organization": "組織を編集",
|
"Edit Organization": "組織を編集",
|
||||||
"Favicon": "ファビコン",
|
"Favicon": "ファビコン",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "アクセストークン",
|
"Access token": "アクセストークン",
|
||||||
"Authorization code": "認証コード",
|
"Authorization code": "認証コード",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Login",
|
|
||||||
"Logout": "Logout",
|
"Logout": "Logout",
|
||||||
"My Account": "My Account",
|
"My Account": "My Account",
|
||||||
"Sign Up": "Sign Up"
|
"Sign Up": "Sign Up"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "File uploaded successfully",
|
"File uploaded successfully": "File uploaded successfully",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
"Edit Organization": "Edit Organization",
|
"Edit Organization": "Edit Organization",
|
||||||
"Favicon": "Favicon",
|
"Favicon": "Favicon",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Access token",
|
"Access token": "Access token",
|
||||||
"Authorization code": "Authorization code",
|
"Authorization code": "Authorization code",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "Логин",
|
|
||||||
"Logout": "Выйти",
|
"Logout": "Выйти",
|
||||||
"My Account": "Мой аккаунт",
|
"My Account": "Мой аккаунт",
|
||||||
"Sign Up": "Регистрация"
|
"Sign Up": "Регистрация"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
"Enable signup - Tooltip": "Whether to allow users to sign up",
|
||||||
"Failed to sign in": "Failed to sign in",
|
"Failed to sign in": "Failed to sign in",
|
||||||
"File uploaded successfully": "Файл успешно загружен",
|
"File uploaded successfully": "Файл успешно загружен",
|
||||||
|
"Follow organization theme": "Follow organization theme",
|
||||||
"Form CSS": "Form CSS",
|
"Form CSS": "Form CSS",
|
||||||
"Form CSS - Edit": "Form CSS - Edit",
|
"Form CSS - Edit": "Form CSS - Edit",
|
||||||
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
"Form CSS - Tooltip": "Form CSS - Tooltip",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
||||||
"Client IP": "IP клиента",
|
"Client IP": "IP клиента",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Compact": "Compact",
|
|
||||||
"Created time": "Время создания",
|
"Created time": "Время создания",
|
||||||
"Dark": "Dark",
|
|
||||||
"Default": "Default",
|
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Аватар по умолчанию",
|
"Default avatar": "Аватар по умолчанию",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "Аватар по умолчанию",
|
"Default avatar": "Аватар по умолчанию",
|
||||||
"Edit Organization": "Изменить организацию",
|
"Edit Organization": "Изменить организацию",
|
||||||
"Favicon": "Иконка",
|
"Favicon": "Иконка",
|
||||||
|
"Follow global theme": "Follow global theme",
|
||||||
"InitScore": "InitScore",
|
"InitScore": "InitScore",
|
||||||
"Is profile public": "Is profile public",
|
"Is profile public": "Is profile public",
|
||||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "Unknown Version",
|
"Unknown Version": "Unknown Version",
|
||||||
"Version": "Version"
|
"Version": "Version"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "Blossom",
|
||||||
|
"Border radius": "Border radius",
|
||||||
|
"Compact": "Compact",
|
||||||
|
"Customize theme": "Customize theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
|
"Document": "Document",
|
||||||
|
"Is compact": "Is compact",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Theme - Tooltip": "Theme - Tooltip"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "Маркер доступа",
|
"Access token": "Маркер доступа",
|
||||||
"Authorization code": "Код авторизации",
|
"Authorization code": "Код авторизации",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"Login": "登录",
|
|
||||||
"Logout": "登出",
|
"Logout": "登出",
|
||||||
"My Account": "我的账户",
|
"My Account": "我的账户",
|
||||||
"Sign Up": "注册"
|
"Sign Up": "注册"
|
||||||
@ -41,6 +40,7 @@
|
|||||||
"Enable signup - Tooltip": "是否允许用户注册",
|
"Enable signup - Tooltip": "是否允许用户注册",
|
||||||
"Failed to sign in": "登录失败",
|
"Failed to sign in": "登录失败",
|
||||||
"File uploaded successfully": "文件上传成功",
|
"File uploaded successfully": "文件上传成功",
|
||||||
|
"Follow organization theme": "使用组织主题",
|
||||||
"Form CSS": "表单CSS",
|
"Form CSS": "表单CSS",
|
||||||
"Form CSS - Edit": "编辑表单CSS",
|
"Form CSS - Edit": "编辑表单CSS",
|
||||||
"Form CSS - Tooltip": "表单的CSS样式(如增加边框和阴影)",
|
"Form CSS - Tooltip": "表单的CSS样式(如增加边框和阴影)",
|
||||||
@ -158,10 +158,7 @@
|
|||||||
"Click to Upload": "点击上传",
|
"Click to Upload": "点击上传",
|
||||||
"Client IP": "客户端IP",
|
"Client IP": "客户端IP",
|
||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
"Compact": "紧凑",
|
|
||||||
"Created time": "创建时间",
|
"Created time": "创建时间",
|
||||||
"Dark": "黑暗",
|
|
||||||
"Default": "默认",
|
|
||||||
"Default application": "默认应用",
|
"Default application": "默认应用",
|
||||||
"Default application - Tooltip": "默认应用",
|
"Default application - Tooltip": "默认应用",
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
@ -341,6 +338,7 @@
|
|||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
"Edit Organization": "编辑组织",
|
"Edit Organization": "编辑组织",
|
||||||
"Favicon": "图标",
|
"Favicon": "图标",
|
||||||
|
"Follow global theme": "使用全局默认主题",
|
||||||
"InitScore": "初始积分",
|
"InitScore": "初始积分",
|
||||||
"Is profile public": "用户个人页公开",
|
"Is profile public": "用户个人页公开",
|
||||||
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
||||||
@ -690,6 +688,19 @@
|
|||||||
"Unknown Version": "未知版本",
|
"Unknown Version": "未知版本",
|
||||||
"Version": "版本"
|
"Version": "版本"
|
||||||
},
|
},
|
||||||
|
"theme": {
|
||||||
|
"Blossom": "桃花缘",
|
||||||
|
"Border radius": "圆角",
|
||||||
|
"Compact": "紧凑",
|
||||||
|
"Customize theme": "定制主题",
|
||||||
|
"Dark": "暗黑",
|
||||||
|
"Default": "默认",
|
||||||
|
"Document": "知识协作",
|
||||||
|
"Is compact": "宽松度",
|
||||||
|
"Primary color": "主色",
|
||||||
|
"Theme": "主题",
|
||||||
|
"Theme - Tooltip": "为你的应用设置主题"
|
||||||
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"Access token": "访问令牌",
|
"Access token": "访问令牌",
|
||||||
"Authorization code": "授权码",
|
"Authorization code": "授权码",
|
||||||
|
@ -23,18 +23,19 @@ class PropertyTable extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
properties: [],
|
properties: [],
|
||||||
count: this.props.properties !== null ? Object.entries(this.props.properties).length : 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// transfer the Object to object[]
|
// transfer the Object to object[]
|
||||||
if (this.props.properties !== null) {
|
if (this.props.properties !== null) {
|
||||||
Object.entries(this.props.properties).map((item, index) => {
|
Object.entries(this.props.properties).map((item, index) => {
|
||||||
this.state.properties.push({key: index, name: item[0], value: item[1]});
|
this.state.properties.push({key: index, name: item[0], value: item[1]});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
page = 1;
|
page = 1;
|
||||||
|
pageSize = 10;
|
||||||
|
count = this.props.properties !== null ? Object.entries(this.props.properties).length : 0;
|
||||||
|
|
||||||
updateTable(table) {
|
updateTable(table) {
|
||||||
this.setState({properties: table});
|
this.setState({properties: table});
|
||||||
@ -46,12 +47,12 @@ class PropertyTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addRow(table) {
|
addRow(table) {
|
||||||
const row = {key: this.state.count, name: "", value: ""};
|
const row = {key: this.count, name: "", value: ""};
|
||||||
if (table === undefined) {
|
if (table === undefined) {
|
||||||
table = [];
|
table = [];
|
||||||
}
|
}
|
||||||
table = Setting.addRow(table, row);
|
table = Setting.addRow(table, row);
|
||||||
this.setState({count: this.state.count + 1});
|
this.count = this.count + 1;
|
||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +62,8 @@ class PropertyTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIndex(index) {
|
getIndex(index) {
|
||||||
// Parameter is the row index in table, need to calculate the index in dataSource. 10 is the pageSize.
|
// Need to be used in all place when modify table. Parameter is the row index in table, need to calculate the index in dataSource.
|
||||||
return index + (this.page - 1) * 10;
|
return index + (this.page - 1) * this.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateField(table, index, key, value) {
|
updateField(table, index, key, value) {
|
||||||
@ -114,7 +115,10 @@ class PropertyTable extends React.Component {
|
|||||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
pagination={{onChange: page => {this.page = page;}}}
|
pagination={{
|
||||||
|
defaultPageSize: this.pageSize,
|
||||||
|
onChange: page => {this.page = page;},
|
||||||
|
}}
|
||||||
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
|
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
24225
web/yarn.lock
24225
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user