mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 09:23:40 +08:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
19d351d157 | |||
d0751bf2fa | |||
290cc60f00 | |||
6a1ec51978 | |||
dffa68cbce | |||
fad209a7a3 | |||
8b222ce2e3 | |||
c5293f428d | |||
146aec9ee8 | |||
50a52de856 | |||
8f7a8d7d4f | |||
23f3fe1e3c | |||
59ff5e02ab | |||
8d41508d6b | |||
04f70cf012 | |||
83724c73f9 | |||
33e419e133 | |||
b832c304ae | |||
4c7f6fda37 | |||
e4a54fe375 | |||
87da3dad76 | |||
44ad88353f | |||
a955fb57d6 | |||
d2960ad66b | |||
5243aabf43 | |||
d3a2c2a66e | |||
0a9058a585 | |||
225719810b | |||
c634d4a891 | |||
3dc01ec85d | |||
a7324f1da1 | |||
6da452d7e0 | |||
5abcf913e6 | |||
58455e688e | |||
4d6f68eddc | |||
67f3c5a489 | |||
9c48582e0c |
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@ -125,26 +125,36 @@ jobs:
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
|
||||||
- name: Push to Docker Hub
|
- name: Push to Docker Hub
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
target: STANDARD
|
target: STANDARD
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||||
|
|
||||||
- name: Push All In One Version to Docker Hub
|
- name: Push All In One Version to Docker Hub
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
target: ALLINONE
|
target: ALLINONE
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,3 +27,6 @@ logs/
|
|||||||
files/
|
files/
|
||||||
lastupdate.tmp
|
lastupdate.tmp
|
||||||
commentsRouter*.go
|
commentsRouter*.go
|
||||||
|
|
||||||
|
# ignore build result
|
||||||
|
casdoor
|
20
Dockerfile
20
Dockerfile
@ -2,7 +2,7 @@ FROM node:16.13.0 AS FRONT
|
|||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web .
|
COPY ./web .
|
||||||
RUN yarn config set registry https://registry.npmmirror.com
|
RUN yarn config set registry https://registry.npmmirror.com
|
||||||
RUN yarn install && yarn run build
|
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||||
|
|
||||||
|
|
||||||
FROM golang:1.17.5 AS BACK
|
FROM golang:1.17.5 AS BACK
|
||||||
@ -13,16 +13,26 @@ RUN ./build.sh
|
|||||||
|
|
||||||
FROM alpine:latest AS STANDARD
|
FROM alpine:latest AS STANDARD
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
|
ARG USER=casdoor
|
||||||
|
|
||||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||||
|
RUN apk add --update sudo
|
||||||
RUN apk add curl
|
RUN apk add curl
|
||||||
RUN apk add ca-certificates && update-ca-certificates
|
RUN apk add ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
|
RUN adduser -D $USER -u 1000 \
|
||||||
|
&& echo "$USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER \
|
||||||
|
&& chmod 0440 /etc/sudoers.d/$USER \
|
||||||
|
&& mkdir logs \
|
||||||
|
&& chown -R $USER:$USER logs
|
||||||
|
|
||||||
|
USER 1000
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY --from=BACK /go/src/casdoor/server ./server
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server ./server
|
||||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
COPY --from=FRONT /web/build ./web/build
|
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
||||||
|
|
||||||
ENTRYPOINT ["/server"]
|
ENTRYPOINT ["/server"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- International: https://casdoor.org
|
- International: https://casdoor.org
|
||||||
- Asian mirror: https://docs.casdoor.cn
|
- Asian mirror: https://casdoor.cn
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ https://casdoor.org/docs/how-to-connect/overview
|
|||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
https://casdoor.org/docs/integration/apisix
|
https://casdoor.org/docs/category/integrations
|
||||||
|
|
||||||
## How to contact?
|
## How to contact?
|
||||||
|
|
||||||
|
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
We are grateful for security researchers and users reporting a vulnerability to us first. To ensure that your request is handled in a timely manner and we can keep users safe, please follow the below guidelines.
|
||||||
|
|
||||||
|
- **Please do not report security vulnerabilities directly on GitHub.**
|
||||||
|
|
||||||
|
- To report a vulnerability, please email [admin@casdoor.org](admin@casdoor.org).
|
@ -15,12 +15,14 @@
|
|||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
xormadapter "github.com/casbin/xorm-adapter/v3"
|
xormadapter "github.com/casbin/xorm-adapter/v3"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -138,6 +140,12 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userId := fmt.Sprintf("%s/%s", subOwner, subName)
|
||||||
|
user := object.GetUser(userId)
|
||||||
|
if user != nil && user.IsAdmin && subOwner == objOwner {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -133,11 +133,12 @@ func (c *ApiController) GetDefaultApplication() {
|
|||||||
userId := c.GetSessionUsername()
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
application := object.GetMaskedApplication(object.GetDefaultApplication(id), userId)
|
application, err := object.GetDefaultApplication(id)
|
||||||
if application == nil {
|
if err != nil {
|
||||||
c.ResponseError("Please set a default application for this organization")
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(application)
|
maskedApplication := object.GetMaskedApplication(application, userId)
|
||||||
|
c.ResponseOk(maskedApplication)
|
||||||
}
|
}
|
||||||
|
@ -183,6 +183,12 @@ func (c *ApiController) AddUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg := object.CheckUsername(user.Name)
|
||||||
|
if msg != "" {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
|
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -100,7 +101,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
|||||||
userName := c.Input().Get("name")
|
userName := c.Input().Get("name")
|
||||||
user := object.GetUserByFields(userOwner, userName)
|
user := object.GetUserByFields(userOwner, userName)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("Please Giveout Owner and Username.")
|
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", userOwner, userName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
options, sessionData, err := webauthnObj.BeginLogin(user)
|
options, sessionData, err := webauthnObj.BeginLogin(user)
|
||||||
@ -121,6 +122,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
|||||||
// @Success 200 {object} Response "The Response object"
|
// @Success 200 {object} Response "The Response object"
|
||||||
// @router /webauthn/signin/finish [post]
|
// @router /webauthn/signin/finish [post]
|
||||||
func (c *ApiController) WebAuthnSigninFinish() {
|
func (c *ApiController) WebAuthnSigninFinish() {
|
||||||
|
responseType := c.Input().Get("responseType")
|
||||||
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
sessionObj := c.GetSession("authentication")
|
sessionObj := c.GetSession("authentication")
|
||||||
sessionData, ok := sessionObj.(webauthn.SessionData)
|
sessionData, ok := sessionObj.(webauthn.SessionData)
|
||||||
@ -138,5 +140,11 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
|||||||
}
|
}
|
||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(userId)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||||
c.ResponseOk(userId)
|
|
||||||
|
application := object.GetApplicationByUser(user)
|
||||||
|
var form RequestForm
|
||||||
|
form.Type = responseType
|
||||||
|
resp := c.HandleLoggedIn(application, user, &form)
|
||||||
|
c.Data["json"] = resp
|
||||||
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if provider == "steam" {
|
if provider == "steam" {
|
||||||
user.Username = user.DisplayName
|
user.Username = user.Id
|
||||||
user.Email = ""
|
user.Email = ""
|
||||||
}
|
}
|
||||||
return &user
|
return &user
|
||||||
|
@ -19,11 +19,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/beego/beego"
|
"github.com/beego/beego"
|
||||||
|
xormadapter "github.com/casbin/xorm-adapter/v3"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||||
_ "github.com/lib/pq" // db = postgres
|
_ "github.com/lib/pq" // db = postgres
|
||||||
|
"xorm.io/xorm/migrate"
|
||||||
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
|
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
@ -40,6 +42,7 @@ func InitConfig() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
|
|
||||||
InitAdapter(true)
|
InitAdapter(true)
|
||||||
|
initMigrations()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitAdapter(createDatabase bool) {
|
func InitAdapter(createDatabase bool) {
|
||||||
@ -214,6 +217,11 @@ func (a *Adapter) createTable() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||||
@ -239,3 +247,22 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
|
|||||||
}
|
}
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initMigrations() {
|
||||||
|
migrations := []*migrate.Migration{
|
||||||
|
{
|
||||||
|
ID: "20221015CasbinRule--fill ptype field with p",
|
||||||
|
Migrate: func(tx *xorm.Engine) error {
|
||||||
|
_, err := tx.Cols("ptype").Update(&xormadapter.CasbinRule{
|
||||||
|
Ptype: "p",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
Rollback: func(tx *xorm.Engine) error {
|
||||||
|
return tx.DropTables(&xormadapter.CasbinRule{})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m := migrate.New(adapter.Engine, migrate.DefaultOptions, migrations)
|
||||||
|
m.Migrate()
|
||||||
|
}
|
||||||
|
@ -50,7 +50,7 @@ func downloadFile(url string) (*bytes.Buffer, error) {
|
|||||||
return fileBuffer, nil
|
return fileBuffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPermanentAvatarUrl(organization string, username string, url string) string {
|
func getPermanentAvatarUrl(organization string, username string, url string, upload bool) string {
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -62,6 +62,14 @@ func getPermanentAvatarUrl(organization string, username string, url string) str
|
|||||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
|
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
|
||||||
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||||
|
|
||||||
|
if upload {
|
||||||
|
DownloadAndUpload(url, fullFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploadedFileUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownloadAndUpload(url string, fullFilePath string) {
|
||||||
fileBuffer, err := downloadFile(url)
|
fileBuffer, err := downloadFile(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -71,6 +79,4 @@ func getPermanentAvatarUrl(organization string, username string, url string) str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedFileUrl
|
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func TestSyncPermanentAvatars(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
|
||||||
updateUserColumn("permanent_avatar", user)
|
updateUserColumn("permanent_avatar", user)
|
||||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,11 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
|||||||
if reWhiteSpace.MatchString(username) {
|
if reWhiteSpace.MatchString(username) {
|
||||||
return "username cannot contain white spaces"
|
return "username cannot contain white spaces"
|
||||||
}
|
}
|
||||||
|
msg := CheckUsername(username)
|
||||||
|
if msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
if HasUserByField(organization.Name, "name", username) {
|
if HasUserByField(organization.Name, "name", username) {
|
||||||
return "username already exists"
|
return "username already exists"
|
||||||
}
|
}
|
||||||
@ -313,3 +318,24 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
|
|||||||
}
|
}
|
||||||
return allowed, err
|
return allowed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUsername(username string) string {
|
||||||
|
if username == "" {
|
||||||
|
return "Empty username."
|
||||||
|
} else if len(username) > 39 {
|
||||||
|
return "Username is too long (maximum is 39 characters)."
|
||||||
|
}
|
||||||
|
|
||||||
|
exclude, _ := regexp.Compile("^[\u0021-\u007E]+$")
|
||||||
|
if !exclude.MatchString(username) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
|
||||||
|
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
|
||||||
|
if !re.MatchString(username) {
|
||||||
|
return "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline."
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -409,6 +409,7 @@ func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found && !AddUser(&User{
|
if !found && !AddUser(&User{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||||
|
@ -218,14 +218,14 @@ func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, str
|
|||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultApplication(id string) *Application {
|
func GetDefaultApplication(id string) (*Application, error) {
|
||||||
organization := GetOrganization(id)
|
organization := GetOrganization(id)
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
return nil
|
return nil, fmt.Errorf("The organization: %s does not exist", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization.DefaultApplication != "" {
|
if organization.DefaultApplication != "" {
|
||||||
return getApplication("admin", organization.DefaultApplication)
|
return getApplication("admin", organization.DefaultApplication), fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
@ -235,7 +235,7 @@ func GetDefaultApplication(id string) *Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(applications) == 0 {
|
if len(applications) == 0 {
|
||||||
return nil
|
return nil, fmt.Errorf("The application does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultApplication := applications[0]
|
defaultApplication := applications[0]
|
||||||
@ -249,5 +249,5 @@ func GetDefaultApplication(id string) *Application {
|
|||||||
extendApplicationWithProviders(defaultApplication)
|
extendApplicationWithProviders(defaultApplication)
|
||||||
extendApplicationWithOrg(defaultApplication)
|
extendApplicationWithOrg(defaultApplication)
|
||||||
|
|
||||||
return defaultApplication
|
return defaultApplication, nil
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
columns := syncer.getCasdoorColumns()
|
columns := syncer.getCasdoorColumns()
|
||||||
|
@ -703,7 +703,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
|
|||||||
}
|
}
|
||||||
// Add new user
|
// Add new user
|
||||||
var name string
|
var name string
|
||||||
if username != "" {
|
if CheckUsername(username) == "" {
|
||||||
name = username
|
name = username
|
||||||
} else {
|
} else {
|
||||||
name = fmt.Sprintf("wechat-%s", openId)
|
name = fmt.Sprintf("wechat-%s", openId)
|
||||||
|
@ -386,7 +386,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
|||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
@ -419,7 +419,7 @@ func UpdateUserForAllFields(id string, user *User) bool {
|
|||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||||
@ -449,7 +449,7 @@ func AddUser(user *User) bool {
|
|||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
user.PreHash = user.Hash
|
user.PreHash = user.Hash
|
||||||
|
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||||
|
|
||||||
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ func AddUsers(users []*User) bool {
|
|||||||
user.UpdateUserHash()
|
user.UpdateUserHash()
|
||||||
user.PreHash = user.Hash
|
user.PreHash = user.Hash
|
||||||
|
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := adapter.Engine.Insert(users)
|
affected, err := adapter.Engine.Insert(users)
|
||||||
|
@ -159,7 +159,7 @@ func DisableVerificationCode(dest string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from Casnode/object/validateCode.go line 116
|
// From Casnode/object/validateCode.go line 116
|
||||||
var stdNums = []byte("0123456789")
|
var stdNums = []byte("0123456789")
|
||||||
|
|
||||||
func getRandomCode(length int) string {
|
func getRandomCode(length int) string {
|
||||||
|
@ -63,11 +63,16 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
if method == http.MethodGet {
|
if method == http.MethodGet {
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id == "" {
|
if id != "" {
|
||||||
return "", ""
|
return util.GetOwnerAndNameFromId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.GetOwnerAndNameFromId(id)
|
owner := ctx.Input.Query("owner")
|
||||||
|
if owner != "" {
|
||||||
|
return owner, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ""
|
||||||
} else {
|
} else {
|
||||||
body := ctx.Input.RequestBody
|
body := ctx.Input.RequestBody
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ module.exports = {
|
|||||||
options: {
|
options: {
|
||||||
lessLoaderOptions: {
|
lessLoaderOptions: {
|
||||||
lessOptions: {
|
lessOptions: {
|
||||||
modifyVars: {"@primary-color": "rgb(45,120,213)"},
|
modifyVars: {"@primary-color": "rgb(89,54,213)", "@border-radius-base": "5px"},
|
||||||
javascriptEnabled: true,
|
javascriptEnabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -16,8 +16,8 @@ import React, {Component} from "react";
|
|||||||
import "./App.less";
|
import "./App.less";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
import {Avatar, BackTop, Button, Card, Dropdown, Layout, Menu, Result} from "antd";
|
import {Avatar, BackTop, Button, Card, Drawer, Dropdown, 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";
|
||||||
@ -74,6 +74,7 @@ import ModelEditPage from "./ModelEditPage";
|
|||||||
import SystemInfo from "./SystemInfo";
|
import SystemInfo from "./SystemInfo";
|
||||||
import AdapterListPage from "./AdapterListPage";
|
import AdapterListPage from "./AdapterListPage";
|
||||||
import AdapterEditPage from "./AdapterEditPage";
|
import AdapterEditPage from "./AdapterEditPage";
|
||||||
|
import {withTranslation} from "react-i18next";
|
||||||
|
|
||||||
const {Header, Footer} = Layout;
|
const {Header, Footer} = Layout;
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ class App extends Component {
|
|||||||
selectedMenuKey: 0,
|
selectedMenuKey: 0,
|
||||||
account: undefined,
|
account: undefined,
|
||||||
uri: null,
|
uri: null,
|
||||||
|
menuVisible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Setting.initServerUrl();
|
Setting.initServerUrl();
|
||||||
@ -298,12 +300,12 @@ class App extends Component {
|
|||||||
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
|
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
|
||||||
<Menu.Item key="/account">
|
<Menu.Item key="/account">
|
||||||
<SettingOutlined />
|
<SettingOutlined />
|
||||||
|
|
||||||
{i18next.t("account:My Account")}
|
{i18next.t("account:My Account")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="/logout">
|
<Menu.Item key="/logout">
|
||||||
<LogoutOutlined />
|
<LogoutOutlined />
|
||||||
|
|
||||||
{i18next.t("account:Logout")}
|
{i18next.t("account:Logout")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -378,6 +380,9 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||||
res.push(
|
res.push(
|
||||||
<Menu.Item key="/users">
|
<Menu.Item key="/users">
|
||||||
<Link to="/users">
|
<Link to="/users">
|
||||||
@ -592,6 +597,18 @@ class App extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.setState({
|
||||||
|
menuVisible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
showMenu = () => {
|
||||||
|
this.setState({
|
||||||
|
menuVisible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (!Setting.isMobile()) {
|
if (!Setting.isMobile()) {
|
||||||
return (
|
return (
|
||||||
@ -610,7 +627,7 @@ class App extends Component {
|
|||||||
// theme="dark"
|
// theme="dark"
|
||||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||||
style={{lineHeight: "64px", width: "80%", position: "absolute", left: "145px"}}
|
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
this.renderMenu()
|
this.renderMenu()
|
||||||
@ -643,22 +660,28 @@ class App extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<Menu
|
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
|
||||||
// theme="dark"
|
<Menu
|
||||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
// theme="dark"
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
|
||||||
style={{lineHeight: "64px"}}
|
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||||
>
|
style={{lineHeight: "64px"}}
|
||||||
{
|
onClick={this.onClose}
|
||||||
this.renderMenu()
|
>
|
||||||
}
|
|
||||||
<div style = {{float: "right"}}>
|
|
||||||
{
|
{
|
||||||
this.renderAccount()
|
this.renderMenu()
|
||||||
}
|
}
|
||||||
<SelectLanguageBox />
|
</Menu>
|
||||||
</div>
|
</Drawer>
|
||||||
</Menu>
|
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
||||||
|
{i18next.t("general:Menu")}
|
||||||
|
</Button>
|
||||||
|
<div style = {{float: "right"}}>
|
||||||
|
{
|
||||||
|
this.renderAccount()
|
||||||
|
}
|
||||||
|
<SelectLanguageBox />
|
||||||
|
</div>
|
||||||
</Header>
|
</Header>
|
||||||
{
|
{
|
||||||
this.renderRouter()
|
this.renderRouter()
|
||||||
@ -682,7 +705,7 @@ class App extends Component {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
Made with <span style={{color: "rgb(255, 255, 255)"}}>❤️</span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casdoor.org" rel="noreferrer">Casdoor</a>
|
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a>
|
||||||
</Footer>
|
</Footer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -776,4 +799,4 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(App);
|
export default withRouter(withTranslation()(App));
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
color: #61dafb;
|
color: #61dafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root{
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,11 +59,18 @@
|
|||||||
height: 70px; /* Footer height */
|
height: 70px; /* Footer height */
|
||||||
}
|
}
|
||||||
|
|
||||||
.language_box {
|
#language-box-corner {
|
||||||
|
position: absolute;
|
||||||
|
top: 75px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-box {
|
||||||
background: url("@{StaticBaseUrl}/img/muti_language.svg");
|
background: url("@{StaticBaseUrl}/img/muti_language.svg");
|
||||||
background-size: 25px, 25px;
|
background-size: 25px, 25px;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
border-radius: 5px;
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 65px;
|
height: 65px;
|
||||||
float: right;
|
float: right;
|
||||||
@ -87,9 +94,19 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.loginBackground {
|
.loginBackground {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #ffffff no-repeat;
|
background: #fff no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React, {useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import Cropper from "react-cropper";
|
import Cropper from "react-cropper";
|
||||||
import "cropperjs/dist/cropper.css";
|
import "cropperjs/dist/cropper.css";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {Button, Col, Modal, Row} from "antd";
|
import {Button, Col, Modal, Row, Select} from "antd";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as ResourceBackend from "./backend/ResourceBackend";
|
import * as ResourceBackend from "./backend/ResourceBackend";
|
||||||
|
|
||||||
export const CropperDiv = (props) => {
|
export const CropperDiv = (props) => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [options, setOptions] = useState([]);
|
||||||
const [image, setImage] = useState("");
|
const [image, setImage] = useState("");
|
||||||
const [cropper, setCropper] = useState();
|
const [cropper, setCropper] = useState();
|
||||||
const [visible, setVisible] = React.useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [confirmLoading, setConfirmLoading] = React.useState(false);
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
const {title} = props;
|
const {title} = props;
|
||||||
const {user} = props;
|
const {user} = props;
|
||||||
const {buttonText} = props;
|
const {buttonText} = props;
|
||||||
@ -60,7 +62,7 @@ export const CropperDiv = (props) => {
|
|||||||
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
|
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
window.location.href = "/account";
|
window.location.href = window.location.pathname;
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", res.msg);
|
Setting.showMessage("error", res.msg);
|
||||||
}
|
}
|
||||||
@ -88,6 +90,48 @@ export const CropperDiv = (props) => {
|
|||||||
uploadButton.click();
|
uploadButton.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getOptions = (data) => {
|
||||||
|
const options = [];
|
||||||
|
if (props.account.organization.defaultAvatar !== null) {
|
||||||
|
options.push({value: props.account.organization.defaultAvatar});
|
||||||
|
}
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].fileType === "image") {
|
||||||
|
const url = `${data[i].url}`;
|
||||||
|
options.push({
|
||||||
|
value: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBase64Image = (src) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = src;
|
||||||
|
image.setAttribute("crossOrigin", "anonymous");
|
||||||
|
image.onload = () => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = image.width;
|
||||||
|
canvas.height = image.height;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.drawImage(image, 0, 0, image.width, image.height);
|
||||||
|
const dataURL = canvas.toDataURL("image/png");
|
||||||
|
resolve(dataURL);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
ResourceBackend.getResources(props.account.owner, props.account.name, "", "", "", "", "", "")
|
||||||
|
.then((res) => {
|
||||||
|
setLoading(false);
|
||||||
|
setOptions(getOptions(res));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="default" onClick={showModal}>
|
<Button type="default" onClick={showModal}>
|
||||||
@ -105,10 +149,20 @@ export const CropperDiv = (props) => {
|
|||||||
[<Button block key="submit" type="primary" onClick={handleOk}>{i18next.t("user:Set new profile picture")}</Button>]
|
[<Button block key="submit" type="primary" onClick={handleOk}>{i18next.t("user:Set new profile picture")}</Button>]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}>
|
<Col style={{margin: "0px auto 60px auto", width: 1000, height: 350}}>
|
||||||
<Row style={{width: "100%", marginBottom: "20px"}}>
|
<Row style={{width: "100%", marginBottom: "20px"}}>
|
||||||
<input style={{display: "none"}} ref={input => uploadButton = input} type="file" accept="image/*" onChange={onChange} />
|
<input style={{display: "none"}} ref={input => uploadButton = input} type="file" accept="image/*" onChange={onChange} />
|
||||||
<Button block onClick={selectFile}>{i18next.t("user:Select a photo...")}</Button>
|
<Button block onClick={selectFile}>{i18next.t("user:Select a photo...")}</Button>
|
||||||
|
<Select
|
||||||
|
style={{width: "100%"}}
|
||||||
|
loading={loading}
|
||||||
|
placeholder={i18next.t("user:Please select avatar from resources")}
|
||||||
|
onChange={(async value => {
|
||||||
|
setImage(await getBase64Image(value));
|
||||||
|
})}
|
||||||
|
options={options}
|
||||||
|
allowClear={true}
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Cropper
|
<Cropper
|
||||||
style={{height: "100%"}}
|
style={{height: "100%"}}
|
||||||
|
@ -35,6 +35,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
permissionName: props.match.params.permissionName,
|
permissionName: props.match.params.permissionName,
|
||||||
permission: null,
|
permission: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
|
model: null,
|
||||||
users: [],
|
users: [],
|
||||||
roles: [],
|
roles: [],
|
||||||
models: [],
|
models: [],
|
||||||
@ -59,6 +60,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
this.getRoles(permission.owner);
|
this.getRoles(permission.owner);
|
||||||
this.getModels(permission.owner);
|
this.getModels(permission.owner);
|
||||||
this.getResources(permission.owner);
|
this.getResources(permission.owner);
|
||||||
|
this.getModel(permission.owner, permission.model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +100,15 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getModel(organizationName, modelName) {
|
||||||
|
ModelBackend.getModel(organizationName, modelName)
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
model: res,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getResources(organizationName) {
|
getResources(organizationName) {
|
||||||
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
|
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -115,6 +126,10 @@ class PermissionEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePermissionField(key, value) {
|
updatePermissionField(key, value) {
|
||||||
|
if (key === "model") {
|
||||||
|
this.getModel(this.state.permission.owner, value);
|
||||||
|
}
|
||||||
|
|
||||||
value = this.parsePermissionField(key, value);
|
value = this.parsePermissionField(key, value);
|
||||||
|
|
||||||
const permission = this.state.permission;
|
const permission = this.state.permission;
|
||||||
@ -124,6 +139,13 @@ class PermissionEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasRoleDefinition(model) {
|
||||||
|
if (model !== null) {
|
||||||
|
return model.modelText.includes("role_definition");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
renderPermission() {
|
renderPermission() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -214,7 +236,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} mode="tags" style={{width: "100%"}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField("roles", value);})}>
|
<Select virtual={false} disabled={!this.hasRoleDefinition(this.state.model)} mode="tags" style={{width: "100%"}} value={this.state.permission.roles} onChange={(value => {this.updatePermissionField("roles", value);})}>
|
||||||
{
|
{
|
||||||
this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>)
|
this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission, index) => <Option key={index} value={`${permission.owner}/${permission.name}`}>{`${permission.owner}/${permission.name}`}</Option>)
|
||||||
}
|
}
|
||||||
|
@ -341,7 +341,7 @@ class PermissionListPage extends BaseListPage {
|
|||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
|
|
||||||
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
|
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
|
||||||
getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
getPermissions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -25,7 +25,7 @@ class RoleListPage extends BaseListPage {
|
|||||||
newRole() {
|
newRole() {
|
||||||
const randomName = Setting.getRandomName();
|
const randomName = Setting.getRandomName();
|
||||||
return {
|
return {
|
||||||
owner: "built-in",
|
owner: this.props.account.owner,
|
||||||
name: `role_${randomName}`,
|
name: `role_${randomName}`,
|
||||||
createdTime: moment().format(),
|
createdTime: moment().format(),
|
||||||
displayName: `New Role - ${randomName}`,
|
displayName: `New Role - ${randomName}`,
|
||||||
@ -211,7 +211,7 @@ class RoleListPage extends BaseListPage {
|
|||||||
value = params.type;
|
value = params.type;
|
||||||
}
|
}
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
RoleBackend.getRoles(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -37,8 +37,8 @@ class SelectLanguageBox extends React.Component {
|
|||||||
Setting.changeLanguage(e.key);
|
Setting.changeLanguage(e.key);
|
||||||
}}>
|
}}>
|
||||||
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
|
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
|
||||||
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
|
|
||||||
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
|
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
|
||||||
|
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
|
||||||
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
|
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
|
||||||
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
|
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
|
||||||
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
|
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
|
||||||
@ -49,7 +49,7 @@ class SelectLanguageBox extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown overlay={menu} >
|
<Dropdown overlay={menu} >
|
||||||
<div className="language_box" />
|
<div className="language-box" id={this.props.id} style={this.props.style} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -554,7 +554,7 @@ export function changeLanguage(language) {
|
|||||||
localStorage.setItem("language", language);
|
localStorage.setItem("language", language);
|
||||||
changeMomentLanguage(language);
|
changeMomentLanguage(language);
|
||||||
i18next.changeLanguage(language);
|
i18next.changeLanguage(language);
|
||||||
window.location.reload(true);
|
// window.location.reload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeMomentLanguage(language) {
|
export function changeMomentLanguage(language) {
|
||||||
|
@ -17,7 +17,6 @@ import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd"
|
|||||||
import * as UserBackend from "./backend/UserBackend";
|
import * as UserBackend from "./backend/UserBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import CropperDiv from "./CropperDiv.js";
|
import CropperDiv from "./CropperDiv.js";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
@ -232,16 +231,6 @@ class UserEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{i18next.t("general:URL")}:
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Input prefix={<LinkOutlined />} value={this.state.user.avatar} onChange={e => {
|
|
||||||
this.updateUserField("avatar", e.target.value);
|
|
||||||
}} />
|
|
||||||
</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}>
|
||||||
{i18next.t("general:Preview")}:
|
{i18next.t("general:Preview")}:
|
||||||
@ -661,7 +650,7 @@ class UserEditPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
this.state.loading ? <Spin size="large" /> : (
|
this.state.loading ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
|
||||||
this.state.user !== null ? this.renderUser() :
|
this.state.user !== null ? this.renderUser() :
|
||||||
<Result
|
<Result
|
||||||
status="404"
|
status="404"
|
||||||
|
@ -374,7 +374,7 @@ class UserListPage extends BaseListPage {
|
|||||||
const sortField = params.sortField, sortOrder = params.sortOrder;
|
const sortField = params.sortField, sortOrder = params.sortOrder;
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
if (this.state.organizationName === undefined) {
|
if (this.state.organizationName === undefined) {
|
||||||
UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
(Setting.isAdminUser(this.props.account) ? UserBackend.getGlobalUsers(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) : UserBackend.getUsers(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -19,40 +19,6 @@ import * as UserWebauthnBackend from "./backend/UserWebauthnBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
class WebAuthnCredentialTable extends React.Component {
|
class WebAuthnCredentialTable extends React.Component {
|
||||||
render() {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: i18next.t("user:WebAuthn credentials"),
|
|
||||||
dataIndex: "ID",
|
|
||||||
key: "ID",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: i18next.t("general:Action"),
|
|
||||||
key: "action",
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
|
|
||||||
{i18next.t("general:Delete")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table scroll={{x: "max-content"}} rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
|
|
||||||
title={() => (
|
|
||||||
<div>
|
|
||||||
{i18next.t("user:WebAuthn credentials")}
|
|
||||||
<Button disabled={!this.props.isSelf} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
|
|
||||||
{i18next.t("general:Add")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteRow(table, i) {
|
deleteRow(table, i) {
|
||||||
table = Setting.deleteRow(table, i);
|
table = Setting.deleteRow(table, i);
|
||||||
this.props.updateTable(table);
|
this.props.updateTable(table);
|
||||||
@ -71,6 +37,41 @@ class WebAuthnCredentialTable extends React.Component {
|
|||||||
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
Setting.showMessage("error", `Failed to connect to server: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Name"),
|
||||||
|
dataIndex: "ID",
|
||||||
|
key: "ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
key: "action",
|
||||||
|
width: "170px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Button style={{marginTop: "5px", marginBottom: "5px", marginRight: "5px"}} type="danger" onClick={() => {this.deleteRow(this.props.table, index);}}>
|
||||||
|
{i18next.t("general:Delete")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{i18next.t("user:WebAuthn credentials")}
|
||||||
|
<Button disabled={!this.props.isSelf} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => {this.registerWebAuthn();}}>
|
||||||
|
{i18next.t("general:Add")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WebAuthnCredentialTable;
|
export default WebAuthnCredentialTable;
|
||||||
|
@ -37,7 +37,7 @@ export function getEmailAndPhone(values) {
|
|||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
function oAuthParamsToQuery(oAuthParams) {
|
export function oAuthParamsToQuery(oAuthParams) {
|
||||||
// login
|
// login
|
||||||
if (oAuthParams === null) {
|
if (oAuthParams === null) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -28,6 +28,8 @@ import SelfLoginButton from "./SelfLoginButton";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../CustomGithubCorner";
|
||||||
import {CountDownInput} from "../common/CountDownInput";
|
import {CountDownInput} from "../common/CountDownInput";
|
||||||
|
import SelectLanguageBox from "../SelectLanguageBox";
|
||||||
|
import {withTranslation} from "react-i18next";
|
||||||
|
|
||||||
const {TabPane} = Tabs;
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
@ -41,7 +43,6 @@ class LoginPage extends React.Component {
|
|||||||
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
|
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
|
||||||
application: null,
|
application: null,
|
||||||
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
|
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
|
||||||
isCodeSignin: false,
|
|
||||||
msg: null,
|
msg: null,
|
||||||
username: null,
|
username: null,
|
||||||
validEmailOrPhone: false,
|
validEmailOrPhone: false,
|
||||||
@ -155,8 +156,8 @@ class LoginPage extends React.Component {
|
|||||||
values["type"] = "saml";
|
values["type"] = "saml";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.owner !== null && this.state.owner !== undefined) {
|
if (this.state.application.organization !== null && this.state.application.organization !== undefined) {
|
||||||
values["organization"] = this.state.owner;
|
values["organization"] = this.state.application.organization;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
postCodeLoginAction(res) {
|
postCodeLoginAction(res) {
|
||||||
@ -349,7 +350,7 @@ class LoginPage extends React.Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: (_, value) => {
|
validator: (_, value) => {
|
||||||
if (this.state.isCodeSignin) {
|
if (this.state.loginMethod === "verificationCode") {
|
||||||
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||||
this.setState({validEmailOrPhone: false});
|
this.setState({validEmailOrPhone: false});
|
||||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||||
@ -372,7 +373,7 @@ class LoginPage extends React.Component {
|
|||||||
<Input
|
<Input
|
||||||
id = "input"
|
id = "input"
|
||||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||||
placeholder={this.state.isCodeSignin ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
|
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
|
||||||
disabled={!application.enablePassword}
|
disabled={!application.enablePassword}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -399,29 +400,17 @@ class LoginPage extends React.Component {
|
|||||||
</a>
|
</a>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
{
|
<Button
|
||||||
this.state.loginMethod === "password" ?
|
type="primary"
|
||||||
(
|
htmlType="submit"
|
||||||
<Button
|
style={{width: "100%", marginBottom: "5px"}}
|
||||||
type="primary"
|
disabled={!application.enablePassword}
|
||||||
htmlType="submit"
|
>
|
||||||
style={{width: "100%", marginBottom: "5px"}}
|
{
|
||||||
disabled={!application.enablePassword}
|
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
|
||||||
>
|
i18next.t("login:Sign In")
|
||||||
{i18next.t("login:Sign In")}
|
}
|
||||||
</Button>
|
</Button>
|
||||||
) :
|
|
||||||
(
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
style={{width: "100%", marginBottom: "5px"}}
|
|
||||||
disabled={!application.enablePassword}
|
|
||||||
>
|
|
||||||
{i18next.t("login:Sign in with WebAuthn")}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
this.renderFooter(application)
|
this.renderFooter(application)
|
||||||
}
|
}
|
||||||
@ -479,19 +468,6 @@ class LoginPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span style={{float: "left"}}>
|
|
||||||
{
|
|
||||||
!application.enableCodeSignin ? null : (
|
|
||||||
<a onClick={() => {
|
|
||||||
this.setState({
|
|
||||||
isCodeSignin: !this.state.isCodeSignin,
|
|
||||||
});
|
|
||||||
}}>
|
|
||||||
{this.state.isCodeSignin ? i18next.t("login:Sign in with password") : i18next.t("login:Sign in with code")}
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
<span style={{float: "right"}}>
|
<span style={{float: "right"}}>
|
||||||
{
|
{
|
||||||
!application.enableSignUp ? null : (
|
!application.enableSignUp ? null : (
|
||||||
@ -599,7 +575,7 @@ class LoginPage extends React.Component {
|
|||||||
const rawId = assertion.rawId;
|
const rawId = assertion.rawId;
|
||||||
const sig = assertion.response.signature;
|
const sig = assertion.response.signature;
|
||||||
const userHandle = assertion.response.userHandle;
|
const userHandle = assertion.response.userHandle;
|
||||||
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish`, {
|
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/finish${AuthBackend.oAuthParamsToQuery(oAuthParams)}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -639,7 +615,23 @@ class LoginPage extends React.Component {
|
|||||||
renderPasswordOrCodeInput() {
|
renderPasswordOrCodeInput() {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (this.state.loginMethod === "password") {
|
if (this.state.loginMethod === "password") {
|
||||||
return this.state.isCodeSignin ? (
|
return (
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||||
|
type="password"
|
||||||
|
placeholder={i18next.t("login:Password")}
|
||||||
|
disabled={!application.enablePassword}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
} else if (this.state.loginMethod === "verificationCode") {
|
||||||
|
return (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="code"
|
name="code"
|
||||||
@ -652,32 +644,29 @@ class LoginPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
) : (
|
|
||||||
<Col span={24}>
|
|
||||||
<Form.Item
|
|
||||||
name="password"
|
|
||||||
rules={[{required: true, message: i18next.t("login:Please input your password!")}]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
|
||||||
type="password"
|
|
||||||
placeholder={i18next.t("login:Password")}
|
|
||||||
disabled={!application.enablePassword}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMethodChoiceBox() {
|
renderMethodChoiceBox() {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application.enableWebAuthn) {
|
if (application.enableCodeSignin || application.enableWebAuthn) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Tabs defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
|
<Tabs size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
|
||||||
<TabPane tab={i18next.t("login:Password")} key="password" />
|
<TabPane tab={i18next.t("login:Password")} key="password" />
|
||||||
<TabPane tab={"WebAuthn"} key="webAuthn" />
|
{
|
||||||
|
!application.enableCodeSignin ? null : (
|
||||||
|
<TabPane tab={i18next.t("login:Verification Code")} key="verificationCode" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!application.enableWebAuthn ? null : (
|
||||||
|
<TabPane tab={i18next.t("login:WebAuthn")} key="webAuthn" />
|
||||||
|
)
|
||||||
|
}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -711,27 +700,29 @@ class LoginPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
||||||
<CustomGithubCorner />
|
<CustomGithubCorner />
|
||||||
<Row >
|
<Row>
|
||||||
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}>
|
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}}>
|
||||||
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}>
|
<div className="login-content">
|
||||||
<div>
|
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center", ...formStyle}}>
|
||||||
{
|
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "80px" : "45px", right: formStyle !== null ? "5px" : "-45px"}} />
|
||||||
Setting.renderHelmet(application)
|
<div>
|
||||||
}
|
{
|
||||||
{
|
Setting.renderHelmet(application)
|
||||||
Setting.renderLogo(application)
|
}
|
||||||
}
|
{
|
||||||
{/* {*/}
|
Setting.renderLogo(application)
|
||||||
{/* this.state.clientId !== null ? "Redirect" : null*/}
|
}
|
||||||
{/* }*/}
|
{/* {*/}
|
||||||
{
|
{/* this.state.clientId !== null ? "Redirect" : null*/}
|
||||||
this.renderSignedInBox()
|
{/* }*/}
|
||||||
}
|
{
|
||||||
{
|
this.renderSignedInBox()
|
||||||
this.renderForm(application)
|
}
|
||||||
}
|
{
|
||||||
|
this.renderForm(application)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -740,4 +731,4 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginPage;
|
export default withTranslation()(LoginPage);
|
||||||
|
@ -129,6 +129,32 @@ export function renderProviderLogo(provider, application, width, margin, size, l
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (provider.type === "Custom") {
|
||||||
|
// style definition
|
||||||
|
const text = i18next.t("login:Sign in with {type}").replace("{type}", provider.displayName);
|
||||||
|
const customAStyle = {display: "block", height: "55px", color: "#000"};
|
||||||
|
const customButtonStyle = {display: "flex", alignItems: "center", width: "calc(100% - 10px)", height: "50px", margin: "5px", padding: "0 10px", backgroundColor: "transparent", boxShadow: "0px 1px 3px rgba(0,0,0,0.5)", border: "0px", borderRadius: "3px", cursor: "pointer"};
|
||||||
|
const customImgStyle = {justfyContent: "space-between"};
|
||||||
|
const customSpanStyle = {textAlign: "center", lineHeight: "50px", width: "100%", fontSize: "19px"};
|
||||||
|
if (provider.category === "OAuth") {
|
||||||
|
return (
|
||||||
|
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "signup")} style={customAStyle}>
|
||||||
|
<button style={customButtonStyle}>
|
||||||
|
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
|
||||||
|
<span style={customSpanStyle}>{text}</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else if (provider.category === "SAML") {
|
||||||
|
return (
|
||||||
|
<a key={provider.displayName} onClick={() => getSamlUrl(provider, location)} style={customAStyle}>
|
||||||
|
<button style={customButtonStyle}>
|
||||||
|
<img width={26} src={getProviderLogoURL(provider)} alt={provider.displayName} style={customImgStyle} />
|
||||||
|
<span style={customSpanStyle}>{text}</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
<div key={provider.displayName} style={{marginBottom: "10px"}}>
|
||||||
|
@ -25,6 +25,7 @@ import * as ApplicationBackend from "../backend/ApplicationBackend";
|
|||||||
import {CountDownInput} from "../common/CountDownInput";
|
import {CountDownInput} from "../common/CountDownInput";
|
||||||
import SelectRegionBox from "../SelectRegionBox";
|
import SelectRegionBox from "../SelectRegionBox";
|
||||||
import CustomGithubCorner from "../CustomGithubCorner";
|
import CustomGithubCorner from "../CustomGithubCorner";
|
||||||
|
import SelectLanguageBox from "../SelectLanguageBox";
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {
|
labelCol: {
|
||||||
@ -622,16 +623,19 @@ class SignupPage extends React.Component {
|
|||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} >
|
<Col span={8} offset={application.formOffset === 0 || Setting.inIframe() || Setting.isMobile() ? 8 : application.formOffset} style={{display: "flex", justifyContent: "center"}} >
|
||||||
<div style={{marginBottom: "10px", textAlign: "center", ...formStyle}}>
|
<div className="login-content">
|
||||||
{
|
<div style={{marginBottom: "10px", textAlign: "center", ...formStyle}}>
|
||||||
Setting.renderHelmet(application)
|
<SelectLanguageBox id="language-box-corner" style={{top: formStyle !== null ? "3px" : "-20px", right: formStyle !== null ? "5px" : "-45px"}} />
|
||||||
}
|
{
|
||||||
{
|
Setting.renderHelmet(application)
|
||||||
Setting.renderLogo(application)
|
}
|
||||||
}
|
{
|
||||||
{
|
Setting.renderLogo(application)
|
||||||
this.renderForm(application)
|
}
|
||||||
}
|
{
|
||||||
|
this.renderForm(application)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -44,10 +44,11 @@ class SingleCard extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card.Grid style={gridStyle} onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}>
|
<Card.Grid style={gridStyle} onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}>
|
||||||
<img src={logo} alt="logo" height={60} style={{marginBottom: "20px"}} />
|
<img src={logo} alt="logo" width={"100%"} style={{marginBottom: "20px"}} />
|
||||||
<Meta
|
<Meta
|
||||||
title={title}
|
title={title}
|
||||||
description={desc}
|
description={desc}
|
||||||
|
style={{justifyContent: "center"}}
|
||||||
/>
|
/>
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
);
|
);
|
||||||
@ -61,7 +62,7 @@ class SingleCard extends React.Component {
|
|||||||
<Card
|
<Card
|
||||||
hoverable
|
hoverable
|
||||||
cover={
|
cover={
|
||||||
<img alt="logo" src={logo} style={{width: "100%", height: "210px", objectFit: "scale-down"}} />
|
<img alt="logo" src={logo} style={{width: "100%", objectFit: "scale-down"}} />
|
||||||
}
|
}
|
||||||
onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}
|
onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}
|
||||||
style={isSingle ? {width: "320px"} : {width: "100%"}}
|
style={isSingle ? {width: "320px"} : {width: "100%"}}
|
||||||
|
@ -87,7 +87,7 @@ export const CaptchaPreview = ({
|
|||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
height: "80px",
|
height: "80px",
|
||||||
width: "200px",
|
width: "200px",
|
||||||
borderRadius: "3px",
|
borderRadius: "5px",
|
||||||
border: "1px solid #ccc",
|
border: "1px solid #ccc",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
|
@ -101,7 +101,7 @@ export const CountDownInput = (props) => {
|
|||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
height: "80px",
|
height: "80px",
|
||||||
width: "200px",
|
width: "200px",
|
||||||
borderRadius: "3px",
|
borderRadius: "5px",
|
||||||
border: "1px solid #ccc",
|
border: "1px solid #ccc",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
|
@ -23,6 +23,7 @@ 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 * as Setting from "./Setting";
|
||||||
|
import {initReactI18next} from "react-i18next";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
en: en,
|
en: en,
|
||||||
@ -80,7 +81,7 @@ function initLanguage() {
|
|||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n.init({
|
i18n.use(initReactI18next).init({
|
||||||
lng: initLanguage(),
|
lng: initLanguage(),
|
||||||
|
|
||||||
resources: resources,
|
resources: resources,
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client-IP",
|
"Client IP": "Client-IP",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "Erstellte Zeit",
|
"Created time": "Erstellte Zeit",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "App's image tag",
|
"Logo - Tooltip": "App's image tag",
|
||||||
"Master password": "Master-Passwort",
|
"Master password": "Master-Passwort",
|
||||||
"Master password - Tooltip": "Masterpasswort - Tooltip",
|
"Master password - Tooltip": "Masterpasswort - Tooltip",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "Methode",
|
"Method": "Methode",
|
||||||
"Model": "Model",
|
"Model": "Model",
|
||||||
"Model - Tooltip": "Model - Tooltip",
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
|
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
|
||||||
"Sign In": "Anmelden",
|
"Sign In": "Anmelden",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "Mit Code anmelden",
|
|
||||||
"Sign in with password": "Mit Passwort anmelden",
|
|
||||||
"Sign in with {type}": "Mit {type} anmelden",
|
"Sign in with {type}": "Mit {type} anmelden",
|
||||||
"Signing in...": "Anmelden...",
|
"Signing in...": "Anmelden...",
|
||||||
"The input is not valid Email or Phone!": "Die Eingabe ist keine gültige E-Mail oder Telefon!",
|
"The input is not valid Email or Phone!": "Die Eingabe ist keine gültige E-Mail oder Telefon!",
|
||||||
"To access": "Zu Zugriff",
|
"To access": "Zu Zugriff",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "jetzt anmelden",
|
"sign up now": "jetzt anmelden",
|
||||||
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "Logo - Tooltip",
|
"Logo - Tooltip": "Logo - Tooltip",
|
||||||
"Master password": "Master password",
|
"Master password": "Master password",
|
||||||
"Master password - Tooltip": "Master password - Tooltip",
|
"Master password - Tooltip": "Master password - Tooltip",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "Method",
|
"Method": "Method",
|
||||||
"Model": "Model",
|
"Model": "Model",
|
||||||
"Model - Tooltip": "Model - Tooltip",
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||||
"Sign In": "Sign In",
|
"Sign In": "Sign In",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "Sign in with code",
|
|
||||||
"Sign in with password": "Sign in with password",
|
|
||||||
"Sign in with {type}": "Sign in with {type}",
|
"Sign in with {type}": "Sign in with {type}",
|
||||||
"Signing in...": "Signing in...",
|
"Signing in...": "Signing in...",
|
||||||
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
|
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
|
||||||
"To access": "To access",
|
"To access": "To access",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "sign up now",
|
"sign up now": "sign up now",
|
||||||
"username, Email or phone": "username, Email or phone"
|
"username, Email or phone": "username, Email or phone"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Certes",
|
"Certs": "Certes",
|
||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "IP du client",
|
"Client IP": "IP du client",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "Date de création",
|
"Created time": "Date de création",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "App's image tag",
|
"Logo - Tooltip": "App's image tag",
|
||||||
"Master password": "Mot de passe maître",
|
"Master password": "Mot de passe maître",
|
||||||
"Master password - Tooltip": "Mot de passe maître - Infobulle",
|
"Master password - Tooltip": "Mot de passe maître - Infobulle",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "Méthode",
|
"Method": "Méthode",
|
||||||
"Model": "Model",
|
"Model": "Model",
|
||||||
"Model - Tooltip": "Model - Tooltip",
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
|
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
|
||||||
"Sign In": "Se connecter",
|
"Sign In": "Se connecter",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "Se connecter avec le code",
|
|
||||||
"Sign in with password": "Se connecter avec le mot de passe",
|
|
||||||
"Sign in with {type}": "Se connecter avec {type}",
|
"Sign in with {type}": "Se connecter avec {type}",
|
||||||
"Signing in...": "Connexion en cours...",
|
"Signing in...": "Connexion en cours...",
|
||||||
"The input is not valid Email or Phone!": "L'entrée n'est pas valide Email ou Téléphone !",
|
"The input is not valid Email or Phone!": "L'entrée n'est pas valide Email ou Téléphone !",
|
||||||
"To access": "Pour accéder à",
|
"To access": "Pour accéder à",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "inscrivez-vous maintenant",
|
"sign up now": "inscrivez-vous maintenant",
|
||||||
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
|
"username, Email or phone": "nom d'utilisateur, e-mail ou téléphone"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "クライアント IP",
|
"Client IP": "クライアント IP",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "作成日時",
|
"Created time": "作成日時",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "App's image tag",
|
"Logo - Tooltip": "App's image tag",
|
||||||
"Master password": "マスターパスワード",
|
"Master password": "マスターパスワード",
|
||||||
"Master password - Tooltip": "マスターパスワード - ツールチップ",
|
"Master password - Tooltip": "マスターパスワード - ツールチップ",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
"Model": "Model",
|
"Model": "Model",
|
||||||
"Model - Tooltip": "Model - Tooltip",
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
|
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
|
||||||
"Sign In": "サインイン",
|
"Sign In": "サインイン",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "コードでサインイン",
|
|
||||||
"Sign in with password": "パスワードでサインイン",
|
|
||||||
"Sign in with {type}": "{type} でサインイン",
|
"Sign in with {type}": "{type} でサインイン",
|
||||||
"Signing in...": "サインイン中...",
|
"Signing in...": "サインイン中...",
|
||||||
"The input is not valid Email or Phone!": "入力されたメールアドレスまたは電話番号が正しくありません。",
|
"The input is not valid Email or Phone!": "入力されたメールアドレスまたは電話番号が正しくありません。",
|
||||||
"To access": "アクセスするには",
|
"To access": "アクセスするには",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "今すぐサインアップ",
|
"sign up now": "今すぐサインアップ",
|
||||||
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
|
"username, Email or phone": "ユーザー名、メールアドレスまたは電話番号"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Certs",
|
"Certs": "Certs",
|
||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "App's image tag",
|
"Logo - Tooltip": "App's image tag",
|
||||||
"Master password": "Master password",
|
"Master password": "Master password",
|
||||||
"Master password - Tooltip": "Master password - Tooltip",
|
"Master password - Tooltip": "Master password - Tooltip",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "Method",
|
"Method": "Method",
|
||||||
"Model": "Model",
|
"Model": "Model",
|
||||||
"Model - Tooltip": "Model - Tooltip",
|
"Model - Tooltip": "Model - Tooltip",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||||
"Sign In": "Sign In",
|
"Sign In": "Sign In",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "Sign in with code",
|
|
||||||
"Sign in with password": "Sign in with password",
|
|
||||||
"Sign in with {type}": "Sign in with {type}",
|
"Sign in with {type}": "Sign in with {type}",
|
||||||
"Signing in...": "Signing in...",
|
"Signing in...": "Signing in...",
|
||||||
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
|
"The input is not valid Email or Phone!": "The input is not valid Email or Phone!",
|
||||||
"To access": "To access",
|
"To access": "To access",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "sign up now",
|
"sign up now": "sign up now",
|
||||||
"username, Email or phone": "username, Email or phone"
|
"username, Email or phone": "username, Email or phone"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "Сертификаты",
|
"Certs": "Сертификаты",
|
||||||
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
||||||
"Client IP": "IP клиента",
|
"Client IP": "IP клиента",
|
||||||
|
"Close": "Close",
|
||||||
"Created time": "Время создания",
|
"Created time": "Время создания",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "App's image tag",
|
"Logo - Tooltip": "App's image tag",
|
||||||
"Master password": "Мастер-пароль",
|
"Master password": "Мастер-пароль",
|
||||||
"Master password - Tooltip": "Мастер-пароль - Tooltip",
|
"Master password - Tooltip": "Мастер-пароль - Tooltip",
|
||||||
|
"Menu": "Menu",
|
||||||
"Method": "Метод",
|
"Method": "Метод",
|
||||||
"Model": "Модель",
|
"Model": "Модель",
|
||||||
"Model - Tooltip": "Модель - Подсказка",
|
"Model - Tooltip": "Модель - Подсказка",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
|
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
|
||||||
"Sign In": "Войти",
|
"Sign In": "Войти",
|
||||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||||
"Sign in with code": "Войти с помощью кода",
|
|
||||||
"Sign in with password": "Войти с помощью пароля",
|
|
||||||
"Sign in with {type}": "Войти с помощью {type}",
|
"Sign in with {type}": "Войти с помощью {type}",
|
||||||
"Signing in...": "Вход...",
|
"Signing in...": "Вход...",
|
||||||
"The input is not valid Email or Phone!": "Введен неверный адрес электронной почты или телефон!",
|
"The input is not valid Email or Phone!": "Введен неверный адрес электронной почты или телефон!",
|
||||||
"To access": "На доступ",
|
"To access": "На доступ",
|
||||||
|
"Verification Code": "Verification Code",
|
||||||
|
"WebAuthn": "WebAuthn",
|
||||||
"sign up now": "зарегистрироваться",
|
"sign up now": "зарегистрироваться",
|
||||||
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
|
"username, Email or phone": "имя пользователя, адрес электронной почты или телефон"
|
||||||
},
|
},
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
"Certs": "证书",
|
"Certs": "证书",
|
||||||
"Click to Upload": "点击上传",
|
"Click to Upload": "点击上传",
|
||||||
"Client IP": "客户端IP",
|
"Client IP": "客户端IP",
|
||||||
|
"Close": "关闭",
|
||||||
"Created time": "创建时间",
|
"Created time": "创建时间",
|
||||||
"Default application": "默认应用",
|
"Default application": "默认应用",
|
||||||
"Default application - Tooltip": "默认应用",
|
"Default application - Tooltip": "默认应用",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"Logo - Tooltip": "应用程序向外展示的图标",
|
"Logo - Tooltip": "应用程序向外展示的图标",
|
||||||
"Master password": "万能密码",
|
"Master password": "万能密码",
|
||||||
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
|
||||||
|
"Menu": "目录",
|
||||||
"Method": "方法",
|
"Method": "方法",
|
||||||
"Model": "模型",
|
"Model": "模型",
|
||||||
"Model - Tooltip": "Casbin模型",
|
"Model - Tooltip": "Casbin模型",
|
||||||
@ -277,12 +279,12 @@
|
|||||||
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号!",
|
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号!",
|
||||||
"Sign In": "登录",
|
"Sign In": "登录",
|
||||||
"Sign in with WebAuthn": "WebAuthn登录",
|
"Sign in with WebAuthn": "WebAuthn登录",
|
||||||
"Sign in with code": "验证码登录",
|
|
||||||
"Sign in with password": "密码登录",
|
|
||||||
"Sign in with {type}": "{type}登录",
|
"Sign in with {type}": "{type}登录",
|
||||||
"Signing in...": "正在登录...",
|
"Signing in...": "正在登录...",
|
||||||
"The input is not valid Email or Phone!": "您输入的电子邮箱格式或手机号有误!",
|
"The input is not valid Email or Phone!": "您输入的电子邮箱格式或手机号有误!",
|
||||||
"To access": "访问",
|
"To access": "访问",
|
||||||
|
"Verification Code": "验证码",
|
||||||
|
"WebAuthn": "Web身份验证",
|
||||||
"sign up now": "立即注册",
|
"sign up now": "立即注册",
|
||||||
"username, Email or phone": "用户名、Email或手机号"
|
"username, Email or phone": "用户名、Email或手机号"
|
||||||
},
|
},
|
||||||
@ -702,7 +704,7 @@
|
|||||||
"Unlink": "解绑",
|
"Unlink": "解绑",
|
||||||
"Upload (.xlsx)": "上传(.xlsx)",
|
"Upload (.xlsx)": "上传(.xlsx)",
|
||||||
"Upload a photo": "上传头像",
|
"Upload a photo": "上传头像",
|
||||||
"WebAuthn credentials": "WebAuthn credentials",
|
"WebAuthn credentials": "WebAuthn凭据",
|
||||||
"input password": "输入密码"
|
"input password": "输入密码"
|
||||||
},
|
},
|
||||||
"webhook": {
|
"webhook": {
|
||||||
|
Reference in New Issue
Block a user